You can code it, I can help!

MSBuild Scripting Part II, Versioning and Deployment

MsBuild Introduction

MsBuild is a language to create build scripts using predefined tasks. Please check my previous blog for a full description


Before talking about deployment we have to consider how to version our assemblies before copying them to any environment. Having the correct version number is very important once you start a development iteration. The QA team needs to know which version to test and report, the user needs to know which version they are seeing, you need to know which version is causing problems, etc... .NET uses the Properties/AssemblyInfo.cs file to store the version info. Inside each file you will find the following code:
// Version information for an assembly consists of the following four values:
//      Major Version
//      Minor Version
//      Build Number
//      Revision
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("")]
Here is a brief description of the recommended usage of each number:
  • Major: This number indicates a big difference between versions. When it changes the user will expect "Major" changes in functionality.
  • Minor: This number indicates fixes, bugs, etc. Still same functionality but with some modifications.
  • Build: This number indicates the amount of times you built the app so far. (not always used)
  • Revision: This number indicates the revision number from your source control. This number is very important because it would give you the information needed in order to check the revision you need and be able to reproduce bugs, errors, etc.
So far, so good. We just need to change all the files in each project and put the same version... and when it changes... again... easy, right?

MsBuild to the rescue

Not to fret my friend, luckily we have msbuild on our side. First of all we will define the version we want in or msbuild file, using a property group.
	<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>

As you can see the revision number is not included because we would like to use the source control tool to get the revision number every time we run the script to make sure we always have the right version. Tigris has a set of tasks that will help us to achieve our goal. You can download it for free and install it. Now that we have the version we want (Major.Minor.Build) we need to get the revision number from the repository and store it into a property. Because I use SVN I'll use the SvnVersion task from Tigris to get the version number and store it into the property Revision. If you also use SVN you will need a command line tool in order to run svnversion. Collabnet has a free SVN client you can download and use. After finding the version number we need to find all the files that we would like to change. For that purpose we will define another ItemGroup including all the AssemblyInfo.cs files in our hierarchy and store them into the variable AssemblyFiles. The final step is to replace each declaration with the version in the AssemblyInfo.cs file with the version we defined in our build file using the FileUpdate task for each cs file.
<Target Name="UpdateAssemblyVersion">
		<SvnVersion LocalPath="$(MSBuildProjectDirectory)">
	      <Output TaskParameter="Revision" PropertyName="Revision" />

			<AssemblyFiles Include="**\AssemblyInfo.cs"/>
 	    <Message Text="Version: $(Major).$(Minor).$(Build).$(Revision)"/>

		<FileUpdate Files="@(AssemblyFiles)"
                ReplacementText="$(Major).$(Minor).$(Build).$(Revision)" />
The FileUpdate task uses a regular expression to find the version number. In this case we are looking for four digits separated by dots and as replacement we use the variables we defined previously.


Alright. We have all our assemblies (executable, etc) with the right version and we are ready to roll. Now, we need to build again to include the right version in release mode and then copy the files to the destination of our preference. A common practice is to copy the files to a folder where all the team knows the latest version will reside. Using a continuous integration tool like Cruise Control .NET or TeamCity makes easier to have always the latest version available as soon a new change is committed to the repository. I'll call this task DeployBin and will do the following:
  • Delete the destination folder
  • Call the UpdateAssemblyVersion task to update to the latest version of all the Assembly.cs files.
  • Build again (Release mode) to have the latest binaries with the version updated.
  • Create the destination folder.
  • Define a variable called SourceFiles using an ItemGroup to include all the assemblies I would like to copy and excluding the ones I don't need (testing assemblies).
  • Copy all the files listed in the SourceFiles variable to the chosen destination.
Ready? Here is the magic:
	<Target Name="DeployBin">
		<RemoveDir Directories="$(BuildFolder);$(ExportFolder)" ContinueOnError="true" />
		<CallTarget Targets="UpdateAssemblyVersion"/>
		<CallTarget Targets="Build"/>
		<MakeDir Directories ="$(BuildFolder)"/>
			<SourceFiles Include="**\bin\Release\*.dll" Exclude="**\*.Tests\bin\release\*.dll"/>
		<Copy SourceFiles="@(SourceFiles)" 
			<Output TaskParameter="CopiedFiles" PropertyName="Copied"/>
Voila! That's it! You can make the deployment as complicated as you want. You can separate the folders, rename the previous version, etc, etc. Here is the complete file in case you would like to look at it. I hope you will find it useful. In my next blog about MSBuild we will talk about creating custom tasks. Any feedback is most welcome. Enjoy.