I'm trying to configure a WiX setup and library so that the version of one of the files in the library is used as the Product/@Version in the setup.
Background
In a setup with the files defined locally this is relatively straightforward in that assuming the component project is referenced by the WiX project and then configured:
<Component Id="Company.Assembly" Guid="[GUID]">
<File Id="Company.AssemblyFile"
Name="Company.Assembly.dll" KeyPath="yes"
DiskId="1"
Source="$(var.Company.Assembly.TargetPath)" />
</Component>
Then the product version can be set as
<Product Id="[GUID]"
Name="Product Name"
Language="1033"
Version="!(bind.FileVersion.$(var.Company.AssemblyFile
.TargetFileName))"
Manufacturer="Company Name"
UpgradeCode="[GUID]">
Issue
So having moved all the components to a WiX Library project it's no longer possible to directly reference the !(bind.FileVersion.$(var.Company.AssemblyFile.TargetFileName))
variable.
I've tried configuring a WixVariable in the library
WixVariable Id="BuildVersion" Value="!(bind.FileVersion.Company.AssemblyFile)"/>
And then reference that from the setup
<Product Id="[GUID]"
Name="Product Name"
Language="1033"
Version="!(wix.BuildVersion)"
Manufacturer="Company Name"
UpgradeCode="[GUID]">
Without success.
Is there some additional step or syntax required in either the library or setup to make the WixVariable (or some derivation of it) accessible from the setup?
I hear this one a lot and I don't think the WiX documentation does a particularly good job of explaining the situation, so here it is. The short answer is that your syntax is correct; a variable declared with the WixVariable
element is referenced with the syntax !(wix.VariableName)
and you can use variables that have been defined in a referenced library so !(wix.BuildVersion)
is correct for the example you've given above. The reason it doesn't work is because the value needs to be verified during the compilation phase but it's not being generated until the linking phase. So here's the long answer:
There are two distinct types of variable that you can reference in a *.wxs file; preprocessor variables and binder (or linker) variables. The former is referenced with the $ syntax, e.g. $(var.VariableName)
and the latter is referenced with the ! syntax, e.g. !(bind.FileVersion.FileId)
. The key difference is simple: preprocessor variables are parsed during the compile phase (by candle.exe) and binder variables are parsed during the link phase (by light.exe). The compiler is responsible for taking the source *.wxs files and compiling them to *.wixobj files; it doesn't process the actual payload so it's not in a position to read version information from a linked file. The *.wixobj files are then passed to the linker which processes the payload and creates the MSI database. The linker is responsible for collecting metadata from the payload which is why it can provide values for variables like !(bind.FileVersion.FileId)
.
Note that a variable declared with the WixVariable
element is referenced with the ! syntax so it's a binder variable; it will be available to light.exe but it won't be available to candle.exe. This is a problem because candle.exe applies validation to certain fields such as Product/@Version. It has no idea what !(wix.BuildVersion)
will evaluate to so it can't verify that it will yield a valid version. In contrast, you can get away with !(bind.FileVersion.FileId)
because candle is satisfied at compile time that it will resolve to a valid version at link time (FileId is a direct reference to a file in the product so candle trusts that it will exist to yield a version number on link).
So you can use !(wix.BuildVersion)
anywhere else in your *.wxs but you can't use it as a value for Product/@Version. As far as I know the only binder variable you can use here is !(bind.FileVersion.FileId)
but obviously this is no good if you want to get the value from a referenced library. Otherwise you'll just have to get your version information from somewhere else and pass it into WiX so it's available at compile time. If you're using MSBuild it can query version information with the GetAssemblyIdentity
task and can pass this to WiX via the DefineConstants property. The following targets in your *.wixproj file should do it:
<Target Name="BeforeBuild">
<GetAssemblyIdentity AssemblyFiles="[Path.To.Target.File]">
<Output TaskParameter="Assemblies" ItemName="AsmInfo" />
</GetAssemblyIdentity>
<CreateProperty Value="%(AsmInfo.Version)">
<Output TaskParameter="Value" PropertyName="BuildVersion" />
</CreateProperty>
<CreateProperty Value="$(DefineConstants)">
<Output TaskParameter="Value" PropertyName="DefineConstantsOriginal" />
</CreateProperty>
<CreateProperty Value="$(DefineConstants);BuildVersion=$(BuildVersion)">
<Output TaskParameter="Value" PropertyName="DefineConstants" />
</CreateProperty>
</Target>
<Target Name="AfterBuild">
<CreateProperty Value="$(DefineConstantsOriginal)">
<Output TaskParameter="Value" PropertyName="DefineConstants" />
</CreateProperty>
</Target>
The BuildVersion property will be passed to candle.exe so you can reference it with the preprocessor variable $(var.BuildVersion). This certainly isn't as clean as keeping it all in the *.wxs file but it's one way of getting the version information into candle so it can be used as a variable in Product/@Version. I'd certainly like to hear any better ways of doing this.
I had a similar issue when using Heat.exe, this allowed me to override the source variable without messing with system environment variables or other such:
"%wix%bin\heat.exe" dir "$(SolutionDir)Web\obj\$(Configuration)\Package" -cg PACKAGEFILES -gg -g1 -sreg -srd -dr DEPLOYFOLDER -var wix.PackageSource="$(SolutionDir)Web\obj\$(Configuration)\Package" -out "$(SolutionDir)WebInstaller\PackageFragment.wxs"
I think this syntax can be used for any variable declaration that will not otherwise work at linking time:
<wix.YOURVAR="VALUE">-in command line/<!(wix.YOURVAR="VALUE")>-in .WXS files
Maybe helpful in other places too.