Continuous Integration (CI) is probably already part of your developer workflow to build your application and run unit tests after each check in. It may also be used to deploy your application to QA for testing, or run integration tests and code analysis. But where does versioning your software come in? Who is responsible for it? How and when is it done?

This article will demonstrate an automatic method of versioning your application in a manner that will link the code that end users interact with to what your source control maintains, making it trivial to find and isolate bugs with specific versions.

Automatic versioning is also a must if you are publishing your code for public consumption such as to a Nuget Feed (internal or otherwise), to the npm registry or to other package management systems such as Bower.

Project Setup

For this post, you will need an already setup Project on TeamCity that checkouts and build your source code. If you have another CI system, you can adapt this method as required. I have used a Visual Studio solution and examples of using either Git or TFS as your source control, but other project types, programming languages and version control systems (VCS) should work equally well.

TeamCity Setup

1.) Set your build number format

The build number format should be set to %build.counter%.

Build Number Setup

2.) Add a version build step

The version update is performed before you build the code so that the latest version number is embedded in your actual generated assemblies (exe’s and/or dlls). Depending on your build process, you can commit this new version number before the build commences or only after a successful build. In my example, the version number has a 1 to 1 mapping with the TeamCity Build number, so even if the build fails, the version number is still incremented, and the update is committed before the build starts.

Your build step should look something similar to this below:

Using Git as the VCS:

Update Version Number - Git

Using TFS as the VCS:

Update Version Number - TFS

Note the different parameters passed in to the PowerShell script. In the next section we will cover the contents of these scripts.

3.) Add a build trigger to prevent run away builds.

The version update commit will trigger a new build unless you exclude it using a Trigger Rule. Add a VCS trigger and then open the Add Rule Trigger dialog and enter the following data:

Add Trigger Rule

(You can set this trigger rule for a single VCS root or all of them, depending on your needs)

The VCS Trigger Rules dialog would then look like this:

VCS Trigger Rules

This will stop the version number update checkin/commit from re-triggering a new build and starting an endless build cycle! :)

Code Setup

The global version number that the code uses is stored in Version.txt. Initially this should be set to or whatever you originally set the AssemblyInfo version number to be. This value combined with the Build number from TeamCity is used to generate the new version number.

Depending on your source control system, you will need a different PowerShell script to update the AssemblyInfo.cs files.

For Git:

This script opens the Version.txt file and creates a new Version number using the major and minor parts combined with the TeamCity build number. It then does a find a replace in all the AssemblyInfo.cs files.

The updated files are then committed to the local repository before being pushed the remote repository. Obviously, the TeamCity service user must have commit access to the local repository, and push access to the remote repository.

Note that the path to the Git exe as well as the remote repository and branch name can all be customised as parameters. The version filename and the solution filename can be customised as well.

For TFS:

The TFS script first has to checkout for editing all the AssemblyInfo.cs files as well as the Version.txt file, using the TF exe. (This exe should be installed on the build machine if VS 2012/2013 is installed)

The script then opens the Version.txt file and creates a new Version number using the major and minor parts combined with the TeamCity build number. It then does a find a replace in all the AssemblyInfo.cs files.

The updated files are then committed back to the TFS repository. Again, the TeamCity service user must have write access to the TFS repository.

Note that the path to the TF exe as well the version filename and solution filename can all be customised as parameters.

An Alternative File Setup

An alternative to the multiple AssemblyInfo.cs files would be to have all projects share a single global AssemblyVersionInfo.cs file which has the version number only in that file. Something like this:

using System.Reflection;
[assembly: AssemblyVersion("")]

The other assembly attributes would be pulled from each projects’ own AssemblyInfo.cs file and all of the attributes would be merged into the dll on build.

TeamCity AssemblyInfo Patcher

TeamCity offers an out of the box solution for automatic versioning called the AssemblyInfo Patcher. However it only works for “standard” projects, which have been created by the Visual Studio wizard, so that all the AssemblyInfo files and content have a standard location. The updated files are then reverted and your source control system is not updated with the latest version number.

Why commit the Version number changes?

In this example the version number changes are committed back to the source control instead of being discarded so that we can branch specific versioned builds for other environments. We have Dev, QA, Pre-Prod and Production environments and we are moving to have a corresponding branch in the source control system for each environment. In this way on successful deployment of the Dev environment, the QA branch is automatically updated with the latest versioned code. When we manually deploy to the QA environment (after sign off in Dev), the QA branch is checked out from Source Control and used for the build. After a successful QA build and deployment the Pre-Prod branch is then updated with the QA branch code. The same happens for the Production environment. This process allows us to make hotfix’s to the relevant branch and still have TeamCity build and deploy the code in a seamless and transparent manner.

We can also extend this version number update process to change the Revision number per environment adding an extra check of accountability to ensure no “unofficial” builds are deployed or released to any environment.

In Conclusion

To version your source code automatically using your CI server you need to perform the following setup:

  • Add the following files to your Source Control repo:
    • Version.txt
    • UpdateVersionNumbers.ps1
  • Ensure that the TeamCity user running the builds has access to commit to the Source Control Repo.
  • Add a trigger rule similar to the following:
    • For a specific repo:
      • -:root=[your code repo name here];comment=^Updating Assembly version:**
    • For all VSC repos:
      • -:comment=^Updating Assembly version:**
Further reading:

Achieving version parity between .NET assemblies, TeamCity, and NuGet

Team City – Update AssemblyInfo with current build number

Update August 2016:

After experimenting with various ways of handling multiple configs for each environment, it seems one of the best solutions is to generate all the configs on publish and store them with the deployed codebase. (Obviously this only works if you have access to all the settings at build time.)

A example way to do this is using the TransformXml task from MsBuild. The following xml snippet can be added to your .pubxml file:

<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="CreateConfigs" AfterTargets="PipelinePreDeployCopyAllFilesToOneFolder">
	<Message Importance="High" Text="***** Running config transforms *****" />
	<MakeDir Directories="$(ConfigDir)\Dev" />
	<TransformXml Source="Web.config" Transform="Web.Dev.config" Destination="$(ConfigDir)\Dev\Web.config"/>
	<MakeDir Directories="$(ConfigDir)\Test" />
	<TransformXml Source="Web.config" Transform="Web.Test.config" Destination="$(ConfigDir)\Test\Web.config"/>
	<MakeDir Directories="$(ConfigDir)\Live" />
	<TransformXml Source="Web.config" Transform="Web.Live.config" Destination="$(ConfigDir)\Live\Web.config"/>
	<Message Importance="High" Text="***** Completed config transforms *****" />

This post was originally published on Entelect’s internal Tech Blog, Yoda.