如何让 Wix Heat.exe 保留自定义文件 ID?

Posted

技术标签:

【中文标题】如何让 Wix Heat.exe 保留自定义文件 ID?【英文标题】:How to make Wix Heat.exe retain custom file id? 【发布时间】:2019-07-23 11:21:38 【问题描述】:

我有一个 ASP.NET 核心应用程序,我正在为其创建 WIX 安装程序。我正在使用 Heat 生成所有文件:

<!-- 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'" />

在我的WebApp.sxs 文件中放入如下条目:

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

问题是,我修改了Id属性,这样我就可以绑定到Product.wxs中的版本了:

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

我怎样才能让 heat 为这个文件保持相同的 ID?但同时添加任何新文件?

【问题讨论】:

Please check this link for now. 您可以通过应用 XSLT(HeatDirectory Transforms="MyTransform.xslt",this answer 中给出 XSLT 的示例)来删除或修改“MyExe.exe”的组件。 我认为你在倒退。不要替换 heat 生成的 ID,而是将 .wxs 文件中的“myExe”替换为 heat 生成的 ID。 Heat 每次都会生成相同的 ID。 【参考方案1】:

基本思路是从 Heat 输出中过滤掉“MyExe.exe”的组件,然后手动将其添加回主 WiX 创作,我们可以在其中指定常量File/@Id 属性。

可以通过将 XSLT 文件传递​​给 Heat 来完成过滤:

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

RemoveMyExeComponent.xslt 可能如下所示(来自this answer 的想法,但针对当前问题进行了简化)。在此文件中,将MyExe.exe 替换为您的EXE 的实际名称,并将- 8 替换为文件名的长度,减一。

<?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>

现在我们已经删除了生成的 EXE 组件,我们可以手动将其添加回 WiX 创作,让我们完全控制所有属性:

<?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>

在我的测试项目中就像一个魅力!

【讨论】:

【参考方案2】:

在 Heat 中使用 SuppressRootDirectory="true"(-srd 选项)。这样 Heat 会忽略完整路径,只使用文件名来生成 ID。只要文件名相同,ID就永远相同。

然后,不要替换 heat 生成的 ID,而是将 .wxs 文件中的“myExe”替换为 heat 生成的 ID。

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

【讨论】:

以上是关于如何让 Wix Heat.exe 保留自定义文件 ID?的主要内容,如果未能解决你的问题,请参考以下文章

WiX 安装程序:使用 xslt 和 heat.exe 如何在找到父/子匹配后更改父 ID 的值?

Wix Installer Heat.exe 错误 参数“exePath”无效

WiX 安装程序:将 xslt 与 heat.exe 一起使用时,如何在还使用匹配属性时找到匹配的子字符串?

如何在 WiX 安装程序中将应用程序添加为防火墙例外

wix heat 使用 xsl 文件移除命名空间

蜡烛.exe 访问被拒绝