XPath in MSBuild (SDC) and WIX - msbuild

Fresh Asking of this Question->
I have a WIX file that I need to modify using MSBuild. It starts like this:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:iis="http://schemas.microsoft.com/wix/IIsExtension">
<?--... Various Removed Params ...-->
<Product Id='$(var.ProductCode)'
UpgradeCode='$(var.UpgradeCode)'
Name='$(var.AppName)' Language="1033" Version='$(var.ProductVersion)'
Manufacturer='$(var.Manufacturer)'>
<Package Id='$(var.PackageCode)' InstallerVersion="200"
Compressed="yes" />
<?--... More of the WIX XML file ...-->
<iis:WebApplication Id='STWebApp' Name='MyWebSite' Isolation='medium' />
<?--... Rest of the WIX XML file ...-->
My problem is the SDC tasks can't seem to reference any of the xml nodes that are WIX related. For example:
<XmlFile.SetAttribute Path="$(MSBuildProjectDirectory)\TestProduct.wxs"
XPath="//iis:WebApplication" Namespaces="#(Namespaces)"
Name="Name" Value="$(VersionTag)"/>
works just fine because it does not use any Wix nodes (just an iis one), but if I use the full XPath path to it (/Wix/Product/iis:WebApplication) the task returns:
Could not find resource string No matches found for XPath expression
This is not a problem till I want to reference a Directory node (/Wix/Product/Directory/Directory/Directory/Directory[#Id='STWebSiteDir'])
I have tried using the full XPath and the shorter //Directory[#Id='STWebSiteDir']. I have tried single quotes and double quotes, I have tried adding the WIX namespace to the call (with no prefix).
<ItemGroup>
<Namespaces Include="http://schemas.microsoft.com/wix/IIsExtension">
<Prefix>iis</Prefix>
<Uri>http://schemas.microsoft.com/wix/IIsExtension</Uri>
</Namespaces>
<Namespaces Include="http://schemas.microsoft.com/wix/2006/wi">
<Prefix></Prefix>
<Uri>http://schemas.microsoft.com/wix/2006/wi</Uri>
</Namespaces>
</ItemGroup>
I have even tried to just get a reference to /Wix/Product and even that fails:
<XmlFile.SetAttribute Path="$(MSBuildProjectDirectory)\TestProduct.wxs"
XPath="/Wix/Product" Namespaces="#(Namespaces)"
Name="Name" Value="MODIFIED"/>
I am clearly missing something. Anyone with a hint on where to go to get this to work?
Vaccano

Can you just define the variables on the command line to the preprocessor?
candle -dVariableName=ValueForVariable
That might be much easier.

Have you included the Wix default namespace in #(Namespaces)?

<ItemGroup>
<Namespaces Include="http://schemas.microsoft.com/wix/IIsExtension">
<Prefix>iis</Prefix>
<Uri>http://schemas.microsoft.com/wix/IIsExtension</Uri>
</Namespaces>
<Namespaces Include="http://schemas.microsoft.com/wix/2006/wi">
<Prefix>wis</Prefix>
<Uri>http://schemas.microsoft.com/wix/2006/wi</Uri>
</Namespaces>
</ItemGroup>
you should add a prefix for wi namespace too,after that it can ok,i have test it.

OK, so here is the answer:
The namespace prefix needed to be missing for the wix part, not just left empty
<ItemGroup>
<Namespaces Include="http://schemas.microsoft.com/wix/IIsExtension">
<Prefix>iis</Prefix>
<Uri>http://schemas.microsoft.com/wix/IIsExtension</Uri>
</Namespaces>
<Namespaces Include="http://schemas.microsoft.com/wix/2006/wi">
<Uri>http://schemas.microsoft.com/wix/2006/wi</Uri>
</Namespaces>
</ItemGroup>
And then you need to add a prefix value to the wix namespace in the file. I used tst.
Vaccano

Related

Adventures in Installing a C#/WPF Application (WiX)

I've got a C#/WPF application that is one step up from simple - it's got the main application, a couple of libraries, and then some NuGet packages. If I look in the project's bin/release/netcoreapp3.1 directory, there's a single EXE, a couple of JSONs, and a bunch of DLLs (some of which have PDBs). Nothing too crazy. The client wants an installer which doesn't sound like an unreasonable request.
I first try the installer that comes with Visual Studio 2019 and am astounded at how useless it is. After deleting it, I poke around on the interwebs and discover that WiX seems to be relatively popular. Ok, says I, I will try it.
After installing the WiX Toolset Build Tools (which sounds a bit redundant) and the WiX Toolset Visual Studio Extension, and restarting Visual Studio, it's looking good. I right-click on my solution, select "Add" and "New Project" and see "Setup Project for WiX v3" which looks good. I click Next, name it Installer, and I get this:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Name="Installer" Language="1033" Version="1.0.0.0" Manufacturer="" UpgradeCode="2995de5a-87ef-443d-a25a-450d39720349">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<MediaTemplate />
<Feature Id="ProductFeature" Title="Installer" Level="1">
<ComponentGroupRef Id="ProductComponents" />
</Feature>
</Product>
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="Installer" />
</Directory>
</Directory>
</Fragment>
<Fragment>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<!-- TODO: Remove the comments around this Component element and the ComponentRef below in order to add resources to this installer. -->
<!-- <Component Id="ProductComponent"> -->
<!-- TODO: Insert files, registry keys, and other resources here. -->
<!-- </Component> -->
</ComponentGroup>
</Fragment>
</Wix>
Which looks promising. If I try to build it gives me the error "The Product/#Manufacturer attribute's value cannot be an empty string" which is a textbook example of what an error message should look like. Precise, tells me exactly what is happening, and points me in the direction of what I need to do. I'm liking this! I put some text in the Manufacturer attribute, rebuild, and it says "Build: 1 succeeded" and I have an installer. It does give me the warning "The media table has no entries" which makes perfect sense as I haven't told it to install any files.
I start reading the official WiX tutorial and it's painfully verbose. It's not actually a tutorial as much as a treatise on why WiX is the way it is. I don't actually care at this point, I just want the darn thing to work. I want a tutorial, not the entire history of the company. Eventually I find a link for "Getting Started" and I feel a little better. But again, instead of getting me started, I get a lengthy explanation of the internal workings of the system. At this point I DON'T CARE. I want to get a basic installer working, then you can hit me with some details. After several more pages of the author thinking he or she is a university professor in a lecture, I finally get some XML - and it's for a media tag that I may or may not need, it's not at all clear. And, since there isn't a Media tag in the WXS file that was generated for me, I don't know where it goes.
Giving up on the tutorial, I do a Google search for "wix "visual studio" project" and find this page which has a link for "Creating a simple setup" which is perfect. It actually takes me through what I need, step-by-step. I follow the instructions, hit Build, and it says "Build: 1 succeeded". I find the MSI file, run it, there's no UI which seems odd but I can live with that, and it creates a folder in my "Program Files (x86)" folder that has the primary DLL for my project. Progress!
So now I need to include the DLLs constructed from my library projects, and all the DLLs that were included from NuGet packages. It would be cool if I could tell WiX "please include all the files required by my various NuGet packets" but poking around the web, it appears WiX doesn't know how to do that, but that's OK, really all I need is for the installer to include all of the DLLs that end up in my bin/release/netcoreapp3.1 directory.
Trying the obvious wildcard approach:
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<Component Id="ProductComponent">
<File Source="$(var.MyApplication.TargetPath)" />
<File Source="$(var.MyApplication.TargetDir)/*" />
</Component>
</ComponentGroup>
Just gives an error that is not as easy to read as the first error, but does seem to indicate the asterisk is a no-go. If I do a Google search for "wix add all files in directory" returns a whole bunch of different suggestions, all of which are complicated, which seems odd for such a basic thing. But following the instructions in this page, I end up with an installer that almost works. I get the error "Undefined preprocessor variable $(var.HarvestPath)". This page happened to have the answer to that one, and I have a working installer again. But now it's including absolutely everything in that folder, and I only want the DLLs. Doing a Google search for "wix heatdirectory exclude files" gives no answers, it appears this feature is relatively brain-dead.
So here is my current WIXPROJ:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" InitialTargets="EnsureWixToolsetInstalled" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<DefineConstants>HarvestPath=...\Deploy</DefineConstants>
<HeatDefinitions>MySourcePath=..\src\Your.App\bin\Release</HeatDefinitions>
</PropertyGroup>
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProductVersion>3.10</ProductVersion>
<ProjectGuid>4ece284d-9247-4f7a-84a7-34820606a003</ProjectGuid>
<SchemaVersion>2.0</SchemaVersion>
<OutputName>Installer</OutputName>
<OutputType>Package</OutputType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<OutputPath>bin\$(Configuration)\</OutputPath>
<IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
<DefineConstants>Debug</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<OutputPath>bin\$(Configuration)\</OutputPath>
<IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
</PropertyGroup>
<ItemGroup>
<Compile Include="Product.wxs" />
<!-- This will be your default one -->
<Compile Include="HeatGeneratedFileList.wxs" />
<!-- This is the Heat created one -->
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MyApplication\MyApplication.csproj">
<Name>MyApplication</Name>
<Project>{6064dca8-d649-4592-bed5-34039bb3d30d}</Project>
<Private>True</Private>
<DoNotHarvest>True</DoNotHarvest>
<RefProjectOutputGroups>Binaries;Content;Satellites</RefProjectOutputGroups>
<RefTargetDir>INSTALLFOLDER</RefTargetDir>
</ProjectReference>
</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="EnsureWixToolsetInstalled" Condition=" '$(WixTargetsImported)' != 'true' ">
<Error Text="The WiX Toolset v3.11 (or newer) build tools must be installed to build this project. To download the WiX Toolset, see http://wixtoolset.org/releases/" />
</Target>
<Target Name="BeforeBuild">
<HeatDirectory Directory="..\MyApplication\bin\$(Configuration)\netcoreapp3.1"
PreprocessorVariable="var.MyApplication.ProjectDir"
OutputFile="HeatGeneratedFileList.wxs"
ComponentGroupName="HeatGenerated"
DirectoryRefId="INSTALLFOLDER"
AutogenerateGuids="true"
ToolPath="$(WixToolPath)"
SuppressFragments="true"
SuppressRegistry="true"
SuppressRootDirectory="true" />
</Target>
<!--
To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Wix.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
All I want is a simple installer that copies over the EXE and associated DLLs. Why is this so difficult? Can WiX do what I need? Or do I need a different installer toolkit?

Build target tools path

I am currently packaging a nuget package for my code generator project and I have gotten so far as to include an executable into the tools directory and a build target into the process.
Partial from the nuspec
<files>
<file src="cgbr.targets" target="build\cgbr.targets" />
<file src="cgbr.json" target="content\cgbr.json" />
<file src="..\bin\CGbR.Lib.dll" target="lib\CGbR.Lib.dll" />
<file src="..\bin\cgbr.exe" target="tools\cgbr.exe" />
</files>
Content of the cgbr.targets file
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="BeforeBuild">
<Exec Command="cgbr.exe $(ProjectDir)"/>
</Target>
</Project>
Now when I install the package I see that it is included into the build process. Unfortunately the path to cgbr.exe is invalid and I am a little stuck. Of course I could use $(SolutionDir)packages\CGbR.0.3\tools\cgbr.exe but than I would have to modify it every time I change the version.
To clarify: I need the path to my packages tools path.
Edit: Found a related post
You probably want a relative path to the tool from the targets file. There are a number of predefined properties in msbuild. Perhaps the most useful for these scenarios is MSBuildThisFileDirectory which returns the full path to the directory of the current proj file. An example:
<Exec Command=""$(MSBuildThisFileDirectory)..\tools\cgbr.exe" "$(ProjectDir)""/>

How can I use MSBuild 'afterbuild' tasks to edit a .config file?

I have a .config in a target project and I need to add a line to it programmatically via an MSBuild task.
Pseduo operations like:
find target .config file
determine the value of attributes for new node (e.g. 'id' and 'version' for 'package' node)
insert new node in correct parent node
save changes
The .config file at $TargetProjectDir\Config\packages.config:
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ABC" version="1.1.0.4" />
<package id="XYZ" version="2.0.0.0" />
</packages>
Needs to look like this afterwards:
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ABC" version="1.1.0.4" />
<package id="XYZ" version="2.0.0.0" />
<package id="CarDataWidget" version="3.0.0.0" />
</packages>
So far i've considered using 'inline tasks', the 'EXEC' task and 'XmlPoke' task but haven't managed to get any of them working.
Here is my attempt with XmlPoke and XmlPeek:
I used the following article as an inspiration on how to add nodes to the packages.config file:
http://weblogs.asp.net/bsimser/appending-nodes-in-xml-files-with-xmlpeek-and-xmlpoke-using-nant
<Target Name="AfterBuild" DependsOnTargets="AddPackage">
</Target>
<Target Name="AddPackage">
<!-- Load existing nodes into a Property -->
<XmlPeek XmlInputPath="config/packages.config" Query="/packages/package" >
<Output TaskParameter="Result" PropertyName="Peeked" />
</XmlPeek>
<Message Text="From Peek: $(Peeked)"></Message>
<!-- Load new node into Property -->
<PropertyGroup>
<WidgetName>CarDataWidget</WidgetName>
<WidgetVersion>2.0.0.0</WidgetVersion>
<NewNode><package id="$(WidgetName)" version="$(WidgetVersion)" /></NewNode>
<!-- Concatenate existing and new node into a Property -->
<ConcatenatedNodes>$(Peeked)$(NewNode)</ConcatenatedNodes>
</PropertyGroup>
<Message Text="New pacakges: $(ConcatenatedNodes)"></Message>
<!-- Replace existing nodes with concatenated nodes -->
<XmlPoke Value="$(ConcatenatedNodes)" XmlInputPath="config/packages.config" Query="/packages">
</XmlPoke>
</Target>
The output from the above build is:
1>AddPackage:
1> From Peek: <package id="ABC" version="1.1.0.4" />;<package id="XYZ" version="2.0.0.0" />
1> New pacakges: <package id="ABC" version="1.1.0.4" />;<package id="XYZ" version="2.0.0.0" /><package id="CarDataWidget" version="2.0.0.0" />
1> C:\_dev\CarDataWidget.csproj(184,14):
error MSB4094: "<package id="ABC" version="1.1.0.4" />;<package id="XYZ" version="2.0.0.0" /><package id="CarDataWidget" version="2.0.0.0" />"
is an invalid value for the "Value" parameter of the "XmlPoke" task.
Multiple items cannot be passed into a parameter of type "Microsoft.Build.Framework.ITaskItem".
1>
1>Build FAILED.
THE QUESTION:
How can get it to add to a .config file with existing package nodes???
I had the same problem. I found the solution here.
The problem is than XmlPoke considers semicolon as a value separator.
Should replace this:
<NewNode><package id="$(WidgetName)" version="$(WidgetVersion)" /></NewNode>
With:
<NewNode>&lt%3Bpackage id&#61%3B&quot%3B$(WidgetName)&quot%3B version&#61%3&quot%3$(WidgetVersion)&quot%3 /&gt%3</NewNode>
Must replace each semicolon by the secuence %3B
Here is a way to do it using MSBuild Extension Pack.
Set the packages and versions in the NewPackage item group and it adds them to the XML file.
<Project
ToolsVersion="4.0"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\ExtensionPack\4.0\MSBuild.ExtensionPack.tasks" />
<Target Name="Test" DependsOnTargets="AddPackage">
</Target>
<ItemGroup>
<NewPackage Include="CarDataWidget">
<Version>3.0.0.0</Version>
</NewPackage>
<NewPackage Include="FooBarWidget">
<Version>1.2.3.4</Version>
</NewPackage>
</ItemGroup>
<Target Name="AddPackage">
<PropertyGroup>
<InputFile>in.xml</InputFile>
<OutputFile>out.xml</OutputFile>
</PropertyGroup>
<Copy SourceFiles="$(InputFile)" DestinationFiles="$(OutputFile)" />
<MSBuild.ExtensionPack.Xml.XmlFile
TaskAction="AddElement"
File="$(OutputFile)"
XPath="//packages"
Element="package"
Key="id"
Value="%(NewPackage.Identity)" />
<MSBuild.ExtensionPack.Xml.XmlFile
TaskAction="AddAttribute"
File="$(OutputFile)"
XPath="//packages/package[#id='%(NewPackage.Identity)']"
Key="version"
Value="%(NewPackage.Version)" />
</Target>
</Project>
Not hoping to wake up an old thread.I had the exact scenario were I had to add new keys to the appsettings section of web.config. I started off with OPs code and was stuck with the same problem with ; in the peeked value preventing the new concatenated value to be written. I fixed it by using Replace function to remove the ;
<ConcatenatedNodes>$(Peeked)$(NewNode)</ConcatenatedNodes>
<!--in the concatenatednode, remove semicolon-->
<ChangedPeek>$(ConcatenatedNodes.Replace(";",""))</ChangedPeek>
<!-- Replace existing nodes with concatenated nodes-->
<XmlPoke XmlInputPath="%(WebConfigFilesSolutionDir.FullPath)" Query="//appSettings" Value="$(ChangedPeek)" />
For the complete answer on how to add a new key to appsetting section of webconfig using MSBuild refer https://stackoverflow.com/a/56760009/6664129
Take a look at my blog post http://sedodream.com/2011/12/29/UpdatingXMLFilesWithMSBuild.aspx which compares the following methods.
Use SlowCheetah to transform the files for you
Use the TransformXml task directly
Use the built in (MSBuild 4.0) XmlPoke task
Use a third party task library

Msbuild just produces a package but does not deploy it when using a publish profile. Why?

MSBuild really seems to like me.
Recently I am trying out the different possibilities to build and deploy from command line.
However I am experiencing some seemingly strange behaviour when I pass a publish profile to MSBuild.
Here is an example of what I just did:
I deploy to a local IIS for the moment with a command such as this:
msbuild D:\PathToFile\DeployDBVariation01\DeployDBVariation01\DeployDBVariation01.csproj
/p:Configuration=Release;
Platform=AnyCpu;
DeployOnBuild=true;
DeployTarget=MSDeployPublish;
MSDeployServiceURL="localhost";
DeployIisAppPath="DeployApp/DeployThis";
MSDeployPublishMethod=InProc;
Username=thisIsNotActuallyMyUsername;
password=guesswhat;
AllowUntrustedCertificate=true
And this works! After that it is successfully deployed and I can call it in a browser.
However, since Visual Studio gives us the comfort of using publishing profiles I wanted to try that in conjunction with MSBuild over command line and tried the following command:
msbuild D:\PathToFile\DeployDBVariation01\DeployDBVariation01\DeployDBVariation01.csproj
/p:DeployOnBuild=true;
AllowUntrustedCertificate=true;
PublishProfile=ReleaseLocal
ReleaseLocal is a profile I created in Visual Studio and it looks like this:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<WebPublishMethod>MSDeploy</WebPublishMethod>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<SiteUrlToLaunchAfterPublish />
<ExcludeApp_Data>False</ExcludeApp_Data>
<MSDeployServiceURL>localhost</MSDeployServiceURL>
<DeployIisAppPath>DeployApp/DeployThis</DeployIisAppPath>
<RemoteSitePhysicalPath />
<SkipExtraFilesOnServer>True</SkipExtraFilesOnServer>
<MSDeployPublishMethod>InProc</MSDeployPublishMethod>
<EnableMSDeployBackup>False</EnableMSDeployBackup>
<UserName />
<_SavePWD>False</_SavePWD>
<PublishDatabaseSettings>
<Objects xmlns="">
<ObjectGroup Name="DefaultConnection" Order="1" Enabled="False">
<Destination Path="Data Source=.\SQLEXPRESS;Initial Catalog=HorstDataProductive;User ID=sa;Password=GuessWhat" />
<Object Type="DbDacFx">
<PreSource Path="Data Source=.\SQLEXPRESS;Initial Catalog=HorstData;User ID=sa;Password=GuessWhat" includeData="False" />
<Source Path="$(IntermediateOutputPath)AutoScripts\DefaultConnection_IncrementalSchemaOnly.dacpac" dacpacAction="Deploy" />
</Object>
<UpdateFrom Type="Web.Config">
<Source MatchValue="Data Source=.\SQLEXPRESS;Initial Catalog=HorstData;User ID=sa;Password=GuessWhat" MatchAttributes="$(UpdateFromConnectionStringAttributes)" />
</UpdateFrom>
</ObjectGroup>
</Objects>
</PublishDatabaseSettings>
</PropertyGroup>
<ItemGroup>
<MSDeployParameterValue Include="$(DeployParameterPrefix)DefaultConnection-Web.config Connection String">
<ParameterValue>Data Source=.\SQLEXPRESS;Initial Catalog=HorstDataProductive;User ID=sa;Password=GuessWhat</ParameterValue>
</MSDeployParameterValue>
</ItemGroup>
</Project>
As you can see I have some additional connection string replacement there that I want to test.
So I execute that last MSBuild-command that I have shown you and it executes without any errors. Instead it says that the web deployment task was successful and that a package has been created in a certain path. Now that is actually the right package. When I import that manually in my IIS it is the result I expect, also the connection string replacement has been done.
But I do not understand why it actually is just creating the package but not deploying it in one run, like in my first command.
Can someone explain?
(Or even better, what do I have to do to make it also deploy that package immediately)
You need to specify the VS version.
http://www.asp.net/mvc/tutorials/deployment/visual-studio-web-deployment/command-line-deployment
msbuild /P:DeployOnBuild=True /P:VisualStudioVersion=11.0 /P:PublishProfile=Dev.pubxml
Don't forget to allow untrusted certs if you're using a 'fake' internal certificate
Looks like you're missing the deploy target.. it's got all the necessary info, but doesn't know what you want it to do. Try this;
msbuild D:\PathToFile\DeployDBVariation01\DeployDBVariation01\DeployDBVariation01.csproj
/p:DeployOnBuild=true;DeployTarget=MSDeployPublish;AllowUntrustedCertificate=true;PublishProfile=ReleaseLocal

WIX: Howto set the name of the msi output file dynamically

I want to include some dynamic part in the filename of the msi file my wix projects produce. This dynamic part should be controlled by variables which are part of my wix project and are declared like this:
<?define ProductVersion="7.1.0.1" ?>
Does anybody know about a way of sending that value of that wix variable to the linker to use it as a part of the output filename?
By the way: I'm using Wix3
You could update the OutputName of your .wixproj and use an MSBuild variable to pass through the version number or any other variable you like.
My build script looks like this:
set PRODUCTVERSION=7.1.0.1
MSBuild.exe /p:Configuration=Debug /p:ProductVersion=%PRODUCTVERSION% Installer.wixproj
And my WiX project looks like this:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProductVersion>1.0.0.0</ProductVersion>
<ProjectGuid>{b7415c44-8d59-4ac2-b698-03e399a305e3}</ProjectGuid>
<SchemaVersion>2.0</SchemaVersion>
<OutputName>Installer.$(ProductVersion)</OutputName>
...
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<OutputPath>bin\$(Configuration)\</OutputPath>
<IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
<DefineConstants>Debug</DefineConstants>
<WixVariables>ProductVersion=$(ProductVersion)</WixVariables>
</PropertyGroup>
...
</Project>
The output would be:
Installer.7.1.0.1.msi
The msi file name is not determined by your wix files, but by the light.exe -out switch. You can use the same value for -out and inside your wix files if you do the following in your build script, assuming it is a batch script:
set an environment variable with set
productversion=1.2.3
Pass -out foo%productversion%.msi to the
light.exe linker
use the same environment variable in
your wix files as
$(env.productversion)
Open *.wixproj (example: Setup.wixproj)
Go to the end of the file.
$(Configuration) = Debug| Release …
Set path of your application on AssemblyFiles.
<Target Name="BeforeBuild">
<GetAssemblyIdentity AssemblyFiles="..\App\bin\$(Configuration)\App.exe">
<Output TaskParameter="Assemblies" ItemName="AsmInfo" />
</GetAssemblyIdentity>
<CreateProperty Value="$(SolutionName)_%(AsmInfo.Version)_$(Configuration)">
<Output TaskParameter="Value" PropertyName="TargetName" />
</CreateProperty>
</Target>
Output = App_1.0.0.0_Debug.msi
I found a couple of great reference posts to do just this operation:
http://blog.tentaclesoftware.com/archive/2009/05/03/38.aspx
and a follow-on with a better method of creating the output file using a pre-build event:
http://blog.tentaclesoftware.com/archive/2010/08/05/99.aspx
I know the links are old, but the method is sound.
Here are the basic steps:
Put the version you wish to use into a file you can access from your project. I use the main executable of my installation because I also bind to that version in my wxs. In my case, since I am building with C# and use SVN, I have a template version of my assembly.cs, assembly.cs.txt, that I run subwcrev on as a pre-build event to create the assembly.cs that gets compiled into my executable (I actually do it in a separate project in my solution). Subwcrev inserts some date and revision information that I use to create a version in the form "major.minor.version.0" where I use "year.month.revision.0" for my version.
Now, I usually just set AssemblyFileVersion with this method, but to be able to use my version number in the wixproj build event referenced in the post above, I also need to set AssemblyVersion since that is what can be accessed with GetAssemblyIdentity. This method would be questionable if I were really using an assembly someone else links to, but for me it is OK since it is in my final application executable.
Follow the steps outlined in the second post (the first post discusses using the binding method for version in wxs and how to unload and edit the wixproj - valuable context for the second post).
Works like a charm!
Update some years later - it looks like the links I referenced are dead. Here is a snapshot of what I changed to make it work.
In my main product wxs file, I changed (some names have been changed or omitted to protect the innocent and the guilty):
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<!-- TODO: Remove the comments around this Component element and the ComponentRef below in order to add resources to this installer. -->
<Component Id="ProductComponent" Guid="DF1AB3A6-17D0-4176-B3AC-C073AC47AA80">
<RemoveFile Id="PurgeAppFolder" Name="*.*" On="uninstall" />
<File Source="$(var.MyApp.TargetPath)" KeyPath="yes"/>
<File Source="$(var.MyApp.ProjectDir)\bin\Release\MyData.dll"/>
<File Source="$(var.MyApp.ProjectDir)\bin\$(var.MyApp.Configuration)\ICSharpCode.SharpZipLib.dll"/>
</Component>
</ComponentGroup>
to
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<!-- TODO: Remove the comments around this Component element and the ComponentRef below in order to add resources to this installer. -->
<Component Id="ProductComponent" Guid="DF1AB3A6-17D0-4176-B3AC-C073AC47AA80">
<RemoveFile Id="PurgeAppFolder" Name="*.*" On="uninstall" />
<File Id="MAINEXECUTABLE" Source="$(var.MyApp.TargetPath)" KeyPath="yes">
<Shortcut Directory="ApplicationProgramsFolder" Id="MYAPPEXEAPF" Name="My App Name" Icon="MyAppIcon.exe" IconIndex="0" Advertise="yes" />
<Shortcut Directory="ApplicationDesktopFolder" Id="MYAPPEXEADF" Name="My App Name" Icon="MyAppIcon.exe" IconIndex="0" Advertise="yes" />
</File>
<File Source="$(var.MyApp.ProjectDir)\bin\Release\MyData.dll"/>
<File Source="$(var.MyApp.ProjectDir)\bin\$(var.MyApp.Configuration)\ICSharpCode.SharpZipLib.dll"/>
</Component>
</ComponentGroup>
and up at the top I added:
<Product Id="C86505BF-303F-4D6B-8F5F-43A57635F85D" Name="My Application Name" Language="1033" Version="!(bind.FileVersion.MAINEXECUTABLE)" Manufacturer="My Software Shop" UpgradeCode="D1C628E5-5478-4DA5-A31A-F9191D5B1544">
Note that the Version is bound to the file version of MAINEXECUTABLE, which is defined in the main product component.
I hope this helps!
If you have several configurations then inside .wixprj file you can do the following
<OutputName Condition="'$(Configuration)' == 'User'">User.Setup</OutputName>
<OutputName Condition="'$(Configuration)' == 'Designer'">Designerr.Setup</OutputName>
Since it's just a file name, why not have a post-build action that renames the file in your build script (assuming MSBuild)?
Do the variables have to be defined in WiX? I'm building my setup binaries from MSBuild, and I've simply set the output file name to MyProject_$(Platform) -- I expect that any MSBuild variable substitution will work equally well.
Product Id="GUID" Name="whatevername $(var.ProductVersion)"