I use a WiX variable from an include file that I regenerate with every build.
Since my project is a .wixproj (MSBuild/Visual Studio), I just code the version extraction and formatting right in there as a custom, inline MSBuild task and call it in the BeforeBuild target.
In the example below, I get the assembly version of the main assembly of the product. You can code it up for any version you want.
Using WiX Include and variable
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<?include ProductVersion.wxi?>
<Product Version="$(var.ProductVersion)" …>
…
</Wix>
Example ProductVersion.wxi
<Include>
<?define ProductVersion=1.0.38549?>
</Include>
I recommend including the .wxi file in the project so it's visible in the Solution View. And, since it's generated, I recommend excluding it from source control.
Edit WixProj
A .wixproj is both a Visual Studio project file and an MSBuild project file. To edit a Visual Studio project file in Visual Studio, pick a tutorial or extension.
BeforeBuild Target
MSBuild systems, including WiX's, offer BeforeBuild and AfterBuild targets, as explained in the .wixproj comments.
Just pull the target out of the comments and add a task call.
<Target Name="BeforeBuild">
<GenerateProductVersion AssemblyPath='../wherever/whatever.exe' />
</Target>
MSBuild Inline Task
Task code can be in its own MSBuild file or even DLL for reuse. Or, for a scripting approach, it can be inline.
There are 3 parts to this task:
- File path parameter (because it would vary from project to project)
- Extraction (with logging)
- Include generation (to a hard-coded name in the project folder because it doesn't need to vary)
.
<UsingTask TaskName="GenerateProductVersion" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<AssemblyPath ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" />
<Using Namespace="System" />
<Using Namespace="System.Xml.Linq" />
<Using Namespace="System.Reflection" />
<Code Type="Fragment" Language="cs"><![CDATA[
var assemblyVersion = AssemblyName.GetAssemblyName(AssemblyPath).Version;
var productVersion = String.Format("{0}.{1}.{2}", assemblyVersion.Major, assemblyVersion.Minor, assemblyVersion.Revision);
Log.LogMessage(MessageImportance.High, "ProductVersion=" + productVersion + " extracted from assembly version of " + AssemblyPath);
new XDocument(
new XElement("Include",
new XProcessingInstruction("define", "ProductVersion=" + productVersion)))
.Save("ProductVersion.wxi");
]]></Code>
</Task>
</UsingTask>
Making EXE path more visible
All of this is hidden too well in the project file. Many project designers have a Build tab that allows entering name-value pairs into the build. That provides a mechanism to raise the path up out of the XML.
<Target Name="BeforeBuild">
<GenerateProductVersion AssemblyPath='$([System.Text.RegularExpressions.Regex]::Match(";$(DefineConstants);", ";VersionExtractionPath=(?<path>.*?);").Groups["path"].Value)' />
</Target>
