Adding BuildSteps to Team Build through a Custom Task


Team Build displays Build Steps in the build report form within Visual Studio.  By default, build steps are added at various points during the course of a build – while getting sources (in the Get task), compiling solutions / projects, copying files to the drop location, etc.  Team Build allows users to insert their own build steps using the publicly accessible BuildStore web service – in particular, the AddBuildStep and UpdateBuildStep methods.  The following sample (I make no claims as to the awesomeness or lack thereof of this sample, etc.) TeamBuildTask class illustrates how this can be done:


using System;
using System.Web.Services;
using Microsoft.Build.Framework;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Build.Common;
using Microsoft.TeamFoundation.Build.Proxy;

/// <summary>
/// Summary description for Class1
/// </summary>
namespace MyNamespace
{
    public abstract class TeamBuildTask : ITask
    {
        /// <summary>
        /// Put real task logic in this method.
        /// </summary>
        /// <returns>True if task is successful, otherwise false.</returns>
        protected abstract bool ExecuteInternal();

        /// <summary>
        /// Returns the name of the build step to be added for this task.
        /// </summary>
        /// <returns>Name of the build step to be added.</returns>
        protected abstract string GetBuildStepName();

        /// <summary>
        /// Returns the message of the build step to be added for this task – this is the
        /// string displayed in the Team Build GUI.
        /// </summary>
        /// <returns>Message of the build step to be added.</returns>
        protected abstract string GetBuildStepMessage();

        /// <summary>
        /// ITask implementation – BuildEngine property.
        /// </summary>
        public IBuildEngine BuildEngine
        {
            get
            {
                return m_buildEngine;
            }
            set
            {
                m_buildEngine = value;
            }
        }

        /// <summary>
        /// ITask implementation – HostObject property.
        /// </summary>
        public ITaskHost HostObject
        {
            get
            {
                return m_hostObject;
            }
            set
            {
                m_hostObject = value;
            }
        }

        /// <summary>
        /// The Url of the Team Foundation Server.
        /// </summary>
        [Required]
        public string TeamFoundationServerUrl
        {
            get
            {
                return m_tfsUrl;
            }
            set
            {
                m_tfsUrl = value;
            }
        }

        /// <summary>
        /// The Uri of the Build for which this task is executing.
        /// </summary>
        [Required]
        public string BuildUri
        {
            get
            {
                return m_buildUri;
            }
            set
            {
                m_buildUri = value;
            }
        }

        /// <summary>
        /// Lazy init property that gives access to the TF Server specified by TeamFoundationServerUrl.
        /// </summary>
        protected TeamFoundationServer Tfs
        {
            get
            {
                if (m_tfs == null)
                {
                    if (String.IsNullOrEmpty(TeamFoundationServerUrl))
                    {
                        // Throw some exception.
                    }
                    m_tfs = TeamFoundationServerFactory.GetServer(TeamFoundationServerUrl);
                }
                return m_tfs;
            }
        }

        /// <summary>
        /// Lazy init property that gives access to the BuildStore service of the TF Server.
        /// </summary>
        protected BuildStore BuildStore
        {
            get
            {
                if (m_buildStore == null)
                {
                    m_buildStore = (BuildStore)Tfs.GetService(typeof(BuildStore));
                }
                return m_buildStore;
            }
        }

        /// <summary>
        /// ITask implementation – Execute method.
        /// </summary>
        /// <returns>
        /// True if the task succeeded, false otherwise.
        /// </returns>
        public bool Execute()
        {
            bool returnValue = false;

            try
            {
                AddBuildStep();
                returnValue = ExecuteInternal();
            }
            catch (Exception e)
            {
                AddExceptionBuildStep(e);
                throw;
            }
            finally
            {
                UpdateBuildStep(returnValue);
            }

            return returnValue;
        }

        private void AddBuildStep()
        {
            BuildStore.AddBuildStep(BuildUri, GetBuildStepName(), GetBuildStepMessage());
        }


 


        private void UpdateBuildStep(bool result)
        {


            BuildStepStatus status = result ? BuildStepStatus.Succeeded : BuildStepStatus.Failed;
            BuildStore.UpdateBuildStep(BuildUri, GetBuildStepName(), DateTime.Now, status);
        }



