Include MajorVersion etc in filename (OutputName)

2019-01-22 13:09发布

问题:

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.

回答1:

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)" ?>


回答2:

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>


回答3:

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.



回答4:

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>


回答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"/>
    


回答6:

You can accomplish this seamlessly by implementing these two answers:

  • First, update your project's AfterBuild task to rename the WIX-built MSI: https://stackoverflow.com/a/10716409/374198
  • Then, update your WIX project to automatically use the verison number of the assembly you're packaging: https://stackoverflow.com/a/641094/374198

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.



回答7:

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>