0
votes

I'm using the WiX heat command (on the command line, not MSBuild) to generate .wxs files for several directories. There are many individual directories to be harvested, so I want to use the generated .wxs files as-is.

My directory structure is the same for my release/build and for the installation, like this:

AppName\Mods\SomeModule
AppName\Mods\SomeOtherModule
...
AppName\Mods\YetAnotherModule
AppName\Mods\ModuleNumber42

Each directory corresponds to a modular piece that I want to be able to add or leave out individually, so I don't want to harvest the entire Mods directory to a single .wxs file.

The names of the directories are important; they need to remain the same from the build folder to the install folder. I've been using heat like this:

heat dir Mods\SomeModule -cg SomeModule -dr MyModsDir -out SomeModule.wxs

I want to do this for each module directory, then include a ComponentGroupRef for each ComponentGroup in a Feature in my main file.

The problem is that when heat generates the .wxs file, it leaves out the name of the directory being harvested in each file's Source. It looks something like this:

<DirectoryRef Id="MyModsDir">
  <Directory Id="xxx" Name="SomeModule">
    <Component Id="xxx" Guid="{someuuid}">
      <File Id="xxx" KeyPath="yes" Source="SourceDir\file1" />
    </Component>
    ...
  </Directory>
</DirectoryRef>

When it needs to look like this:

<DirectoryRef Id="MyModsDir">
  <Directory Id="xxx" Name="SomeModule">
    <Component Id="xxx" Guid="{someuuid}">
      <File Id="xxx" KeyPath="yes" Source="SourceDir\SomeModule\file1" />
    </Component>
    ...
  </Directory>
</DirectoryRef>

Try as I might, I can't seem to convince heat to include the harvest directory's name in the file's Source attribute.

I've pored through the WiX documentation and played with options like -var and -srd, even running the command from different directories, with no success. I've looked at some possible solutions, but none of these work for me:


  1. Use the -var option to heat so that I can pass a preprocessor or wix variable.

I could define a WixVariable with the path to my Mods source directory, then pass in, for example, -var wix.ModsDir. However, then WiX would look directly in the Mods directory for files that are actually located deeper. I could define a WixVariable for every single module, but it seems silly and dangerous to clutter up my namespace with so many variables.

If I used a preprocessor variable, I would need to manually add an <?include?> and/or <?define?> line to each generated file, to append the module name into the path. As I said, I don't want to have to do this -- too labor-intensive and prone to errors, when this is something that should easily be automated by heat.

  1. Use the -b option when using light.

I could add a -b option for my Mods directory, but then I get the same problem as above, where WiX doesn't look deep enough to find the files.

Alternatively, I would have to add a -b option for every single module, and the module directories often contain files with the same names as other modules' files, so there would be name collisions.


Including the name of the harvested directory is such a simple and obvious thing, that I would have thought heat would do this by default. I'm new to WiX, so maybe I'm just misunderstanding something. Any ideas on a way around this problem?

1

1 Answers

1
votes

Normally you would use the -var var.ModNameDir. I do something like this with the HarvestDirectory target and update the DefineConstants property as <DefineConstants>ModNameDir=$(ModNameDir);$(DefineConstants)</DefineConstants> where $(ModNameDir) is the msbuild property pointing to the folder path.

This is a bit confusing if you dont know what you are doing with regards to msbuild so, a 3rd option you can use is a simple xslt transform like this:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" 
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:wix="http://schemas.microsoft.com/wix/2006/wi"
                xmlns="http://schemas.microsoft.com/wix/2006/wi"
                exclude-result-prefixes="xsl wix">

  <xsl:output method="xml" indent="yes" omit-xml-declaration="yes" />

  <xsl:strip-space elements="*"/>

  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <!-- Get the right sourceDir for the files -->
  <xsl:template match="wix:File/@Source">
    <xsl:attribute name="{name()}">
        <xsl:variable name="sourceDirStart" select="substring-before(., '\')"/>
        <xsl:variable name="sourceDirEnd" select="substring-after(., '\')" />
        <xsl:value-of select="concat($sourceDirStart, '\SomeModule\', $sourceDirEnd)" />
    </xsl:attribute>
  </xsl:template>  

</xsl:stylesheet>

And this should take your SourceDir\file1 and split into SourceDir and file1 then you are concatenating it back together with \SomeModule\ so it becomes SourceDir\SomeModule\file1.

I'm not super versed in xslt but it looks like this should do what you want. Unfortunately heat.exe uses the 1.0 version of xslt so you don't have access to the nice replace function and instead have to do it this awkward way. You'll also have to define a new xslt file for each Mod you have and you apply the transform by supplying heat with the file using -t.

You can also just omit the SourceDir\ if you want by replacing the '\' part of the substring-after with 'SourceDir\' and then removing the first sourceDirStart and then just make the concat concat('expected path', $sourceDirEnd)