26

In my Defines.wxi I have:

<?define MajorVersion="1" ?>
<?define MinorVersion="08" ?>
<?define BuildVersion="11" ?>

In my MyProject.Setup.wixproj I have:

<OutputName>MyProject</OutputName>
<OutputType>Package</OutputType>

Is it possible to include the version variables in the filename somehow, so that my file can be named MyProject.1.08.11.msi?

This didn't work (no such variable is defined):

<OutputName>MyProject-$(MajorVersion)</OutputName>
<OutputType>Package</OutputType>

This didn't work (no such variable is defined):

<Target Name="AfterBuild" Condition="'$(Configuration)' == 'Release'">
    <Copy SourceFiles="$(OutputPath)$(OutputName).msi" DestinationFiles="C:\$(OutputName)-$(MajorVersion).msi" />
</Target>

It seems very clear to me that $(MajorVersion) is not the correct way of fetching the definition from the Defines.wxi file. What is?


Update

I tried to put this in MyProject.Setup.wixproj:

<InstallerMajorVersion>7</InstallerMajorVersion>
<InstallerMinorVersion>7</InstallerMinorVersion>
<InstallerBuildNumber>7</InstallerBuildNumber>

...

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
    <OutputPath>bin\$(Configuration)\</OutputPath>
    <IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
    <DefineConstants>PrebuildPath=..\..\obj\prebuild\web\;InstallerMajorVersion=$(InstallerMajorVersion);InstallerMinorVersion=$(InstallerMinorVersion);InstallerBuildNumber=$(InstallerBuildNumber)</DefineConstants>
</PropertyGroup>

And this in Defines.wxi:

<?define MajorVersion="$(var.InstallerMajorVersion)" ?>
<?define MinorVersion="$(var.InstallerMinorVersion)" ?>
<?define BuildVersion="$(var.InstallerBuildNumber)" ?>
<?define Revision="0" ?>
<?define VersionNumber="$(var.InstallerMajorVersion).$(var.InstallerMinorVersion).$(var.InstallerBuildNumber)" ?>

Didn't work either. Got these error messages:

  • The Product/@Version attribute's value, '..', is not a valid version. Legal version values should look like 'x.x.x.x' where x is an integer from 0 to 65534.
  • The Product/@Version attribute was not found; it is required.

7 Answers7

16

This common task should be simplified in future versions of WiX!

This solution combines @Wimmel's and this post. It draws the version from a target .exe, and otherwise does not store version numbers in files; it doesn't rename the output file in post-build. But, it is necessary to update the property ProjectDefineConstants, from which the candle arguments are derived (in wix.targets). Otherwise, updating only the TargetPath property does not change the inputs to candle.exe.

*.wixproj:

<Import Project="$(WixTargetsPath)" />
<Target Name="BeforeBuild">
  <!-- Read the version from the to-be-installed .exe -->
  <GetAssemblyIdentity AssemblyFiles="path.to.primary.exe">
    <Output TaskParameter="Assemblies" ItemName="AsmInfo" />
  </GetAssemblyIdentity>

  <!-- Create the MSBuild property $(VersionNumber) -->
  <CreateProperty Value="%(AsmInfo.Version)">
    <Output TaskParameter="Value" PropertyName="VersionNumber" />
  </CreateProperty>

  <!-- Create the WiX preprocessor variable $(var.VersionNumber) -->
  <CreateProperty Value="$(DefineConstants);VersionNumber=$(VersionNumber)">
    <Output TaskParameter="Value" PropertyName="DefineConstants" />
  </CreateProperty>

  <!-- Update the MSBuild properties $(TargetName), etc. -->
  <CreateProperty Value="$(SolutionName)-$(Platform)-$(VersionNumber)">
    <Output TaskParameter="Value" PropertyName="TargetName" />
  </CreateProperty>
  <CreateProperty Value="$(TargetName)$(TargetExt)">
    <Output TaskParameter="Value" PropertyName="TargetFileName" />
  </CreateProperty>
  <CreateProperty Value="$(TargetName)$(TargetPdbExt)">
    <Output TaskParameter="Value" PropertyName="TargetPdbName" />
  </CreateProperty>
  <CreateProperty Value="$(TargetDir)$(TargetFileName)">
    <Output TaskParameter="Value" PropertyName="TargetPath" />
  </CreateProperty>
  <CreateProperty Value="$(TargetPdbDir)$(TargetPdbName)">
    <Output TaskParameter="Value" PropertyName="TargetPdbPath" />
  </CreateProperty>

  <!-- Update the MSBuild property from which candle.exe args are derived -->
  <CreateProperty Value="
    Configuration=$(ConfigurationName);
    OutDir=$(OutDir);
    Platform=$(PlatformName);
    ProjectDir=$(ProjectDir);
    ProjectExt=$(ProjectExt);
    ProjectFileName=$(ProjectFileName);
    ProjectName=$(ProjectName);
    ProjectPath=$(ProjectPath);
    TargetDir=$(TargetDir);
    TargetExt=$(TargetExt);
    TargetFileName=$(TargetFileName);
    TargetName=$(TargetName);
    TargetPath=$(TargetPath);
  ">
    <Output TaskParameter="Value" PropertyName="ProjectDefineConstants" />
  </CreateProperty>
