The TFS 2010 build system is very powerful but has a steep learning curve through build controllers, build agents, MSBuild, Windows Workflow Foundation, custom activities and more. With that in mind, I thought it would be helpful to explain a simple (but very useful) TFS 2010 build customisation from end to end. If you are going to follow this process along I recommend you use a development machine or temporary VM so you can break things without consequences – it’s tough to learn if you are terrified of making mistakes!
The Goal
The aim here is to:
- Create a sample application
- Create a build definition for the application
- Customise the build process to stamp the application’s output assembly with the full build number, so you only need the assembly to trace right back to source code and work items
The customisation also includes a common versioning approach, which is well documented in the ALM Rangers Build Customisation Guide. The ALM guide is targeted to intermediate to advanced users, whereas this is more of a tutorial for developers with little exposure to build.
I will assume you have already installed the build service and created a build controller and build agent.
Sample Application
This is going to be a console application that simply outputs the assembly properties we are interested in.
- Start Visual Studio and create a new C# Console Application called SampleApplication or similar (VB.NET would be fine, but the sample code below is C#). Tick the box to add it to source control.
- Open the AssemblyInfo.cs file and add some text within the AssemblyDescription that you will recognise – anything will do. Delete the existing comment which has an AssemblyVersion example as it will confuse the versioning workflow that comes later.
- Open the Program.cs file, paste in the code below, then run the application to see the output.
- Check in to source control.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Diagnostics;
namespace AssemblyVersioning
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("AssemblyVersion: {0}",
Assembly.GetExecutingAssembly().GetName().Version.ToString());
Console.WriteLine("AssemblyFileVersion: {0}",
FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion.ToString());
Console.WriteLine("AssemblyDescription: {0}",
FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).Comments.ToString());
Console.ReadLine();
}
}
}
Build Definition
We will set up a build using the default template to make sure it works before we start changing things. Note that there are many other configuration options available, but those below are the minimum required for now.
- In Visual Studio open the Team Explorer panel.
- Expand your Team Project, then right-click on Builds and select New Build Definition…
- Select General on the left. Give the definition a name.
- Select Trigger on the left. Leave this as Manual.
- Select Workspace on the left. Under the Source Control Folder heading modify the location to the folder containing the sample application solution file. The build system will get the latest version of everything under this folder (and it’s also the trigger folder for continuous integration builds).
- Select Build Defaults on the left. Ensure the build controller is selected, then enter a folder in the drop location field. Under live use this would normally be a file share accessible to the team, but for this purpose could be local to the build machine. Note that even if it’s a local folder it needs to be in the \\machinename\fileshare format.
- Select Process on the left. Expand Items to Build and set the Projects to Build to the sample application’s solution file. If the sample application is still open, this will have been populated for you.
- Save the build definition.
- In Team Explorer, right-click the new build definition and select Queue New Build then click the Queue button.
- The Build Explorer will open and you can double-click the running build to see its progress.
- Go to the drop location configured above and verify that the compiled application is present and runs OK.
Custom Build Activity
The template changes depend on a custom build activity, so we will need to get that in place first.
- Create a new source control folder called CustomActivities (or similar). (A logical location would be under the BuildProcessTemplates, or you could make both “templates” and “activities” child folders of a new BuildProcess folder.)
- Download the Community TFS Build Extensions. Open the zip file, copy the contents of the Visual Studio 2010 folder into the new CustomActivities folder, then add the files to source control and check-in.
- In Visual Studio open the Team Explorer panel.
- Right-click on Builds and select Manage Build Controllers…
- Select your build controller then click the Properties button
- Under Version control path to custom assemblies… enter the path to the CustomActivities folder
- Click OK
- You will now be back at the Manage Build Controllers dialog and will see that the controller and agent are both offline. This happens while new build activities are being processed. After a short while the controller then the agent should come back online.
Process Template
Now we will create a customised process template and add a custom versioning activity to it.
- Locate the build template DefaultTemplate.xaml in source control, by default this is under the BuildProcessTemplates folder.
- Make a copy of the file and rename it to CustomTemplate.xaml (or similar). Add to source control but don’t check-in yet.
- Create a new C# class library called BuildTemplates and put it under the BuildProcessTemplates folder. Tick the box to add it to source control.
- Delete the Class1.cs file.
- In Solution Explorer right-click the project and select Add -> Existing Item then select the CustomTemplate.xaml file. Click the small downwards pointing triangle on the Add button and select Add As Link.
- Add references to all the DLLs in the CustomActivities folder.
- Add references to the following assemblies on the .NET tab of the Add Reference dialog: System.Drawing, System.Xaml, Microsoft.TeamFoundation.Build.Client, Microsoft.TeamFoundation.VersionControl.Client, Microsoft.TeamFoundation.WorkItemTracking.Client
- Add references to the following assemblies located in folder C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\PrivateAssemblies\: Microsoft.TeamFoundation.Build.Workflow, Microsoft.TeamFoundation.TestImpact.BuildIntegration
- Add a reference to the following assembly located in folder C:\Windows\assembly\GAC_MSIL\Microsoft.TeamFoundation.TestImpact.Client\10.0.0.0__b03f5f7f11d50a3a\: Microsoft.TeamFoundation.TestImpact.Client
- Double-click CustomTemplate.xaml to open the workflow editor.
- Open the Toolbox panel. You should see many workflow activities organised into different tabs.
- Right-click within the Toolbox and select Add Tab… then name the tab Community TFS Build Extensions
- Right-click the new tab then select Choose Items…
- Click the Browse button and navigate to the BuildActivities folder.
- Select TfsBuildExtensions.Activities.dll then click the Open button.
- Click the OK button
- Right-click the toolbox tab again and select Sort Items Alphabetically (recommended)
- You should now be back at CustomTemplate.xaml in the editor. Click the Arguments tab at the bottom.
- Add three new arguments VersioningFilespec, DisableVersioning and VersioningStartDate with types and defaults as shown under the Arguments heading below.
- Still in the list of arguments locate the Metadata entry and click the ellipsis button (three little dots) in the Default value column. Add three entries, one for each new argument, as shown under the Metadata heading below. In addition, set the View this parameter when: drop-down for all three to Always show the parameter.
- Now you need to edit the workflow.
- Locate the activity Run On Agent then expand it
- Within Run On Agent locate Initialize Workspace
- From the Control Flow tab of the toolbox drag an If activity right after Initialize Workspace (before If CreateLabel)
- Open the properties for the If activity and set the DisplayName to If Versioning and the Condition to DisableVersioning = False
- Expand the If Versioning activity so you can see the Then and Else boxes
- From the Control Flow tab of the toolbox drag a Sequence activity into the Then box then set its DisplayName to Versioning
- This should have given you some understanding of how to modify the workflow, but it would be tortuous to continue this way so we will cut-and-paste to finish this work
- Save and close CustomTemplate.xaml then right-click the file in Solution Explorer and select View Code.
- Locate the root Activity node and add a new namespace attribute as follows: xmlns:tat=”clr-namespace:TfsBuildExtensions.Activities.TeamFoundationServer;assembly=TfsBuildExtensions.Activities”
- Find the text If Versioning then replace the whole If element with the XML shown under the heading Workflow XML below.
- New close the file and re-open in the Workflow editor again. The versioning workflow should look like the example under the Workflow heading below.
- Check the template and project into source control.
Note: The forthcoming Windows Workflow Foundation 4.5 has a search function which will make finding your way around the workflow much easier
Arguments
.
Metadata
| Parameter Name | Display Name | Category | Description |
| VersioningFilespec | Files to Version | Versioning | Enter search patterns for files where version info should be updated |
| DisableVersioning | Disable Versioning | Versioning | Specify True to skip versioning assemblies; False to version as defined by the Versioning settings |
| VersioningStartDate | Start Date | Versioning | Start date for calculating build element of version number |
.
Workflow XML
<If Condition="[DisableVersioning = False]" DisplayName="If Versioning" sap:VirtualizedContainerService.HintSize="552,995" mtbwt:BuildTrackingParticipant.Importance="Low">
<sap:WorkflowViewStateService.ViewState>
<scg:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsPinned">True</x:Boolean>
<x:Boolean x:Key="IsExpanded">True</x:Boolean>
</scg:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<If.Then>
<Sequence DisplayName="Versioning" sap:VirtualizedContainerService.HintSize="427,889">
<Sequence.Variables>
<Variable x:TypeArguments="scg:IEnumerable(x:String)" Name="VersioningFiles" />
<Variable x:TypeArguments="x:String" Name="major" />
<Variable x:TypeArguments="x:String" Name="minor" />
<Variable x:TypeArguments="x:String" Name="version" />
<Variable x:TypeArguments="x:String" Name="filecontents" />
</Sequence.Variables>
<sap:WorkflowViewStateService.ViewState>
<scg:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsExpanded">True</x:Boolean>
<x:Boolean x:Key="IsPinned">True</x:Boolean>
</scg:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<mtbwa:FindMatchingFiles DisplayName="Find Versioning Files" sap:VirtualizedContainerService.HintSize="405,22" mtbwt:BuildTrackingParticipant.Importance="Low" MatchPattern="[SourcesDirectory & String.Join(";" & SourcesDirectory, VersioningFilespec)]" Result="[VersioningFiles]" />
<ForEach x:TypeArguments="x:String" DisplayName="For Each File" sap:VirtualizedContainerService.HintSize="405,703" mtbwt:BuildTrackingParticipant.Importance="Low" Values="[VersioningFiles]">
<sap:WorkflowViewStateService.ViewState>
<scg:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsPinned">True</x:Boolean>
<x:Boolean x:Key="IsExpanded">True</x:Boolean>
</scg:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<ActivityAction x:TypeArguments="x:String">
<ActivityAction.Argument>
<DelegateInArgument x:TypeArguments="x:String" Name="file" />
</ActivityAction.Argument>
<Sequence DisplayName="Version File" sap:VirtualizedContainerService.HintSize="375,597" mtbwt:BuildTrackingParticipant.Importance="Low">
<sap:WorkflowViewStateService.ViewState>
<scg:Dictionary x:TypeArguments="x:String, x:Object">
<x:Boolean x:Key="IsExpanded">True</x:Boolean>
<x:Boolean x:Key="IsPinned">True</x:Boolean>
</scg:Dictionary>
</sap:WorkflowViewStateService.ViewState>
<mtbwa:WriteBuildMessage sap:VirtualizedContainerService.HintSize="353,22" Importance="[Microsoft.TeamFoundation.Build.Client.BuildMessageImportance.High]" Message="[String.Format("Versioning file {0}", file)]" mva:VisualBasic.Settings="Assembly references and imported namespaces serialized as XML namespaces" />
<Assign DisplayName="Read file contents" sap:VirtualizedContainerService.HintSize="353,58" mtbwt:BuildTrackingParticipant.Importance="Low">
<Assign.To>
<OutArgument x:TypeArguments="x:String">[filecontents]</OutArgument>
</Assign.To>
<Assign.Value>
<InArgument x:TypeArguments="x:String">[System.IO.File.ReadAllText(file)]</InArgument>
</Assign.Value>
</Assign>
<Assign DisplayName="Get major version" sap:VirtualizedContainerService.HintSize="353,58" mtbwt:BuildTrackingParticipant.Importance="Low">
<Assign.To>
<OutArgument x:TypeArguments="x:String">[major]</OutArgument>
</Assign.To>
<Assign.Value>
<InArgument x:TypeArguments="x:String">[New System.Text.RegularExpressions.Regex(".*?assembly: AssemblyVersion\(""(?<major>[\d]*)\.(?<minor>[\d]*)").Match(filecontents).Groups("major").Value]</InArgument>
</Assign.Value>
</Assign>
<Assign DisplayName="Get minor version" sap:VirtualizedContainerService.HintSize="353,58" mtbwt:BuildTrackingParticipant.Importance="Low">
<Assign.To>
<OutArgument x:TypeArguments="x:String">[minor]</OutArgument>
</Assign.To>
<Assign.Value>
<InArgument x:TypeArguments="x:String">[New System.Text.RegularExpressions.Regex(".*?assembly: AssemblyVersion\(""(?<major>[\d]*)\.(?<minor>[\d]*)").Match(filecontents).Groups("minor").Value]</InArgument>
</Assign.Value>
</Assign>
<tat:TfsVersion AssemblyVersion="{x:Null}" Build="{x:Null}" DateFormat="{x:Null}" FailBuildOnError="{x:Null}" IgnoreExceptions="{x:Null}" Revision="{x:Null}" TextEncoding="{x:Null}" TreatWarningsAsErrors="{x:Null}" Action="GetAndSetVersion" AssemblyDescription="[BuildDetail.BuildNumber]" CombineBuildAndRevision="False" Delimiter="." DisplayName="Set Version" Files="[{file}]" ForceSetVersion="False" sap:VirtualizedContainerService.HintSize="242,22" mtbwt:BuildTrackingParticipant.Importance="Low" LogExceptionStack="True" Major="[major]" Minor="[minor]" PaddingCount="0" PaddingDigit="" SetAssemblyDescription="True" SetAssemblyFileVersion="True" SetAssemblyVersion="False" StartDate="[VersioningStartDate]" UseUtcDate="False" Version="[version]" VersionFormat="Elapsed" VersionTemplateFormat="0.0.0.0" />
<mtbwa:WriteBuildMessage sap:VirtualizedContainerService.HintSize="353,22" Importance="[Microsoft.TeamFoundation.Build.Client.BuildMessageImportance.High]" Message="[String.Format("Version has been set to {0}", version)]" mva:VisualBasic.Settings="Assembly references and imported namespaces serialized as XML namespaces" />
</Sequence>
</ActivityAction>
</ForEach>
</Sequence>
</If.Then>
</If>
Update Build Definition
Now we need to modify the build definition created earlier to use the customised template.
- Right-click the build definition created earlier and select Edit Build Definition…
- Select Process on the left. On the right under Build process template click Show details.
- Click the New button then choose the option to Select an existing XAML file
- Choose CustomTemplate.xaml then click the OK button
- You will notice the new Versioning parameters but don’t need to edit them – the defaults will work fine.
- Save the build definition.
- Right-click the build definition and select Queue New Build the click the Queue button.
- Once the build has finished:
- In the build output click View Log and find the message saying that the version has been set
- Go to the drop location and check that the file version matches the one generated by the build.
- Also check that the file comments contains the full build number. Prior to Windows 7 you could simply bring up the file properties to see this. In Windows 7 it is frustratingly not shown in Windows Explorer but can be displayed using Powershell – change the directory to the drop folder containing the executable then run (dir “SampleApplication.exe”).VersionInfo.Comments
How it Works
After the build agent retrieves the latest files from source control, the process locates files matching the pattern specified and uses community build extension TfsVersion to do the rest – remove the read-only flag on the file, set the version and description, then put the read-only flag back on to avoid upsetting future gets from source control. Compilation then proceeds as normal, oblivious to the fact that something has been changed behind the scenes.


