Implementing Automatic Build Versions using PowerShell and Subversion

Recently I was tasked with implementing an automatic build and versioning system for our .NET development project. As part of that process, I needed to come up with a versioning mechanism to place into the process.

Background

The system had to be automatic so that any member of the team could implement it as part of a regular build cycle. Other projects within the company integrate with this project, so distribution builds are a weekly occurrence.

The build environment has been kept simple: Visual Studio 2008 and Subversion. Integration between the two was accomplished by using the ankhsvn Visual Studio plugin. To enable Subversion maintenance from outside of Visual Studio, the TortoiseSVN Explorer shell extension is used.

Build Versions

Due to the numerous builds that occur, it is necessary to integrate a build number into the application version information. The AssemblyVersion and AssemblyFileVersion attributes allow for a four level versioning system. Having four levels would allow us to simply add the build number to the end of our existing standard three-level version system (Major.Minor.ServicePack).

The only issue was how to come up with a build number that would be meaningful. Visual Studio has an option to replace the third and fourth levels with values that are hacks of the date and time of the build. However, this option resulted in the removal of the Service Pack number from our version number, which was not acceptable.

Back in Visual Basic 6, there was a build option that auto-incremented the last value in the version each time the output was built. That feature was not available in the C/C++ versions of Visual Studio and was not brought forward to the .NET versions. Although an auto-incrementing value allows for determining which build is newer, it provides for not much else.

The Plan

During my research, I came across a blog post where the author was utilizing the latest subversion revision as the build number. This allowed for a build to be directly connected to point in the subversion repository. However, this author’s instructions were utilizing MSBuild, which we were not.

The plan was to implement the following versioning scheme: Major.Minor.ServicePack.Build. The major, minor and service pack values would be specified manually and the build value would be generated automatically as part of the build process.

The Tools

Coming up with the versioning scheme was only half of the work.  The other half, and arguably the hardest half, was to come up with the actual implementation. The challenge was to determmine what tools would be needed to accomplish the task. The tools would have to be lightweight and easy to use.

I decided to utilize Windows PowerShell to accomplish the task. Not only could it be used for automating the build process, but it could also be used to perform automated tests on our .NET classes. PowerShell utilizes the .NET framework classes, so any task that we can accomplish in a .NET application could be accomplished in a PowerShell script.

To allow access to the Subversion repository, a command-line SVN client would be needed. I chose the SlikSVN client available from the Subversion client page. It comes with a Microsoft MSI installation package and was easily distributed to other team members.

The Setup

Before diving into the PowerShell script, some work had to be done to make the job easier and more efficient. Each project has its own AssemblyInfo.cs file. Since our procedure would only update the build number, this would mean that each AssemblyInfo.cs file would have to be updated with the major, minor and service pack numbers. As the number of projects grows, this task becomes more prone to error.

To solve this issue, the AssemblyInfo.cs file would be split into two files. The first file, AssemblyVersionInfo.cs, would contain the company, product, copyright and version information. The second, AssemblyInfo.cs, would contain the remaining attributes. AssemblyVersionInfo.cs would be a single file that would be shared by all projects, whereas AssemblyInfo.cs would be located in each project’s directory.

By placing all version information into a single file, this reduces the modifications needed by the development team when a version changes. It also allows the PowerShell script to simply modify a single file rather than having to search through each project directory.

Getting the Latest Revision Number

The first step in the process is to obtain the value of the latest Subversion revision. Alejandro Espinoza wrote a little PowerShell script that accomplished this task. With a bit of simplification for my use, here’s the result:

function GetLatestRevisionNumber()
{
$revLinePattern = "(.*?)(Revision: )(\d+\.?\d*)(.*?)";
$result = svn info .
$revision = 0;
if([regex]::Match($result, $revLinePattern)) {
$matches = [regex]::Split($result,$revLinePattern);
$revision = $matches[3];
}
return [int]$revision;
}

This PowerShell function calls ‘svn info’ to get the Subversion repository information for the current directory. Then it locates the line with ‘Revision’, pulls out the value and returns it to the caller.

Updating AssemblyVersionInfo.cs

Now that the latest revision value is obtained, we need to take that value and update the AssemblyVersionInfo.cs file. However, there are two issues that need to be addressed when doing so:

  1. If the build number in AssemblyVersionInfo.cs is the same, the file should not be updated.
  2. After the AssemblyVersionInfo.cs is updated, the new copy will be loaded back into the Subversion respository at the next revision level.

To address issue #1, the script will need to compare the updated value with the value that exists in the file. The file will only be updated if the value has changed.

To address issue #2, the revision level obtained above will be incremented before writing it to the file. Otherwise, the file will be updated each time the script is run. By incrementing the value, the value written to the file will match the level when the file is copied back into the respository after the update.

The PowerShell script to perform the update is quite simple:


function UpdateAssemblyVersion()
{
$currRevision = GetLatestRevisionNumber;
$nextRevision = [int]$currRevision + 1;
$versionPattern = '(?\d+)\.(?\d+)\.(?\d+)\.\d+';
$hasChanged = 0;
$assemblyFileData = Get-Content Common\AssemblyVersionInfo.cs;
for($line=0; $line -lt $assemblyFileData.Length; $line++)
{
$newLine = [regex]::Replace($assemblyFileData[$line], $versionPattern,
"`${major}.`${minor}.`${sp}." + $currRevision);
if($newLine.CompareTo($assemblyFileData[$line]) -ne 0)
{
$hasChanged = 1;
$newLine = [regex]::Replace($assemblyFileData[$line], $versionPattern,
"`${major}.`${minor}.`${sp}." + $nextRevision);
$currRevision = $nextRevision;
$assemblyFileData[$line] = $newLine;
}
}
if($hasChanged -eq 1)
{
write-host "Updating AssemblyVersionInfo.cs to version " $nextRevision ".";
Set-Content Common\AssemblyVersionInfo.cs $assemblyFileData;
svn commit Common -m `"Version change for build.`"
}
}

Conclusion

These PowerShell functions are simply two steps in the entire build automation process. When placed between a Subversion update and a Visual Studio compile, they provide an automated method for integrating meaningful build numbers into your .NET assemblies.