In the series the following parts have been published
- Part 1: Introduction
- Part 2: Add arguments and variables
- Part 3: Use more complex arguments
- Part 4: Create your own activity
- Part 5: Increase AssemblyVersion
- Part 6: Use custom type for an argument
- Part 7: How is the custom assembly found
- Part 8: Send information to the build log
- Part 9: Impersonate activities (run under other credentials)
- Part 10: Include Version Number in the Build Number
- Part 11: Speed up opening my build process template
- Part 12: How to debug my custom activities
- Part 13: Get control over the Build Output
- Part 14: Execute a PowerShell script
- Part 15: Fail a build based on the exit code of a console application
- Part 16: Specify the relative reference path
When you queue a new build, the number (or name) of the build will be determined based on the BuildNumberFormat. In the 5th part of this series, the AssemblyFileVersion is increased on every run. To be able to have a relation between an assembly (for which you can retrieve the file version) and the build, I want to include the version number in the build number. This post will show you how you can achieve this.
You can change this format by clicking on the ellipsis (…) button when you edit the build definition. You see then the BuildNumber Format Editor.

When you click on the Macros >> button, you get a list of all available variables.

This list does not contain the version number, nor is it extensible to add a custom token. So we have to do something else.
Approach
The approach I will use is the following:
- Add the text $(Version) to the BuildNumberFormat.
- Read the AssemblyFileVersion from the first file that is found which applies to the AssemblyInfoFileMask (see Customize Team Build 2010 – Part 5: Increase AssemblyVersion), and store this value in a variable called Version
- Replace in the BulidNumberFormat the text $(Version) with the Version variable.
- The default workflow is continued to determine the build number based on the BuildNumberFormat
The solution for this approach consists of two classes. One to retrieve the version number and one to replace the $(Version) token.
Retrieve version number
I have chosen to get the version number directly from Source Control, because the determining of the build number is performed before the sources are retrieved locally. When the AssemblyFileVersion is found, the build part of the version number is increased because in a later stadium of the Build Process Template, the file version is increased in the files.
This results in the following class:
using System;
using System.Activities;
using System.IO;
using System.Text.RegularExpressions;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Build.Workflow.Activities;
using Microsoft.TeamFoundation.VersionControl.Client;
namespace BuildTasks.Activities
{
[BuildActivity(HostEnvironmentOption.Controller)]
public sealed class GetAssemblyVersion : CodeActivity<string>
{
[RequiredArgument]
public InArgument<string> AssemblyInfoFileMask { get; set; }
[RequiredArgument]
public InArgument<IBuildDetail> BuildDetail { get; set; }
protected override string Execute(CodeActivityContext context)
{
// Obtain the runtime value of the input arguments
string assemblyInfoFileMask = context.GetValue(this.AssemblyInfoFileMask);
IBuildDetail buildDetail = context.GetValue(this.BuildDetail);
var workspace = buildDetail.BuildDefinition.Workspace;
var vc = buildDetail.BuildServer.TeamProjectCollection.GetService<VersionControlServer>();
string attribute = "AssemblyFileVersion";
// Define the regular expression to find (which is for example 'AssemblyFileVersion("1.0.0.0")' )
Regex regex = new Regex(attribute + @"\(""\d+\.\d+\.\d+\.\d+""\)");
// For every workspace folder (mapping)
foreach (var folder in workspace.Mappings)
{
// Get all files (recursively) that apply to the file mask
ItemSet itemSet = vc.GetItems(folder.ServerItem + "//" + assemblyInfoFileMask, RecursionType.Full);
foreach (Item item in itemSet.Items)
{
context.TrackBuildMessage(string.Format("Download {0}", item.ServerItem));
// Download the file
string localFile = Path.GetTempFileName();
item.DownloadFile(localFile);
// Read the text from the AssemblyInfo file
string text = File.ReadAllText(localFile);
// Search for the first occurrence of the version attribute
Match match = regex.Match(text);
// When found
if (match.Success)
{
// Retrieve the version number
string versionNumber = match.Value.Substring(attribute.Length + 2, match.Value.Length - attribute.Length - 4);
Version version = new Version(versionNumber);
// Increase the build number -> this will be the new version number for the build
Version newVersion = new Version(version.Major, version.Minor, version.Build + 1, version.Revision);
context.TrackBuildMessage(string.Format("Version found {0}", newVersion));
return newVersion.ToString();
}
}
}
return "No version found";
}
}
}
Replace token in BuildNumberFormat
As stated before I want to reuse the BuildNumberFormat capabilities to be as flexible as possible. So I add a simple activity to replace the $(Version) token.
using System.Activities;
using Microsoft.TeamFoundation.Build.Client;
namespace BuildTasks.Activities
{
[BuildActivity(HostEnvironmentOption.Controller)]
public sealed class UpdateVersionInBuildNumber : CodeActivity<string>
{
[RequiredArgument]
public InArgument<string> BuildNumberFormat { get; set; }
[RequiredArgument]
public InArgument<string> VersionNumber { get; set; }
protected override string Execute(CodeActivityContext context)
{
string buildNumberFormat = context.GetValue<string>(this.BuildNumberFormat);
string versionNumber = context.GetValue<string>(this.VersionNumber);
return buildNumberFormat.Replace("$(Version)", versionNumber);
}
}
}
Change the Build Process Template
Now compile the Activity project to get the new activities in the template (if that does not add the activities, then restart Visual Studio). Now open the CustomTemplate.xaml.
Select the “Update Build Number for Triggered Builds” activity, and select the Variables. Add a new variable called newVersionNumber.

Now add the GetAssembly to the “Update Build Number for Triggered Builds” activity (before the “Update Build Number” activity), and set the arguments to the following values.

Add the UpdateVersionInBuildNumber under the GetAssembly and set the arguments to the following values

The workflow will look like the following

The Build Process Template is now ready. Check in the changes to proceed to the next steps.
Update BuildNumberFormat
Next go to the build definition that makes use of the Build Process Template, and choose Edit Build Definition. Then go to the Process Tab. In the Basic group, you will find the BuildNumberFormat. By default, this is set to $(BuildDefinitionName)_$(Date:yyyyMMdd)$(Rev:.r). I want to change this to include the version number and the date. I can achieve this with the following BuildNumberFormat (but you can use any format you like):
$(BuildDefinitionName) $(Version) ($(Date:yyyyMMdd HHmm))
Now save the build definition and queue a new build. You will now see that the format of the build number is changed to something like

You can download the full solution at BuildProcess.zip. It will include the sources of every part and will continue to evolve.