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

  • Anonymous
    August 29, 2006
    Aaron Hallberg, a developer working on Team Build,&amp;nbsp;has taken the plunge and started a blog.&amp;nbsp;...
  • Anonymous
    August 29, 2006
    I learned from Buck Hodges that a developer on his Team Foundation Build team in North Carolina, Aaron...
  • Anonymous
    August 29, 2006
    One of the things that made working with the early betas and CTPs of VSTS great was the transparency
  • Anonymous
    August 30, 2006
    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,...
  • Anonymous
    September 01, 2006
    Buck Hodges on Team Build blogger: Aaron Halberg.

    And speaking of Aaron Halberg, here is his post...
  • Anonymous
    September 07, 2006
    一直在思考是否有真正的範例,來整合 Team Build 與 Java Compiler... Well .. 不幸的是 還真的沒有 超級現成的範例。 但至少目前 Aaron 的腳本 給了一點方向,透過...
  • Anonymous
    September 21, 2006
    In a forum post a while back, I laid out a method for determining whether tests had passed during a build.&amp;nbsp;...
  • Anonymous
    March 28, 2007
    Way back in August I did a post (my 2nd ever!) on adding build steps to Team Build using a custom task.
  • Anonymous
    March 30, 2007
    Building non-MSBuild projects in Team Build has never been a particularly nice experience... You can
  • Anonymous
    March 30, 2007
    Aaron Hallberg wrote a great post today showing how to use a custom task to better integrate other build
  • Anonymous
    April 04, 2007
    I've had several people inquire recently about how to figure out which files have changed since the previous
  • Anonymous
    April 04, 2007
    In a forum post a while back, I laid out a method for determining whether tests had passed during a build.
  • Anonymous
    April 04, 2007
    Most of the magic in a Team Build is done using either (a) customized tasks, or (b) a customized logger
  • Anonymous
    April 25, 2007
    I'm speaking at TechEd 2007 in Orlando this coming June on the whys and hows of customizing TFS. As part
  • Anonymous
    May 01, 2007
    Sharing this information from Jeff Beehler's Blog Aaron’s posts on how to extend team build through custom
  • Anonymous
    June 06, 2007
    As those who are doing unit testing in Visual Studio Team System will know, it is not always the easiest
  • Anonymous
    December 05, 2007
    DavidKean_MS (Moderator): The Visual Studio Team System chat will begin in 15 minutes. DavidKean_MS...
  • Anonymous
    February 22, 2008
    Hi Aaron,Could you please check my post at Microsoft forums?Here it is: http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2892292&SiteID=1I 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 :(
  • Anonymous
    April 20, 2008
    Is there a way to mention a URL in message so that we can click it and browse the URL ?
  • Anonymous
    June 06, 2008
    Hi Aaron,Do you have an update that will work with TFS 2008?
  • Anonymous
    June 09, 2008
    TFS 2008 includes a BuildStep task in the core product - see http://msdn.microsoft.com/en-us/library/bb399129.aspx.