Search code examples
wix

Configure WiX to automatically set Product Version attribute?


Currently, whenever I build my package, I have to manually increment the Version attribute inside Product.wxs file like this:

<Product 
    Id = "*"
    Version="4.1.3"

I'd like to automate that, to streamline the build process. We use the following versioning scheme for exe/dll files:

major.minor.special.build

The special is almost never used, and set to 0, and the convention was to version the packaged MSI's as following, since you can only use three numbers:

major.minor.build

The only solutions I've seen let you grab the 4 digit version of the other project, and then truncate the build version, so you end up with this:

major.minor.special

Clearly that won't work with our scheme, since we lose the build number. How can I grab major.minor.build, ignoring special?


Solution

  • 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=(?&lt;path&gt;.*?);").Groups["path"].Value)' />
    </Target>
    

    enter image description here