</Target>

*.wxs

<Product Id="*" Version="$(var.VersionNumber)" ... >
  ...
</Product>
Community
  • 1
  • 1
Steve Mitchell
  • 1,895
  • 1
  • 15
  • 12
  • Excellent. Best solution here. – Zac Morris Sep 15 '14 at 20:44
  • @l33t why? what's the problem with this solution? Your link doesn't work for me currently... – Nicolas Feb 14 '19 at 15:43
  • @Steve Mitchell thx this worked for me. Is there an easier way to crawl to the project executable file than this: `$(ProjectDir)..\MyName\bin\$(Configuration)\MyExeName.exe`? Also, is there a possibility to add "Beta" to the msi name if the setup was compiled with debug binaries? – Nicolas Feb 14 '19 at 16:25
  • @Nicolas, try this link: http://web.archive.org/web/20150416052138/http://wixtoolset.org/issues/4555/ – l33t Feb 19 '19 at 14:29
  • @l33t thx for the link! I checked that `candle.exe` prints `-d"TargetFileName=Correct Modified Name Including Version.msi"` for me, so I assume this is fixed. – Nicolas Feb 21 '19 at 13:28
  • `GetAssemblyIdentity` only works for .NET assemblies. It does not read the `VS_VERSIONINFO` data on native binaries. T_T – Keith Russell Dec 15 '20 at 19:45
15

This is what I ended up with, and it works!

Setup.Version.proj

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <PropertyGroup>
        <InstallerMajorVersion>55</InstallerMajorVersion>
        <InstallerMinorVersion>66</InstallerMinorVersion>
    <InstallerBuildVersion>$(BuildNumber)</InstallerBuildVersion>
        <InstallerBuildVersion Condition="$(InstallerBuildVersion) == ''">0</InstallerBuildVersion>
  </PropertyGroup>
</Project>

MyProject.Setup.wixproj

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="Setup.Version.proj" />
  <PropertyGroup>
    <OutputName>MyProject_$(InstallerMajorVersion)_$(InstallerMinorVersion)_$(InstallerBuildVersion)</OutputName>
    <OutputType>Package</OutputType>
    <DefineConstants>InstallerMajorVersion=$(InstallerMajorVersion);InstallerMinorVersion=$(InstallerMinorVersion);InstallerBuildVersion=$(InstallerBuildVersion)</DefineConstants>
    ...

Defines.wxi

<?define MajorVersion="$(var.InstallerMajorVersion)" ?>
<?define MinorVersion="$(var.InstallerMinorVersion)" ?>
<?define BuildVersion="$(var.InstallerBuildVersion)" ?>
  • 1
    so... after trying out quite a lot, I found it works as described, moreover the **defines.wxi** is not necessary. however I was stuck for a while because I did copy-paste the `` bit from the web, and somehow there was a special character or something VS did not accept - I always got the `var.InstallerMajorVersion not found` error. after all it works great, imho really elegant solution! – Damian Vogel Nov 19 '12 at 16:30
  • just identified another potential error: you want to make sure the `` tag is not defined in another `PropertyGroup`, else you get a `var.InstallerMajorVersion not found` error. – Damian Vogel Nov 26 '12 at 09:54
  • If you change the version number and your projects are loaded in a Visual Studio solution with the WiX Visual Studio Extension installed, you have to close and reopen the entire solution for the change to take effect. Somehow the data sticks around even if you unload and reload the WiX project. – snips-n-snails Aug 30 '18 at 20:22
  • after some experiments... if you place the property/properties directly in the wixproj, then you only have to reload the project, instead of reopening the whole solution (VS2022 Community)... I like the Idea, baut apparently the "Import" is not meant to support this goal :-) – dba Sep 08 '22 at 07:52
10

In your .wixproj file. Add the following section just before the </Project> tag.

<!-- rename the output msi with Version number -->
  <Target Name="AfterBuild">
    <GetAssemblyIdentity AssemblyFiles="[Path of the main assembly with the assembly version number you want to use]">
      <Output TaskParameter="Assemblies" ItemName="AssemblyVersion"/>
    </GetAssemblyIdentity>
    <Copy SourceFiles=".\bin\$(Configuration)\$(OutputName).msi" DestinationFiles=".\bin\$(Configuration)\$(OutputName)_%(AssemblyVersion.Version).msi" />
    <Delete Files=".\bin\$(Configuration)\$(OutputName).msi" />
  </Target>