[...] Step by Step: Automatically Stamp Assemblies with the Build Number [...]
Thanks for sharing nice article with us, I appreciate your efforts. I did all the steps that mentioned by you and it was completed successfully, but i am getting error while compiling project. it will giving me following error:
Error 1 Assembly ‘System.Xaml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′ can not be resolved. Please add a reference to this assembly in the project that is being built. E:\KaushikTemp\AssemblyVersioning\TestBuild\BuildProcessTemplates\CustomTemplate.xaml BuildTemplates
Do you have any idea for the same?
seeking for your co-operation
Thanks and Regards,
Imdadhusen
Sorry, I missed some references that you need to add to the project – I’ll update the post now. Here they are:
.NET assemblies System.Drawing, System.Xaml, Microsoft.TeamFoundation.Build.Client, Microsoft.TeamFoundation.VersionControl.Client, Microsoft.TeamFoundation.WorkItemTracking.Client
Assemblies in folder C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\PrivateAssemblies\:
Microsoft.TeamFoundation.Build.Workflow, Microsoft.TeamFoundation.TestImpact.BuildIntegration
Assemblies in folder C:\Windows\assembly\GAC_MSIL\Microsoft.TeamFoundation.TestImpact.Client\10.0.0.0__b03f5f7f11d50a3a\:
Microsoft.TeamFoundation.TestImpact.Client
Thanks for working through it and letting me know about the problem!