        private void AddExceptionBuildStep(Exception e)
        {
            try
            {
                BuildStore.AddBuildStep(BuildUri, “Exception“, e.Message);
                BuildStore.UpdateBuildStep(BuildUri, “Exception“, DateTime.Now, BuildStepStatus.Failed);
            }
            catch
            {
                // Eat any exceptions.
            }
        }

        private IBuildEngine m_buildEngine;
        private ITaskHost m_hostObject;
        private string m_tfsUrl;
        private string m_buildUri;
        private TeamFoundationServer m_tfs;
        private BuildStore m_buildStore;
    }
}


To use this base class, just override the ExecuteInternal method – put the actual task logic here.  Then override the GetBuildStepName and GetBuildStepMessage methods to specify the Name of the build step (which serves as its ID and should therefore be reasonably unique) and the Message of the build step (which will be the string displayed in the build report form).


The Execute method will add a build step (with the specified Name and Message) when task execution starts, and update the build step with the appropriate status and time when execution completes.  If an exception is thrown by the ExecuteInternal method, an exception build step will be added (if possible) by the AddExceptionBuildStep method.


Note the TeamFoundationServerUrl and BuildUri properties.  These properties will need to be set for derived tasks to function properly, and can be easily set to the TeamFoundationServerUrl and BuildURI properties available during a Team Build (e.g. within TfsBuild.proj).

<Target Name=“BeforeBuild”>
<SomeCustomTask
TeamFoundationServerUrl=“$(TeamFoundationServerUrl)”
BuildUri=“$(BuildURI)” />
</Target>

Happy building!

Comments (21)

  1. Buck Hodges says:

    Aaron Hallberg, a developer working on Team Build,&amp;nbsp;has taken the plunge and started a blog.&amp;nbsp;…

  2. Rob Caron says:

    I learned from Buck Hodges that a developer on his Team Foundation Build team in North Carolina, Aaron…

  3. Ponder .NET says:

    One of the things that made working with the early betas and CTPs of VSTS great was the transparency

  4. Most of the magic in a Team Build is done using either (a) customized tasks, or (b) a customized logger.&amp;nbsp;&amp;nbsp;Well,…

  5. Buck Hodges on Team Build blogger: Aaron Halberg.

    And speaking of Aaron Halberg, here is his post…

  6. 一直在思考是否有真正的範例,來整合 Team Build 與 Java Compiler… Well .. 不幸的是 還真的沒有 超級現成的範例。 但至少目前 Aaron 的腳本 給了一點方向,透過…

  7. In a forum post a while back, I laid out a method for determining whether tests had passed during a build.&amp;nbsp;…

  8. Way back in August I did a post (my 2nd ever!) on adding build steps to Team Build using a custom task.

  9. Building non-MSBuild projects in Team Build has never been a particularly nice experience… You can

  10. Buck Hodges says:

    Aaron Hallberg wrote a great post today showing how to use a custom task to better integrate other build

  11. I’ve had several people inquire recently about how to figure out which files have changed since the previous

  12. In a forum post a while back, I laid out a method for determining whether tests had passed during a build.

  13. Most of the magic in a Team Build is done using either (a) customized tasks, or (b) a customized logger

  14. I’m speaking at TechEd 2007 in Orlando this coming June on the whys and hows of customizing TFS. As part

  15. Sharing this information from Jeff Beehler’s Blog Aaron’s posts on how to extend team build through custom

  16. As those who are doing unit testing in Visual Studio Team System will know, it is not always the easiest

  17. DavidKean_MS (Moderator): The Visual Studio Team System chat will begin in 15 minutes. DavidKean_MS…

  18. AlexeyYumashin says:

    Hi Aaron,

    Could you please check my post at Microsoft forums?

    Here it is: http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2892292&SiteID=1

    I use the recommended approach under TFS 2005 and everything is OK except that BuildStepData.BuildStepName always returns an empty string (BuildStepData.BuildStepMessage returns correct value at the same time). Because of that I’m lack of unique ID to identify buils steps in my custom "Monitor build progress" dialog 🙁

  19. Raman Chandrayan says:

    Is there a way to mention a URL in message so that we can click it and browse the URL ?

  20. myost2921 says:

    Hi Aaron,

    Do you have an update that will work with TFS 2008?

  21. Garry Trinder says:

    TFS 2008 includes a BuildStep task in the core product – see http://msdn.microsoft.com/en-us/library/bb399129.aspx.