This works for me.

Kuffs
  • 35,581
  • 10
  • 79
  • 92
Ross Brigoli
  • 676
  • 1
  • 12
  • 22
5

One way would be to define the variables in your MSBuild script, and have it update Defines.wxi at build time, as in this example.

In your MSBuild script, you could define the version properties as follows:

  <PropertyGroup>
    <MajorVersion>1</MajorVersion>
    <MinorVersion>08</MinorVersion>
    <BuildVersion>11</BuildVersion>
    <WixConfigPath>.\Defines.wxi</WixConfigPath>

    <_VariableDefinitions>
      <Root>
        <VariableDefinition Name="MajorVersion" NewValue="$(MajorVersion)" />
        <VariableDefinition Name="MinorVersion" NewValue="$(MinorVersion)" />
        <VariableDefinition Name="BuildVersion" NewValue="$(BuildVersion)" />
      </Root>
    </_VariableDefinitions>
  </PropertyGroup>

  <Target Name="UpdateWixVars">
    <WixVarSubstitution SourceFile="$(WixConfigPath)" VariableDefinitions="$(_VariableDefinitions)"/>
  </Target>

Then running the UpdateWixVars target will update the version numbers in Defines.wxi with the version numbers specified in your MSBuild project.

Note that I could not find an actual compiled dll with this custom build task, so I had to create it by:

  1. Download the source from here. Build it and name the file Tranxition.BuildTasks.dll.
  2. Add the reference to the build task like so:

    <UsingTask TaskName="WixVarSubstitution"
         AssemblyFile="$(MSBuildExtensionsPath)\Tranxition\Tranxition.BuildTasks.dll"/>
    
hemp
  • 5,602
  • 29
  • 43
BryanJ
  • 8,485
  • 1
  • 42
  • 61
  • Thanks for your answer. Would love to fix it without using Tranxition. I updated my questions with my latest attempt which is somewhat similar. Do you have any input on that? –  Sep 07 '12 at 19:27
  • while this answer works, it involves manually updating a file for each build, I prefer the answer below which automates the process – nrjohnstone Jun 24 '13 at 22:47
5

It is not possible to read the .wxi file from the .wixproj file. So you have to use another way to specify the version. I can give an example where I read the version from a assembly included in the installer, and use that version to rename the msi;

Open the .wixproj file in an editor and add a ReadVersion target:

  <Target Name="ReadVersion">
    <GetAssemblyIdentity AssemblyFiles="bin\program.exe">
      <Output TaskParameter="Assemblies" ItemName="MyAssemblyIdentities" />
    </GetAssemblyIdentity>
    <Message Text="AssemblyVersion = %(MyAssemblyIdentities.Version)" />
    <CreateProperty Value="$(TargetName) %(MyAssemblyIdentities.Version)">
      <Output TaskParameter="Value" PropertyName="TargetName" />
    </CreateProperty>
    <CreateProperty Value="$(TargetName)$(TargetExt)">
      <Output TaskParameter="Value" PropertyName="TargetFileName" />
    </CreateProperty>
    <CreateProperty Value="$(OutDir)$(TargetFileName)">
      <Output TaskParameter="Value" PropertyName="TargetPath" />
    </CreateProperty>
  </Target>

This reads the version from bin\program.exe, displays it for debugging purposes, and changes the TargetName, TargetFileName and TargetPath.

After the line containing <Import Project="$(WixTargetsPath)" />, add the following to inject this target into the build process:

  <PropertyGroup>
    <BuildDependsOn>ReadVersion;$(BuildDependsOn)</BuildDependsOn>
  </PropertyGroup>
wimh
  • 15,072
  • 6
  • 47
  • 98
  • I updated my question with my latest attempt. It might be clearer now. –  Sep 07 '12 at 19:27
  • Then you have to use `DefineConstants` in a wixproj `PropertyGroup`, see http://stackoverflow.com/a/627279/33499 – wimh Sep 07 '12 at 19:45
  • yes i did that too, pasted it now. looks correct to me, but still get that error –  Sep 07 '12 at 19:47
  • this is a good solution, since it automatically includes the version number of the main assembly in the setup filename rather than having to manually update a file. – nrjohnstone Jun 24 '13 at 22:48
  • For me, this worked in isolation but not with my bootstrapper project. The bootstrapper, using a project reference to the msi, was still looking for the installer's outputname.targetext...not targetfilename. – JDennis Jun 12 '14 at 11:17
  • Perfect! I just had to give the relative path of my main assembly (e.g. ..\bin\MyAssembly.dll) and the installer was named with the version! – Sudhanshu Mishra Aug 04 '15 at 02:10
2

