Before we get started, Ewald Hofman has an excellent blog post on how to write your own custom activities to do the same thing, which is actually quite a lot more graceful than using a batch file. If you are confused about which approach to take, use Ewald’s way of doing this. I suppose you could call this article “Automatically Updating Your AssemblyInfo The Wrong Way“.
If there is some constraint or reason that means you really must use a batch file, I’m not here to preach at you or tell you that those constraints are not valid. You know your domain better than I do!
So here is how you would integrate a batch file into your TFS Build process to update your version automatically.
This example is based on a few environmental assumptions.
I am using a shared file, rather than looping through every AssemblyInfo.cs. Each project still has its own AssemblyInfo.cs with any specific information that is needed, which means AssemblyInfoShared.cs just has the common properties. The shared file lives next to the solution file and is referenced as a linked file in each project.
I am using Visual Studio 2012 and TFS 2012, but things should be similar in Visual Studio 2010, which also has the XAML based build definition workflows. This definitely won’t apply to Visual Studio 2005 or 2008 as they just used MSBuild.
Lastly, although my examples do some specific things, please generalise them and adapt them to your specific scenario. Use your own agreen build number formats and decide what properties belong in the shared files.
Build Number Format
The default build number format is:
This results in a build number of “BUILD_NIGHTLY-20121201.0”. I am using my own custom build number format:
This results in “ProjectName-18.104.22.168”. When we come to use this build number, we won’t want “ProjectName-“, so we’ll just take the rest. You can invent whatever numbering scheme you like, but in this specific case the first three numbers a hard-coded. That is because sales and marketing wanted control over the major and minor version and the third number was used for patches. For example:
- 1.1.*.* is a marketing decision
- 2.0.*.* is a big marketing decision
- *.*.1.* is a patch
- *.*.*.243 is an automatic number that increases with each build
The batch file replaces the entire contents of the shared Assembly Info file every time the build runs. It also deals with source control. Whether you need to version this shared file will depend on your individual and unique situation. Generally speaking it is better if you don’t have to version it, but in my specific case, there was a need for developers to pull the latest and create DLLs that were the correct version to drop onto their test environments, so it was easier if they didn’t have to check and edit this file manually.
The file accepts two parameters. The version number and the file path. This allows TFS to pass this information when it executes the batch file. I also parameterised the path to the TF source control command line (which you only need if you want to check-out and check-in the file) and the current year, for the copyright message. The batch file can also be executed manually, so it prompts for the arguments if they haven’t been supplied.
set version=%~1 set outfile=%~2 set tfstool="C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\tf" set year=%DATE:~6,4% echo off if "%outfile%"=="" set /p "outfile=[Full Path] " if "%version%"=="" set /p "version=[0.0.0.0] " %tfstool% checkout %outfile% attrib -r %outfile% echo using System.Reflection; > %outfile% echo using System.Runtime.InteropServices; >> %outfile% echo //This file is auto generated by AssemblyVersion.bat >> %outfile% echo [assembly: AssemblyConfiguration("")] >> %outfile% echo [assembly: AssemblyCompany("Your Company")] >> %outfile% echo [assembly: AssemblyProduct("Your Cool Product")] >> %outfile% echo [assembly: AssemblyCopyright("Copyright Â© 2007-%year% Your Company. All rights reserved.")] >> %outfile% echo [assembly: AssemblyTrademark("")] >> %outfile% echo [assembly: AssemblyCulture("")] >> %outfile% echo [assembly: ComVisible(false)] >> %outfile% echo [assembly: AssemblyVersion("%version%")] >> %outfile% echo [assembly: AssemblyFileVersion("%version%")] >> %outfile% %tfstool% checkin %outfile% /comment:"***NO_CI*** Assembly Info Increment" /noprompt
You can remove the three lines containing the text “%tfstool%” if you don’t want to version the file. Here is a breakdown of what the file does.
Check Out File
%tfstool% checkout %outfile%
Make File Writable
If you aren’t checking out the file, you’ll need to make it writable.
attrib -r %outfile%
Echo File Contents
The batch file uses > to write over a file and >> to append to a file.
echo Some line >> %outfile%
Check In File
On check-in, we specify the exact file to check in, add a comment that prevents CI builds from being triggered and ask for no prompts.
%tfstool% checkin %outfile% /comment:"***NO_CI*** Assembly Info Increment" /noprompt
To make the magic happen, we need to call the batch file from the build definition. TFS has an InvokeProcess task that is ideal for this. You’ll need to open up the build process definition XAML file to do this. I added the InvokeProcess activity in the “Run On Agent” step that you’ll find in the default template, just after “Initialize Workspace” activity.
You’ll need to set a couple of properties on the InvokeProcess task.
The arguments property contains the string that you would pass in the command line to call the batch file. We create the string using some TFS built-in properties that tell us where the build directory is and to remove the project name from the version number.
LabelName.Replace("ProjectName-", "") + " " + SourcesDirectory + "\Main\Source\AssemblyInfoShared.cs"
Again we use the TFS properties to get the build directory so we can pass in the full path to the shared Assembly Info file.
SourcesDirectory + "\Path\AssemblyVersion.bat"
You can also drag a WriteBuildMessage and WriteBuildWarning activity onto the InvokeProcess activity to push happy and sad messages into the build log.
And that’s it. A SharedAssemblyInfo.cs file, a batch file and an InvokeProcess activity are all you need to auto-increment your build numbers.
In later versions of TFS, you’ll need to get the SourcesDirectory and LabelName in a slightly different way…
- Create variables for buildNumber and sourcesDirectory (add these using the “Variables” link at the bottom of your IDE)
- Add a couple of GetEnvironmentVariable activities to ship the values into those variables
- Use those variables in place of LabelName and SourcesDirectory
The GetEnvironmentVariables activity has the following properties:
- Name (the name of the variable to capture), use WellKnownEnvironmentVariables.BuildNumber and WellKnownEnvironmentVariables.SourcesDirectory
- Result (the name of the variable to store the value in), use buildNumber and sourcesDirectory
In my example I change both AssemblyVersion and AssemblyFileVersion. If you change the AssemblyVersion, you may make your assemblied incompatible with code that is using a fixed version. You can opt to just change the AssemblyFileVersion automatically and move the AssemblyVersion into a concious decision (i.e. manually update the batch file when you want to change it, or leave it out of the shared file so it can be set in each project).