Eternal21
Eternal21

Reputation: 4684

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?

Upvotes: 5

Views: 8106

Answers (2)

Based on Tom Blodget answer i did the following:

  1. On <projectName>.wixproj file i added the following under <target Name="BeforeBuild">:

<GenerateProductVersion AssemblyPath='path\to\file.dll' />

  1. Within <Project> tag (for example, just before the closing), i added something like this:
    <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=" + assemblyVersion)))
                  .Save("ProductVersion.wxi");
               ]]>
            </Code>
        </Task>
    </UsingTask>

When compiling the wix project, this will generate the ProductVersion.wxi as Tom explains.

<Include>
  <?define ProductVersion=1.0.38549?>
</Include> 
  1. Then on product.wxs we have to include the autogenerated file ProductVersion.wxi and reference the variable defined there:
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  <?include ProductVersion.wxi?>
  <Product Version="$(var.ProductVersion)" …>
…
</Wix>

Hope this can clarify you a bit more!

Upvotes: 2

Tom Blodget
Tom Blodget

Reputation: 20812

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

Upvotes: 9

Related Questions