3

I have a ASP.NET core app for which I am creating a WIX installer. I am using Heat to generate all the files:

<!-- Remove read-only attribute -->
<Exec Command="attrib -R %(ProjectReference.Filename).wxs" Condition="'%(ProjectReference.WebProject)'=='True'" />

<ItemGroup>
  <LinkerBindInputPaths Include="%(ProjectReference.RootDir)%(ProjectReference.Directory)bin\publish\" />
</ItemGroup>

<!-- Generate a WiX installer file using Heat Tool -->
<HeatDirectory OutputFile="%(ProjectReference.Filename).wxs"
               Directory="%(ProjectReference.RootDir)%(ProjectReference.Directory)bin\publish\"
               DirectoryRefId="INSTALLFOLDER"
               ComponentGroupName="%(ProjectReference.Filename)"
               AutogenerateGuids="True"
               SuppressCom="True"
               SuppressFragments="True"
               SuppressRegistry="True"
               ToolPath="$(WixToolPath)"
               Condition="'%(ProjectReference.WebProject)'=='True'" />

Which puts entries like the following in my WebApp.sxs file:

<Component Id="cmp64BF6D207C595218157C321E631ED310" Guid="*">
    <File Id="myExe" KeyPath="yes" Source="SourceDir\MyExe.exe" />
</Component>

Issue is, I amended the Id attribute, so that I could bind to the version in Product.wxs:

  <Product Id="*"
           Name="..."
           Manufacturer="..."
           Version="!(bind.fileVersion.myExe)"
           Language="1033"
           UpgradeCode="143521a5-99df-4594-9d71-b028cddb9ed8">

How can I make it so that heat keeps the same Id for this file? Yet at the same time add any new files?

Colton Scottie
  • 807
  • 1
  • 8
  • 22
  • [Please check this link for now](https://stackoverflow.com/a/55977336/129130). – Stein Åsmul Jul 23 '19 at 12:12
  • You could remove or modify the component of "MyExe.exe" by applying a XSLT (`HeatDirectory Transforms="MyTransform.xslt"`, an example of XSLT is given in [this answer](https://stackoverflow.com/a/44766600/7571258)). – zett42 Jul 23 '19 at 18:35
  • I think you're doing it backwards. Instead of replacing the ID generated by heat, replace 'myExe' in your .wxs file with the ID generated by heat. Heat will generate the same ID every time. – john k Jul 06 '21 at 18:31

2 Answers2

1

The basic idea is to filter out the component of "MyExe.exe" from the Heat output, then manually add it back to the main WiX authoring, where we can specify a constant File/@Id attribute.

The filtering can be done by passing a XSLT file to Heat:

<HeatDirectory Transforms="$(ProjectDir)RemoveMyExeComponent.xslt" ... />

RemoveMyExeComponent.xslt could look as follows (idea from this answer, but simplified for current question). In this file, replace MyExe.exe with the actual name of your EXE and replace - 8 by the length of the file name, decremented by one.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
    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"
    version="1.0" 
    exclude-result-prefixes="xsl wix">

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

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

    <!--
    Find the component of the main EXE and tag it with the "ExeToRemove" key.

    Because WiX's Heat.exe only supports XSLT 1.0 and not XSLT 2.0 we cannot use `ends-with( haystack, needle )` (e.g. `ends-with( wix:File/@Source, '.exe' )`...
    ...but we can use this longer `substring` expression instead (see https://github.com/wixtoolset/issues/issues/5609 )
    -->
    <xsl:key
        name="ExeToRemove"
        match="wix:Component[ substring( wix:File/@Source, string-length( wix:File/@Source ) - 8 ) = 'MyExe.exe' ]"
        use="@Id"
    /> <!-- In this expression "-8" is the length of "MyExe.exe" - 1 because XSLT uses 1-based indexes, not 0-based indexes. -->

    <!-- By default, copy all elements and nodes into the output... -->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" />
        </xsl:copy>
    </xsl:template>

    <!-- ...but if the element has the "ExeToRemove" key then don't render anything (i.e. removing it from the output) -->
    <xsl:template match="*[ self::wix:Component or self::wix:ComponentRef ][ key( 'ExeToRemove', @Id ) ]" />

</xsl:stylesheet>

Now that we have removed the generated EXE component, we can manually add it back to the WiX authoring, giving us full control over all attributes:

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  <Product Id="*" Name="WixHeatConstantExeFileId" Language="1033" Version="!(bind.fileVersion.myExe)" Manufacturer="zett42" UpgradeCode="PUT-GUID-HERE">
    <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />

    <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
    <MediaTemplate />

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

    <Directory Id="TARGETDIR" Name="SourceDir">
      <Directory Id="ProgramFilesFolder">
        <Directory Id="INSTALLFOLDER" Name="WixHeatConstantExeFileId" />
      </Directory>
    </Directory>

    <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
      <!-- Manually add back the EXE component that was filtered out from Heat output. -->
      <Component Id="myExe" Guid="*">
        <File Id="myExe" KeyPath="yes" Source="SourceDir\MyExe.exe" />
      </Component>
    </ComponentGroup>
  </Product>
</Wix>

Works like a charm in my test project!

zett42
  • 25,437
  • 3
  • 35
  • 72
0

Use SuppressRootDirectory="true" (-srd option) in Heat. That way Heat ignores the full path and just uses the file name to generate IDs. As long as the filename is the same, the ID will always be the same.

Then instead of replacing the ID generated by heat, replace 'myExe' in your .wxs file with the ID generated by heat.

See Wix Harvest: Same Component/File ID when files are in different folders

john k
  • 6,268
  • 4
  • 55
  • 59