Adding project output to a WiX installer project

Published on Monday, July 7, 2014

Photo by Jan Loyde Cabrera on Unsplash

In the last post I mentioned that I've fully moved to WiX for my installers; in this one I'll cover the first big hurdle I ran into with WiX. Hopefully it will help someone else move along a little faster. Just a heads up - I'm doing all this in Visual Studio 2013 with WiX 3.8. With other versions of these tools, YMMV.

The first surprising thing that you'll run into coming from VS Setup Projects is that WiX doesn't grab all of your project output by default. To do that, you have to use WiX's harvester tool, a program called Heat. Heat is a command-line tool which will collect file information and generate the correct WiX XML entries for you. Now, heat actually has a project option which is supposed to get the output of a Visual Studio .csproj build, but sadly in the current version it only gets the main output (e.g., your .exe.), not the dependencies. So it's pretty much useless. Apparently the dependencies will get harvested in version 4, but for now you're stuck with the dir option, which will grab all the files in an directory. If you call this in a pre-build event, you can create a WiX file with the project output and use that list in your installer. Mine looks something like this:

call "%wix%\bin\heat.exe" dir $(SolutionDir)Product\bin\Release -var "var.Product.TargetDir" -ag -srd -dr Product -cg ProductComponents -template fragment -out $(ProjectDir)Output.wxs -v -t $(ProjectDir)Filter.xslt

The %wix% environment variable gets set up by the WiX toolset installer. This is handy on your dev machine, but might be tricky on a CI server. More on that later. Also, make sure you check out the heat.exe documentation for what some of these other flags do; you may want to adjust them depending on your circumstances. In my case, I'm using -dr to create a directory reference called 'Product' - that's the folder where these files will ultimately install. The component group is 'ProductComponents' - the main WiX installer file will reference that.

Once I've got this pre-build event generating the 'Output.wxs' file, I need to add that file (as a link) to my WiX project. That last part of the command line, the -t $(ProjectDir)Filter.xslt bit - I'm telling heat.exe to run the output XML through an XSLT file after it's generated. The XSLT is responsible for removing all the stuff from the output folder which I don't really want in the installer - the .pdb files, .xml files, etc. Because no one should have to slog through writing their own XSLT anymore, here's my Filter.xslt file in full, to make it easier:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
  <xsl:output method="xml" indent="yes" />
  <xsl:template match="@*|node()">
      <xsl:apply-templates select="@*|node()"/>
  <xsl:key name="service-search" match="wix:Component[contains(wix:File/@Source, '.pdb')]" use="@Id" />
  <xsl:key name="service-search" match="wix:Component[contains(wix:File/@Source, '.xml')]" use="@Id" />
  <xsl:key name="service-search" match="wix:Component[contains(wix:File/@Source, 'app.config')]" use="@Id" />
  <xsl:template match="wix:Component[key('service-search', @Id)]" />
  <xsl:template match="wix:ComponentRef[key('service-search', @Id)]" />

In my main WiX project file, I can now reference all these files using the 'ProductComponents' component group by add a ComponentGroupRef to the Feature section, something like this:

<Feature Id="ProductFeature" Title="LiveViewer" Level="1">
  <ComponentGroupRef Id="ProductComponents" />

Every time I rebuild the setup project the components in the output folder will be re-harvested and added. No need to maintain the list manually. Again, hopefully all of this will become obsolete when the new version of WiX hits.

If you're more comfortable editing XML than messing with the command line, there's also an msbuild target for harvesting a directory; you'd just need to translate the command line options for heat.exe into the correct attributes in the XML wrapper.