Move from manual component creation to harvesting

2019-08-30 08:32发布

问题:

I am using WiX 3.8 to build a project which installs a product. The installer is basically done. One of the programmes which gets installed by the package is relying on dlls by a third party which get updated frequently. Idiotically the version number of the dlls change on a regular basis. When first writing the installer project I did not consider inculding support for changing file names and wrote every component manually.

This behaviour should be changed in the future. If I understand correctly, generating components for files automatically can be done with the help of the HeatDirectory Task. I now have created an example project using the HeatDirectory Task which is working. But there is some discrepancy in the output the HeatDirectory Task produces and the manually authored components I have been using in the past.

I would like the HeatDirectory Task to produce the same output as my manual approach, as good as possible. Following is the code of two components, first created manually and secondly created by the HeatDirectory Task:

Manually created components:

<ComponentGroup Id="ThirdParty.v13.2" Directory="INSTALLFOLDER">
<Component 
  Id="CMP_ThirdParty.v13.2.dll" 
  Guid="AC5E00F0-B458-4272-B132-F13594ED4916">
  <File 
    Id="ThirdParty.v13.2.dll" 
    Name="ThirdParty.v13.2.dll" 
    Source="ComponentsDir\ThirdParty\ThirdParty.v13.2.dll" 
    KeyPath="yes" 
    Assembly=".net" 
    AssemblyApplication="ThirdParty.v13.2.dll" 
    AssemblyManifest="ThirdParty.v13.2.dll" 
    Compressed="no" 
    DiskId="$(var.ThirdPartyDiskId)"/>
</Component>
<Component 
  Id="CMP_ThirdParty.v13.2.xml" 
  Guid="64AC3F5F-38E9-41EC-B714-636F5D9C0CB4">
  <File 
    Id="ThirdParty.v13.2.xml" 
    Name="ThirdParty.v13.2.xml" 
    Source="Source="ComponentsDir\ThirdParty\ThirdParty.v13.2.xml" 
    KeyPath="yes" 
    Compressed="no" 
    DiskId="$(var.ThirdPartyDiskId)"/>
</Component>
</ComponentGroup>

HeatDirectory Task generated code:

<ComponentGroup Id="Files">
<Component 
  Id="cmp9D064A733360960E07277CFD9AB84AF1" 
  Directory="INSTALLFOLDER" 
  Guid="*">
  <File 
    Id="filD5DCB6E091D2D12303E2E80B0B767438" 
    KeyPath="yes" 
    Source="$(var.Path)\ThirdParty.v13.2.dll"/>
</Component>
<Component 
  Id="cmpA8681A63A8A4991D18824BA17E4CA4BF" 
  Directory="INSTALLFOLDER" 
  Guid="*">
  <File 
    Id="fil17554B3CD0E576337AEC758831009938" 
    KeyPath="yes" 
    Source="$(var.Path)\ThirdParty.v13.2.xml"/>
</Component>
</ComponentGroup>

The code producing the output above is following:

<Target Name="BeforeBuild">
<HeatDirectory 
  DirectoryRefId="INSTALLFOLDER" 
  OutputFile="Files.wxs" 
  Directory="S:\omePath" 
  SuppressRootDirectory="true" 
  ToolPath="$(WixToolPath)" 
  AutogenerateGuids="true" 
  ComponentGroupName="Files" 
  PreprocessorVariable="var.Path">
</HeatDirectory>
</Target>

I'll now note down the characteristics of the HeatDirectory Task generated code I'd like to change:

  1. Every component in the componentgroup has a Directory attribute. I want the parent ComponentGroup to have the Directory attribute and omit it in every child component.
  2. I want static guids.
  3. I want the component's Id attribute to be composed of the prefix CMP followed by the file name. I understand that there cannot be two files having the same file name in the project but I know this is not the case. I don't want the cryptic identifier generated by the task.
  4. The File child item of the component is too spartanic. I want the HeatDirectoy Task to create a Name attribute for every file which is the current name of the file. Then the Compressed attribute should be added with the value no and the DiksId should be added with a variable value that can be specified in the task somehow.
  5. If the harvested file is a dll, the task should append the attributes Asssembly with the value .net, AssemblyApplication with the name of the harvested file as its value & AssemblyManifest also with the name of the harvested file as its value.

