Wow - it's been a long time. Sorry for the insanely long delay between posts, loyal reader(s). A lot has happened since I last managed to post something on my blog:

  • I've been working like crazy, along with the rest of the Team Build team, to get our Continuous Integration solution finished up for the Orcas release (see Brian Harry's blog post here for the full TFS roadmap, or Buck Hodge's post here for more details on the Team Build features in Orcas).
  • I got hooked on that Ugly Betty show. At first the hourlong format put me off, but now I can't imagine it being any shorter.
  • Control of the House and Senate shifted to the Democrats.
  • Britney and Kevin broke up, just days after my wife and I dressed as the two of them for Halloween. Coincidence? Perhaps...
  • Vista, Office, and the Zune all shipped. One very positive aspect of the Zune launch from my point of view - my favorite radio station, KEXP, was heavily involved and various excellent bands (e.g. Band of Horses) have been featured in ad spots, etc.

In any case, during a conference call the other day, a Team Build user expressed a desire to easily insert build steps into a build from within a csproj file... In previous posts I have laid out custom tasks which, as part of their execution, insert build steps. In this post, I lay out a simpler custom task which inserts arbitrary text as a build step - think of it as a <Message> task in Team Build form.

Here's the code:

using System;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Build.Proxy;

namespace CustomTasks
{
    public class BuildStepTask : Task
    {
        /// <summary>
        /// Execute the task logic.
        /// </summary>
        /// <returns>True. Exceptions thrown on failure.</returns>
        public override bool Execute()
        {
            BuildStore.AddBuildStep(BuildUri, Text, Text);
            BuildStore.UpdateBuildStep(BuildUri, Text, DateTime.Now, m_buildStatus);

            return true;
        }

        /// <summary>
        /// The text of the build step.
        /// </summary>
        [Required]
        public String Text
        {
            get
            {
                return m_text;
            }
            set
            {
                m_text = value;
            }
        }

        /// <summary>
        /// The status of the build step. If not specified, BuildStepStatus.Succeeded will be assumed.
        /// </summary>
        public String Status
        {
            get
            {
                return m_buildStatus.ToString();
            }
            set
            {
                // Go ahead and throw an exception if the value is invalid.
                m_buildStatus = (BuildStepStatus)Enum.Parse(typeof(BuildStepStatus), 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)
                {
                    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;
            }
        }

        private String m_text;
        private BuildStepStatus m_buildStatus = BuildStepStatus.Succeeded;
        private String m_tfsUrl;
        private String m_buildUri;
        private TeamFoundationServer m_tfs;
        private BuildStore m_buildStore;
    }
}

And here's a snippet from the csproj file I used to test it out:

  <UsingTask TaskName="CustomTasks.BuildStepTask" AssemblyFile="..\TeamBuildTypes\HelloWorld\CustomTasks.dll" />

  <Target Name="AfterCompile" Condition=" '$(BuildUri)' != '' ">
    <BuildStepTask TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
                   BuildUri="$(BuildUri)"
                   Text="Finished compiling $(AssemblyName)" />
  </Target>

Note the condition on the Target to avoid running it outside of a Team Build environment. 

Hopefully this post will be the first of many in the months to come.