Matching Binary Version to Build Number Version in TFS 2013 Builds

teambuild
89478

Jim Lamb wrote a post about how to use a custom activity to match the compiled versions of your assemblies to the TFS build number. This was not a trivial exercise (since you have to edit the workflow itself) but is the best solution for this sort of operation. Interestingly the post was written in November 2009 and updated for TFS 2010 RTM in February 2010.

I finally got a chance to play with a VM that’s got TFS 2013 Preview installed. I was looking at the changes to the build engine. The Product Team have simplified the default template (they’ve collapsed a lot of granular activities into 5 or 6 larger activities). In fact, if you use the default build template, you won’t even see it (it’s not in the BuildProcessTemplates folder – you have to download it if you want to customize it).

The good news is that the team have added pre- and post-build and pre- and post-test script hooks into the default workflow. I instantly realised this could be used to solve the assembly-version-matches-build-number problem in a much easier manner.

Using the Pre-Build Script

The solution is to use a PowerShell script that can replace the version in the AssemblyInfo files before compiling with the version number in the build. Here’s the procedure:

  1. Import the UpdateVersion.ps1 script into source control (the script is below)
  2. Change the build number format of your builds to produce something that contains a version number
  3. Point the pre-build script argument to the source control path of the script in step 1

The script itself is pretty simple – find all the matching files (AssemblyInfo.* by default) in a target folder (the source folder by default). Then extract the version number from the build number using a regex pattern, and do a regex replace on all the matching files.

If you’re using TFVC, the files are marked read-only when the build agent does a Get Latest, so I had to remove the read-only bit as well. The other trick was getting the source path and the build number – but you can use environment variables when executing any of the pre- or post- scripts (as detailed here).

Param(
  [string]$pathToSearch = $env:TF_BUILD_SOURCESDIRECTORY,
  [string]$buildNumber = $env:TF_BUILD_BUILDNUMBER,
  [string]$searchFilter = "AssemblyInfo.*",
  [regex]$pattern = "\d+\.\d+\.\d+\.\d+"
)

try
{
    if ($buildNumber -match $pattern -ne $true) {
        Write-Host "Could not extract a version from [$buildNumber] using pattern [$pattern]"
        exit 1
    } else {
        $extractedBuildNumber = $Matches[0]
        Write-Host "Using version $extractedBuildNumber"

        gci -Path $pathToSearch -Filter $searchFilter -Recurse | %{
            Write-Host "  -> Changing $($_.FullName)" 
        
            # remove the read-only bit on the file
            sp $_.FullName IsReadOnly $false

            # run the regex replace
            (gc $_.FullName) | % { $_ -replace $pattern, $extractedBuildNumber } | sc $_.FullName
        }

        Write-Host "Done!"
    }
}
catch {
    Write-Host $_
    exit 1
}

Save this script as “UpdateVersion.ps1” and put it into Source Control (I use a folder called $/Project/BuildProcessTemplates/CommonScripts to house all the scripts like this one for my Team Project).


image


The open your build and specify the source control path to the pre-build script (leave the arguments empty, since they’re all defaulted) and add a version number to your build number format. Don’t forget to add the script’s containing folder as a folder mapping in the Source Settings tab of your build.


image


image


Now you can run your build, and your assembly (and exe) versions will match the build number:


image


I’ve tested this script using TFVC as well as a TF Git repository, and both work perfectly.


Happy versioning!