Injecting Version Numbers Into WiX Projects

I've been meaning to blog about this for a bit. When I responded to someone asking about this very thing on the OzTFS mailing list, I basically crafted the post. So here it is.

I was tasked with making sure the product version number was applied to the deployment package during the release build process. I pored over the problem for a while and found many strange and complex approaches to solving it. Then I had a V8 moment and realized the answer was much simpler than I thought.

I didn't have the burden of persisting the version number in any of my source code because it's date/time driven. I know a lot of people like incrementing revisions that rollover each day, but I gave that up for simplicity. The major and minor values are set statically in the TFSBuild.proj or passed in via command-line arguments. Those values change far less often so this was an acceptable approach. I can even use Error tasks to ensure those values are passed in when doing a release candidate build.

I was already using a regular expression-based FileUpdate task from the MSBuild.Community.Tasks component to update my AssemblyInfo files. I just applied the same technique to update the .wixproj files in the AfterGet target.

<CreateItem Include="$(SolutionRoot)\**\*.wixproj">  
   <Output TaskParameter="Include" ItemName="WixProjectsToVersion" />  
</CreateItem>  
<Attrib Files="@(WixProjectsToVersion)" Normal="true" />  
<FileUpdate Files="@(WixProjectsToVersion)" Regex="SimpleVersion=.*;DetailedVersion=.*&lt;" ReplacementText="$(WixVersions)&lt;" />

This updates preprocessor variables defined in the Property pages of the WiX project as follows.

SimpleVersion=1.0;DetailedVersion=1.0.0.0

The $(WixVersions) property is generated earlier in the build process. As long as it's value ends up looking similar to the following line by the time the FileUpdate task executes, you'll be in good shape.

SimpleVersion=3.5;DetailedVersion=3.5.123.12345

I then used the MSBuild task in the PackageBinaries target to compile the WiX projects. Setting the OutputPath property ensures that the resulting MSI file(s) end up where the rest of the build outputs are.

<MSBuild Projects="$(SolutionRoot)\Setup.wixproj"   
Properties="OutputPath=$(BinariesRoot);Configuration=Release" />

While we're on the subject of build outputs, I should mention a little trick that enables your WiX projects to be built on locally AND on the build server. This next snippet should be self-explanatory:

<!-- Preprocessor directives to conditionally set source paths based on type of build -->  
<?if "$(env.USERNAME)"="s-tfsservice"?>  
   <?define Project1Bin="..\..\..\..\..\..\Binaries\Mixed Platforms\$(var.Configuration)"?>  
<?else?>  
   <?define Project1Bin="..\..\..\some path\bin\$(var.Configuration)"?>  
<?endif?>

A lot of people struggle with the approach of specifying the DefineConstants property override in the <SolutionToBuild> element in their build scripts. The reason this doesn’t work is because those properties and items are all defined when the build is initialized. The dynamic version number hasn't been generated yet.

jb