Is it possible to achieve this with the HeatDirectory Task?

回答1:

The Answer to manipulating output of the HeatDirectory Task is to use XSLT. In the HeatDirectory task an attribute called Transforms can be specified which points to a file containing XSLT instructions. To achieve the output I was asking for, the following XSLT code can be used:

<?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:msxsl="urn:schemas-microsoft-com:xslt"
  exclude-result-prefixes="msxsl">

  <xsl:output
    method="xml"
    indent="yes"/>
  <xsl:variable name="ComponentGroup-Id" select="//wix:ComponentGroup/@Id"/>
  <xsl:variable name="DestinationFolder" select="//wix:Component[1]/@Directory"/>
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select ="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="//wix:Directory">
    <xsl:variable name="DirName" select="@Name" />
    <xsl:copy>
      <xsl:attribute name="Id">
        <xsl:value-of select="$ComponentGroup-Id"/>
        <xsl:text>_</xsl:text>
        <xsl:value-of select="$DirName"/>
      </xsl:attribute>
      <xsl:attribute name="Name">
        <xsl:value-of select="$DirName"/>
      </xsl:attribute>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="//wix:ComponentGroup">
    <xsl:copy>
      <xsl:attribute name="Id">
        <xsl:value-of select="$ComponentGroup-Id"/>
      </xsl:attribute>
      <xsl:attribute name="Directory">
        <xsl:value-of select="$DestinationFolder"/>
      </xsl:attribute>
      <xsl:apply-templates />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="//wix:Component">
    <xsl:variable name="FilePath" select="current()/wix:File/@Source" />
    <xsl:variable name="FileName" select="substring-after($FilePath,'\')" />
    <xsl:variable name="Guid" select="@Guid" />
    <xsl:copy>
      <xsl:attribute name="Id">
        <xsl:text>CMP_</xsl:text>
        <xsl:choose>
          <xsl:when test="contains($FileName,'\')">
            <xsl:value-of select="substring-after($FileName,'\')"/>
          </xsl:when>
          <xsl:otherwise>
            <xsl:value-of select="$FileName"/>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:attribute>
      <xsl:attribute name="Guid">
        <xsl:value-of select="$Guid"/>
      </xsl:attribute>
      <xsl:apply-templates />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="//wix:File">
    <xsl:variable name="FilePath" select="@Source" />
    <xsl:variable name="FileName" select="substring-after($FilePath,'\')" />
    <xsl:copy>
      <xsl:attribute name="Id">
        <xsl:choose>
          <xsl:when test="contains($FileName,'\')">
            <xsl:value-of select="substring-after($FileName,'\')"/>
          </xsl:when>
          <xsl:otherwise>
            <xsl:value-of select="$FileName"/>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:attribute>
      <xsl:attribute name="KeyPath">
        <xsl:text>yes</xsl:text>
      </xsl:attribute>
      <xsl:attribute name="Source">
        <xsl:value-of select="$FilePath"/>
      </xsl:attribute>
      <xsl:if test="contains($FileName,'.dll')">
        <xsl:attribute name="Assembly">.net</xsl:attribute>
        <xsl:attribute name="AssemblyApplication">
          <xsl:choose>
            <xsl:when test="contains($FileName,'\')">
              <xsl:value-of select="substring-after($FileName,'\')"/>
            </xsl:when>
            <xsl:otherwise>
              <xsl:value-of select="$FileName"/>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:attribute>
        <xsl:attribute name="AssemblyManifest">
          <xsl:choose>
            <xsl:when test="contains($FileName,'\')">
              <xsl:value-of select="substring-after($FileName,'\')"/>
            </xsl:when>
            <xsl:otherwise>
              <xsl:value-of select="$FileName"/>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:attribute>
      </xsl:if>
      <xsl:attribute name="Compressed">
        <xsl:text>no</xsl:text>
      </xsl:attribute>
      <xsl:attribute name="DiskId">
        <xsl:text>$(var.SomeDiskID)</xsl:text>
      </xsl:attribute>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>