How to auto-increment assembly version using a custom MSBuild task
The assembly version string has the format “major.minor.build.revision”, such as 2.0.50727.42. Here is a sample on how to create a custom MSBuild task for Web Deployment Projects to auto-increment the build and revision portion of your version strings with each build.
Web Deployment Projects provide a way to pre-compile a Visual Studio 2005 Web Site project into a single assembly. It also allows you to specify the AssemblyFileVersion and AssemblyVersion for this assembly.
These values can be set in the Project Properties dialog of the Web Deployment Project. Once set the following will be added to the Web Deployment Project file.
<ItemGroup>
<AssemblyAttributes Include="AssemblyFileVersion">
<Value>2.0.0.0</Value>
</AssemblyAttributes>
<AssemblyAttributes Include="AssemblyVersion">
<Value>2.0.0.0</Value>
<AutoIncrement>true</AutoIncrement>
</AssemblyAttributes>
</ItemGroup>
The Microsoft.WebDeployment.targets file includes a target named GenerateAssemblyInfo that will use the @(AssemblyAttributes) item collection defined above to dynamically create an AssemblyInof.cs file. This file is compiled into an assembly and passed to aspnet_merge.exe using its –copyattrs argument.
This is great if you want to fix the version to a specific value, but what if you want to generate a new build number with every build. After all the format of version string is “major.minor.build.revision”.
To do this we’ll need to dynamically generate the AssemblyAttributes item collection at build time instead of statically declaring their values in the Web Deployment Project file.
First create a custom MSBuild task. MSBuild tasks are classes derived from Microsoft.Build.Utilities.Task that override the Execute() method. You can learn more about writing MSBuild tasks from How To: Write a Task
Here is a sample MSBuild task that will increment build and revision numbers.
Contents of file IncrementBuildNumber.cs:
using System;
using System.IO;
using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;
namespace MyTasks
{
public class IncrementBuildNumber : Task
{
string m_fileName; // Text file containing previous build number
long m_buildNumber; // Build number based on current date. 12/2/2005 would be 51202
long m_revisionNumber; // Revision number, increments within a day with each new day starting at 0
/// <summary>
/// MSBuild entry point into this task.
/// </summary>
public override bool Execute()
{
bool bSuccess = true;
try
{
IncrementNumbers();
Log.LogMessage(MessageImportance.Normal, "Build {0}.{1}", m_buildNumber, m_revisionNumber);
}
catch (Exception ex)
{
// Log Failure
Log.LogErrorFromException(ex);
Log.LogMessage(MessageImportance.High, "Failed to increment Build Number!");
bSuccess = false;
}
return bSuccess;
}
/// <summary>
/// Task argument set MSBuild project file
/// </summary>
[Required]
public string File
{
get { return m_fileName; }
set { m_fileName = value; }
}
/// <summary>
/// Task output available to MSBuild
/// </summary>
[Output]
public long BuildNumber
{
get { return m_buildNumber; }
set { m_buildNumber = value; }
}
/// <summary>
/// Task output available to MSBuild
/// </summary>
[Output]
public long RevisionNumber
{
get { return m_revisionNumber; }
set { m_revisionNumber = value; }
}
/// <summary>
/// Increments Build Number and Build Revision
/// based on the numbers saved in a text file.
/// </summary>
private void IncrementNumbers()
{
// Set build number to current date, 12/02/2005 == 51202
DateTime dDate = DateTime.Now;
m_buildNumber = dDate.Year % 2000 * 10000;
m_buildNumber += dDate.Month * 100;
m_buildNumber += dDate.Day;
// Defualt build revision to 0
m_revisionNumber = 0;
// Check for a previous build and revision number
if (System.IO.File.Exists(m_fileName))
{
StreamReader sr = new StreamReader(m_fileName);
string previousBuild = sr.ReadLine();
sr.Close();
string[] previousNumbers = previousBuild.Split('.');
if (m_buildNumber == long.Parse(previousNumbers[0]))
m_revisionNumber = long.Parse(previousNumbers[1]) + 1;
}
// Write the current build numbers to the file
StreamWriter sw = new StreamWriter(m_fileName);
sw.WriteLine(string.Format("{0}.{1}", m_buildNumber, m_revisionNumber));
sw.Flush();
sw.Close();
}
}
}
Compile file IncrementBuildNumber.cs into the assembly MyTasks.dll
csc /t:library /r:Microsoft.Build.Utilities.dll;Microsoft.Build.Framework.dll /out:MyTasks.dll IncrementBuildNumber.cs
Copy the MyTasks.dll into the same directory as the Web Deployment Project.
Now that we’ve got our custom MSBuild task we need to modify the Web Deployment Project file to use our new custom task.
You can edit the project file by right clicking on the Web Deployment Project in the solution explorer and selecting “Open Project File” from the shortcut menu.
Once opened add a <UsingTask> between the <Project> and the first <PropertyGroup> to register the new custom IncrementBuildNumber task with MSBuild.
<!--
Microsoft Visual Studio 2005 Web Deployment Project
http://go.microsoft.com/fwlink/?LinkId=55111
-->
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask TaskName="MyTasks.IncrementBuildNumber" AssemblyFile="MyTasks.dll" />
<PropertyGroup>
…
</PropertyGroup>
Next override the BeforeBuild target with the code below.
<Target Name="BeforeBuild">
<IncrementBuildNumber
File = "$(MSBuildProjectDirectory)\BuildNumbers.txt">
<Output TaskParameter="BuildNumber" PropertyName="BuildNumber"/>
<Output TaskParameter="RevisionNumber" PropertyName="RevisionNumber"/>
</IncrementBuildNumber>
<CreateItem Include="AssemblyFileVersion" AdditionalMetadata="Value=2.0.$(BuildNumber).$(RevisionNumber)">
<Output ItemName="AssemblyAttributes" TaskParameter="Include" />
</CreateItem>
<CreateItem Include="AssemblyVersion" AdditionalMetadata="Value=2.0.$(BuildNumber).$(RevisionNumber)">
<Output ItemName="AssemblyAttributes" TaskParameter="Include" />
</CreateItem>
</Target>
In this sample we’re using a BuildNumbers.txt file to store the build numbers from each build. It is passed to the IncrementBuildNumber task through the File property. The $(MSBuildProjectDirectory) property is a built in property provided by MSBuild that specifies the directory containing the project file.
The BuildNumber and BuildRevision values are then copied from our custom MSBuild task IncrementBuildNumbers as MSBuild properties using the <Output> tags.
<Output TaskParameter="BuildNumber" PropertyName="BuildNumber"/>
<Output TaskParameter="RevisionNumber" PropertyName="RevisionNumber"/>
At this point there are now 2 new MSBuild properties $(BuildNumber) and $(RevisionNumber). The next step is to dynamically create the AssemblyAttributes version strings using these properties.
<CreateItem Include="AssemblyFileVersion" AdditionalMetadata="Value=2.0.$(BuildNumber).$(RevisionNumber)">
<Output ItemName="AssemblyAttributes" TaskParameter="Include" />
</CreateItem>
<CreateItem Include="AssemblyVersion" AdditionalMetadata="Value=2.0.$(BuildNumber).$(RevisionNumber)">
<Output ItemName="AssemblyAttributes" TaskParameter="Include" />
</CreateItem>
Once created they will be automatically picked up the GenerateAssemblyInfo target provided in the Web Deployment Project.
Be sure to delete the statically declared AssemblyAttributes Items for AssemblyFileVersion and AssemblyVersion.
Delete any of these entries from your project file.
<AssemblyAttributes Include="AssemblyFileVersion">
<Value>2.0.0.0</Value>
</AssemblyAttributes>
<AssemblyAttributes Include="AssemblyVersion">
<Value>2.0.0.0</Value>
<AutoIncrement>true</AutoIncrement>
</AssemblyAttributes>
You will get a build error if forget to delete these statically declared values.
Now let’s build and see what happens.
Microsoft (R) Build Engine Version 2.0.50727.42
[Microsoft .NET Framework, Version 2.0.50727.42]
Copyright (C) Microsoft Corporation 2005. All rights reserved.
Build started 12/2/2005 8:46:42 AM.
__________________________________________________
Project "C:\MyProjects\MyWeb\MyWeb_deploy\MyWeb_deploy.wdproj" (default targets):
Target BeforeBuild:
Build 51202.0
Target AspNetCompiler:
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_compiler.exe -v /MyWeb -p C:\MyProjects\MyWeb\MyWeb -u -f -d C:\MyProjects\MyWeb\MyWeb_deploy\Debug\
Updateing web.config compilation debug = 'True' ...
Successfully updated web.config compilation debug = 'True' ...
Target GenerateAssemblyInfo:
Generating AssemblyInfo ...
Setting [assembly: AssemblyFileVersion("2.0.51202.0")]
Setting [assembly: AssemblyVersion("2.0.51202.0")]
Successfully Generated AssebmlyInfo ...
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Csc.exe /out:C:\MyProjects\MyWeb\MyWeb_deploy\AssemblyInfo\Debug\AssemblyInfo.dll /target:library C:\MyProjects\MyWeb\MyWeb_deploy\AssemblyInfo\Debug\AssemblyInfo.cs
Target AspNetMerge:
Running aspnet_merge.exe ...
C:\Program Files\MSBuild\Microsoft\WebDeployment\v8.0\aspnet_merge.exe C:\MyProjects\MyWeb\MyWeb_deploy\Debug -o MyWeb -copyattrs C:\MyProjects\MyWeb\MyWeb_deploy\AssemblyInfo\Debug\AssemblyInfo.dll -debug
Successfully merged 'C:\MyProjects\MyWeb\MyWeb_deploy\Debug'.
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:10.29
Since this is the first build today it’s getting a build number based on the current date 51202 and the revision is 0. What happens if we build again?
Microsoft (R) Build Engine Version 2.0.50727.42
[Microsoft .NET Framework, Version 2.0.50727.42]
Copyright (C) Microsoft Corporation 2005. All rights reserved.
Build started 12/2/2005 8:46:56 AM.
__________________________________________________
Project "C:\MyProjects\MyWeb\MyWeb_deploy\MyWeb_deploy.wdproj" (default targets):
Target BeforeBuild:
Build 51202.1
Target AspNetCompiler:
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_compiler.exe -v /MyWeb -p C:\MyProjects\MyWeb\MyWeb -u -f -d C:\MyProjects\MyWeb\MyWeb_deploy\Debug\
Updateing web.config compilation debug = 'True' ...
Successfully updated web.config compilation debug = 'True' ...
Target GenerateAssemblyInfo:
Generating AssemblyInfo ...
Setting [assembly: AssemblyFileVersion("2.0.51202.1")]
Setting [assembly: AssemblyVersion("2.0.51202.1")]
Successfully Generated AssebmlyInfo ...
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Csc.exe /out:C:\MyProjects\MyWeb\MyWeb_deploy\AssemblyInfo\Debug\AssemblyInfo.dll /target:library C:\MyProjects\MyWeb\MyWeb_deploy\AssemblyInfo\Debug\AssemblyInfo.cs
Target AspNetMerge:
Running aspnet_merge.exe ...
C:\Program Files\MSBuild\Microsoft\WebDeployment\v8.0\aspnet_merge.exe C:\MyProjects\MyWeb\MyWeb_deploy\Debug -o MyWeb -copyattrs C:\MyProjects\MyWeb\MyWeb_deploy\AssemblyInfo\Debug\AssemblyInfo.dll -debug
Successfully merged 'C:\MyProjects\MyWeb\MyWeb_deploy\Debug'.
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:13.26
The build number stays as 51202 but the revision number has incremented to 1. The revision number will continue to increment for each build during the day then starting tomorrow the build number will change to 51203 and the revision will reset to 0.
This is just one example of how to increment build numbers during a build. In this case the build numbers are coming from a text file and being auto incremented by a custom MSBuild task.
There are other techniques that you could use. For example MSBuild also exposes all environment variables from the command shell. So if you primarily build from a command line or batch build environment you could simply set an environment variable for BuildNumber and RevisionNumber.
c:\set BuildNumber=51202
c:\set RevisionNumber=2
Using this technique you can go back to statically defining your AssemblyAttributes and simply reference the environment variables.
<ItemGroup>
<AssemblyAttributes Include="AssemblyFileVersion">
<Value>2.0.$(BuildNumber).$(RevisionNumber)</Value>
</AssemblyAttributes>
<AssemblyAttributes Include="AssemblyVersion">
<Value>2.0.$(BuildNumber).$(RevisionNumber)</Value>
</AssemblyAttributes>
</ItemGroup>
While simpler to implement it is not auto-incrementing the revision with each build, instead it’s relying on an external build process to set these values. MSBuild is an extremely powerful build tool exposing many capabilities. The techniques you use will vary depending on your needs.
Hope this helps,
Brad