If you are working on a large project it is very probable that the project is composed of several parts/modules which are build separately. What are your options if you want to be able to rebuild the whole thing easily? If you are a Quake tournament winner equipped with Razer Copperhead mouse you could probably schedule 10 builds (in the right order) in a matter of seconds but what about your colleagues that are not so gifted? Well, there is the TfsBuild command line tool but in case you are using the 2008 version and you would like to have the freedom to choose a build agent when scheduling a build you are out of luck. It seems that the only viable option is to write a custom tool that talks directly to Team Build.


In my opinion the best strategy is to create a custom MSBuild task and integrate it into a Team Build project. At first it might seem that using MSBuild  and Team Build is an overkill, but I beg to differ. By using MSBuild we get many things for free – easy and well know method to feed data into our custom tool (properties and items), easy way to report errors, easy way to make modifications. By integrating the custom task into Team Build – i.e. creating a new build definition, we can easily schedule it to run automatically at a specified time. Moreover, everyone on the team can easily use it without the need to have some kind of a tool or script on his computer.

Ultimately we are looking at a “tool” that can be configured easily, can be run by everyone, and can also be scheduled to run automatically at a specified time of the day.

Custom MSBuild task

First we need to create our workhorse – the code that will actually talk to Team Build and schedule builds. Since we want to have a nice integration with MSBuild we create a custom task. In case you are not familiar with custom tasks you can find more information here . And here is our task:

   1: public class ScheduleBuild : Task
   2: {
   3:     [Required]
   4: public string BuildAgent { get; set; }
   6:     [Required]
   7: public string TfsUrl { get; set; }
   9:     [Required]
  10: public ITaskItem BuildDefinition { get; set; }
  12: public override bool Execute()
  13:     {
  14: // schedule build
  15:     }
  16: }

For the sake of brevity I have skipped all the details but a full implementation is attached to this post. 
The task receives three input parameters – BuildAgent, TfsUrl, and BuildDefinition – all of which are required. The first two are self explanatory but the last once is a bit tricky. The BuildDefinition is actually a pair of a team project name and a build definition name in the format “project name\definition name” – for example “WinForms\Core_API”. You might be wondering how we can schedule multiple builds when the task accepts only a single build definition? Well, I have decided to keep the task a bit simpler and rely on batching to schedule multiple builds. More on that later.
In case you like to create your own task that schedules builds you will need the following references:





Scheduling builds from MSBuild project

Now that we have the ScheduleBuild task we are ready to add it to a MSBuild project and use it. First we need to create a new build definition , for example, called RunAllBuilds. Once we have created RunAllBuilds we have to edit its project file. Since we only want to execute a single task we can strip the project file of its default content and we end up with a very simple file.

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <Project DefaultTargets="DesktopBuild" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
   4: <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets" />
   6: <!--Map the ScheduleBuild task to the assembly that contains the task's implementation-->
   7: <UsingTask AssemblyFile="PathToScheduleBuildAssembly" TaskName="ScheduleBuild"/>
   9: <!--The properties that the ScheduleBuild task will use-->
  10: <PropertyGroup>
  11: <ScheduleBuildTfsUrl>http://myvss:8080</ScheduleBuildTfsUrl>
  12: <BuildAgent>agent1</BuildAgent>
  13: </PropertyGroup>
  15: <!--List of all build definitions to be scheduled-->
  16: <ItemGroup>
  17: <BuildsToSchedule Include="WinForms\Core_API"/>
  18: <BuildsToSchedule Include="WinForms\AnotherCore_API"/>
  19: </ItemGroup>
  21: <Target Name="ScheduleBuilds">
  22: <ScheduleBuild
  23: BuildAgent="$(BuildAgent)"
  24: TfsUrl="$(ScheduleBuildTfsUrl)"
  25: BuildDefinition="%(BuildsToSchedule.Identity)"/>
  26: </Target>
  28: <!-- Override the Team Build script entry point and swap out its built-in logic entirely -->
  29: <Target Name="EndToEndIteration"
  30: DependsOnTargets="InitializeBuildProperties;InitializeWorkspace;Get;ScheduleBuilds">
  32: <!-- Mark the build as successful if we get to this point. -->
  33: <SetBuildProperties
  34: TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
  35: BuildUri="$(BuildUri)"
  36: CompilationStatus="Succeeded"
  37: TestStatus="Succeeded"
  38: Status="Succeeded" />
  39: </Target>
  40: </Project>

The file contains a property group where the address of a TFS server and the name of a build agent are specified. In the ItemGroup section (line 16) we provide a list of all builds that we want to be scheduled. Right after that there is a target definition called ScheduleBuilds that runs our custom task. As I have mentioned we use batching (BuildDefinition="%(BuildsToSchedule.Identity)") to instruct MSBuild to run the ScheduleBuild task for every item in the BuildToSchedule group. The last target (line 29) actually overrides the default build behavior – this is done to skip some build steps that are unnecessary for running our task.

In case we need to schedule builds on different agents we could get a little bit more creative:

   1: <!-- Builds to run on agent1-->
   2: <ItemGroup>
   3: <BuildsToSchedule Include="WinForms\Core_API">
   4: <BuildAgent>agent1</BuildAgent>
   5: </BuildsToSchedule>
   6: <BuildsToSchedule Include="WinForms\AnotherCore_API">
   7: <BuildAgent>agent1</BuildAgent>
   8: </BuildsToSchedule>
   9: </ItemGroup>
  11: <!-- Build to run on agent2-->
  12: <ItemGroup>
  13: <BuildsToSchedule Include="WinForms\Core_API2">
  14: <BuildAgent>agent2</BuildAgent>
  15: </BuildsToSchedule>
  16: <BuildsToSchedule Include="WinForms\AnotherCore_API2">
  17: <BuildAgent>agent2</BuildAgent>
  18: </BuildsToSchedule>
  19: </ItemGroup>
  21: <Target Name="ScheduleBuilds">
  22: <ScheduleBuild
  23: BuildAgent="%(BuildAgent)"
  24: TfsUrl="$(ScheduleBuildTfsUrl)"
  25: BuildDefinition="%(BuildsToSchedule.Identity)"/>
  26: </Target>

Here I have added custom metadata that specifies a build agent for each build definition. 

Once we are done editing the project file we are ready to try our invention.

By now you might be wondering what do we get in the end? First of all we can schedule any number of builds just by running RunAllBuilds. In addition to that we get the freedom to choose the order and the build agents of our build definitions. Moreover, all input data (like build agents, list of builds to be scheduled, etc) is defined in XML and anyone could easily modify it. We can also schedule RunAllBuilds to run every morning - that way we can get immediate feedback about the state of our code and our build infrastructure.


Happy building!

Related Posts


Comments are disabled in preview mode.