You can accomplish this seamlessly by implementing these two answers:

The other answers are much too complex!

PS: If you want to drop the fourth digit, following semantic versioning, you can do it like this:

<Target Name="AfterBuild">
    <GetAssemblyIdentity AssemblyFiles="..\Path\To\MyProject\bin\$(Platform)\$(Configuration)\MyProject.dll">
        <Output TaskParameter="Assemblies" ItemName="AssemblyInfo" />
    </GetAssemblyIdentity>
    <PropertyGroup>
        <In>%(AssemblyInfo.Version)</In>
        <Pattern>^(\d+.\d+.\d+)</Pattern>
        <AssemblyVersion>$([System.Text.RegularExpressions.Regex]::Match($(In), $(Pattern)))</AssemblyVersion>
    </PropertyGroup>
    <Move SourceFiles="bin\$(Platform)\$(Configuration)\MyProject.msi" DestinationFiles="bin\$(Platform)\$(Configuration)\CodeGenerator-$(AssemblyVersion).$(Platform).msi" />
</Target>

This will create an MSI named, for example, MyProject-1.2.3.x64.msi. See this answer for more.

Community
  • 1
  • 1
Josh M.
  • 26,437
  • 24
  • 119
  • 200
  • 1
    I think renaming the MSI will cause problems: https://blogs.msdn.microsoft.com/robmen/2004/12/08/why-does-changing-the-name-of-your-msi-file-require-a-major-upgrade/ – Loring May 15 '18 at 15:54
0

This is a full example of a wixproj file where you can set a version number in the UI and use that to modify the output msi file name.

In Visual Studio (e.g. 2015):

  • define a version number in "Define preprocessor variables" in the project properties window. I've entered VersionNumber=1.14 for this example;
  • unload your project in the solution explorer;
  • right click your project in the solution explorer and select edit yourproject.wixproj file;
  • add code in the Target Name="BeforeBuild" element as shown below.

    <?xml version="1.0" encoding="utf-8"?>
    <Project ToolsVersion="4.0" DefaultTargets="Build" InitialTargets="EnsureWixToolsetInstalled" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <PropertyGroup>
        <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
        <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
        <ProductVersion>3.10</ProductVersion>
        <ProjectGuid>PROJECT-GUID</ProjectGuid>
        <SchemaVersion>2.0</SchemaVersion>
        <OutputName>my project</OutputName>
        <OutputType>Package</OutputType>
      </PropertyGroup>
      <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
        <OutputPath>bin\$(Configuration)\</OutputPath>
        <IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
        <!-- These constants can be set in the UI -->
        <DefineConstants>VersionNumber=1.14</DefineConstants>
      </PropertyGroup>
      <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
        <OutputPath>bin\$(Configuration)\</OutputPath>
        <IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
        <DefineConstants>VersionNumber=1.14</DefineConstants>
      </PropertyGroup>
      <ItemGroup>
        <Compile Include="Product.wxs" />
      </ItemGroup>
      <ItemGroup>
        <WixExtension Include="WixUIExtension">
          <HintPath>$(WixExtDir)\WixUIExtension.dll</HintPath>
          <Name>WixUIExtension</Name>
        </WixExtension>
      </ItemGroup>
      <Import Project="$(WixTargetsPath)" Condition=" '$(WixTargetsPath)' != '' " />
      <Import Project="$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets" Condition=" '$(WixTargetsPath)' == '' AND Exists('$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\Wix.targets') " />
    
      <Target Name="BeforeBuild">
        <!-- This extracts the version number from a setting in the UI -->
        <!-- This method comes from: geekswithblogs.net/alexhildyard/archive/2013/03/09/passing-data-between-msbuild-and-wix.aspx -->
        <ItemGroup>
          <DefineConstantsKVPairs Include="$(DefineConstants)" />
        </ItemGroup>
        <!-- Evaluate each key/value pair with task batching, then make a conditional assignment -->
        <PropertyGroup>
          <VersionNumber Condition="$([System.String]::new('%(DefineConstantsKVPairs.Identity)').Contains('VersionNumber='))">$([System.String]::new('%(DefineConstantsKVPairs.Identity)').Split('=')[1])</VersionNumber>
        </PropertyGroup>
    
        <CreateProperty Value="$(OutputName)-$(VersionNumber)">
          <Output TaskParameter="Value" PropertyName="TargetName" />
        </CreateProperty>
        <CreateProperty Value="$(TargetName)$(TargetExt)">
          <Output TaskParameter="Value" PropertyName="TargetFileName" />
        </CreateProperty>
        <CreateProperty Value="$(TargetDir)$(TargetFileName)">
          <Output TaskParameter="Value" PropertyName="TargetPath" />
        </CreateProperty>
      </Target>
    </Project>
    
neilt17
  • 345
  • 4
  • 13