自定义安装操作
许多游戏使用防欺骗和其他中间件。 这些组件在用户安装游戏时通常采用链式安装。 一个用户可以通过 Microsoft Store 安装一个游戏,并通过某个其他分发渠道安装另一个游戏。 平台确保这两个游戏在链式安装同一中间件包时不会发生冲突。 游戏可以在其包中包含一个或多个任意 .exe 或 .msi 文件,并要求将这些文件作为完成应用安装的一部分来运行。 自定义安装操作功能旨在安装游戏防欺骗软件或其他一些可再发行的中间件。 通过这种方式,您可以在提交到 Microsoft Store 的 MSIX 中以及通过其他分发渠道提交的 .exe 或 .msi 文件中使用确切的同一个链式安装程序 .exe 文件。
在传统的 Win32 应用中,共享的中间件通常通过独立的可再发行文件进行安装,这通常采用自解压 .exe 或可与游戏本身捆绑的 .msi 的形式。 Microsoft Store 应用将依赖项建模为框架包,这些包不与游戏捆绑但通过 Microsoft Store 单独部署。 在此模型中,使用方应用清单声明对一个或多个框架包的依赖项。 然后,Microsoft Store 负责将各依赖项的安装链接起来。 尽管许多可再发行文件可用作 Microsoft Store 中的框架包,但不可避免有些可再发行文件不可用作框架包。 对于这些包,解决办法是允许游戏在游戏包中捆绑可再发行文件。
为了包括 .exe 或 .msi 可再发行文件,您需要对 MicrosoftGame.config 文件进行更新。 这些更改包括声明您想要使用的自定义操作的类型及其在安装包中的位置。
任何可再发行文件都必须包含在包中,并在 MicrosoftGame.config 中声明。声明包含指向可执行文件的路径(相对于声明的文件夹路径,进而相对于包本身的根路径)。 声明还包括运行可执行文件时将传递到此可执行文件的任何命令行参数。 这是通过在 MicrosoftGame.config 文件中包含 CustomInstallActions 元素来实现的。
CustomInstallActions
对于要运行哪些自定义安装操作和何时运行这些操作,CustomInstallActions 元素包含关于它们的所有定义。 您只能在清单中声明此扩展的一个实例。 此扩展具有一个名为 CustomInstall 的顶级子级。 它具有一个必需的属性 Folder,这是一个字符串,用于指定包含所有自定义操作需要的所有文件的文件夹。 该文件夹可能包含子文件夹。 您负责确保包中包括任何自定义操作可执行文件的任何依赖项,并且它们位于各自对应的加载路径中。
注意
不得将任何主游戏可执行文件或其他文件放入指定的文件夹中。 它仅明确用于自定义安装文件。
在自定义安装扩展中,应用可能声明 InstallAction、RepairAction 和 UninstallAction 的子元素。 所有这些都是可选的:您可以声明其中任何一个或者一个也不声明。 在其中的每个元素内,您可以指定一个或多个 InstallAction、RepairAction 或 UninstallAction 子项。 平台将按照您指定的顺序,使用您指定的命令行参数,运行您指定的操作。
操作类型
自定义安装扩展的三个子节点规定了何时运行某些自定义操作。 有三种类型的自定义安装操作。
- 安装操作:平台在第一次启用应用之前运行的操作。
- 修复操作:用户选择“修复”或“重置”时运行的操作。
- 卸载操作:用户卸载应用时运行的操作
通常,您可以指定将某些可再发行文件作为 InstallAction 进行链式安装,然后指定将其作为 UninstallAction 进行卸载。 但在某些情况下,可以选择安装而不带匹配的卸载。 在这种情况下,应用会安装在卸载它时留下的一些内容,这可能适用于由其他应用共享的可再发行文件。 同样,RepairAction 可能只是重新声明您为 InstallAction 指定的可执行文件,且这是典型的操作。 可能您要在安装/维修/卸载中执行的每个操作都需要不同的可执行文件。 架构非常灵活:平台不需要任何操作。 您可随意配置适合每个游戏的行为。
重要
仅当已运行安装或修复操作时,才会运行"卸载"操作。 系统使用名称属性跟踪此状态。 因此,安装/修复/卸载操作必须具有相同的名称。
操作的组成部分
文件
对于每个操作,您必须指定要运行的文件,并且该文件必须位于包中。 如果您指定路径,它将隐式相对于 CustomInstallActionsFolder 路径。 不能指定绝对路径。 路径不得以反斜杠 (\) 开头。
名称
必须为操作指定名称。 此名称在父操作节点中必须是唯一的,但可以跨不同的“操作”节点共享。 例如,可以指定 File="MySetup.exe" 和 Name="abc123" 作为 InstallAction 和 RepairAction。 另一方面,如果您有两个 InstallAction 元素,每个元素都必须具有不同的名称。 您应跨包版本对同一可执行文件使用相同的名称(只要该可执行文件不发生变化)。 名称用作操作的标识,并允许平台跟踪已成功运行哪些操作,以及是否需要对更新的包运行这些操作。 如果更新的包使用名称指定已成功运行的自定义操作,则平台在更新时会跳过此操作。
重要
参数列表的差异不会构成标识的差异。 如果您想要在更新的包中使用不同的参数运行同一个可执行文件,则您必须提供不同的名称。 适当地配置声明的名称并跨版本跟踪它们是您的责任。
参数
每个自定义安装操作具有第三个元素参数,这允许您包括运行可再发行命令所需加入的任何参数。
自定义操作使用情况
安装反欺骗软件通常要求用户具有管理员特权,一般情况下,由于自定义操作是一项非常强大的功能,平台需要对具有自定义操作的任何包拥有管理员权限。 对于使用管理员权限运行的操作,Windows 要求在首次运行应用时显示用户帐户控制 (UAC) 提示。 用户工作流如下所示。
- 游戏的 Microsoft Store 页面包括有关这些需求的说明,包括安装是否需要提升的权限、安装是否运行自定义操作,以及这可能对用户意味着什么。 提供此信息,以便用户可以做出有关购买游戏的明智决定。
- 假定用户对约束和影响感到满意,则选择“安装”。
- 平台检测到包中包括自定义操作,并记录需要运行自定义操作的事实。 但是,它不会在初始安装阶段运行自定义操作。 任何自定义操作都是在用户首次启动游戏时运行的。
- 在首次启动游戏时,在平台即将运行自定义操作的时点,它将显示 UAC 提示。 然后,用户需要提供管理员凭据并接受提升。 即使包中包含多项自定义操作,也只向用户提供一个 UAC 提示。 没有任何进一步的 UAC 更新提示,除非已更改了一个或多个自定义操作。 卸载游戏时,会出现 UAC 提示。
所有自定义操作必须返回零以表示成功。 如果任何自定义操作失败,平台会继续运行任何剩余的自定义操作,并且继续尝试启动应用。 在每次后续应用启动时,平台会继续重试任何失败/未完成的安装自定义操作,直至该操作成功。 如果应用面对自定义操作失败无法正常发挥作用,用户始终可以转到应用的“设置”页并选择“修复”或“重置”。 修复/重置不会重新下载游戏文件,只会重新注册包。 任何失败或未执行的自定义操作都要重新注册以运行。 如果您具有对成功返回某个非零值的自定义安装程序,一个选项是将该安装程序包装在另一个您创建的可执行文件中,这确实会对成功返回零。
Microsoft Store 策略围绕着允许链式安装哪些类型的中间件/可再发行软件提供了一些准则。 概括而言,链式安装适用于运行游戏需要的共享软件。 不适用于安装不相关的应用或其他软件。
注意
自定义安装操作仅在主 MSIXVC 包中受支持。 在框架包、可选包、修改包或任何其他类型的包中不支持此类操作。
作为自定义安装操作运行 MSI
对于 MSI,您提供 MSI 的名称,平台会对该文件运行 msiexec.exe。 请不要提供 /i、/f 或 /x 参数,因为这些参数会从操作的类型(安装、修复或卸载)推断出声明位置。 不能向 /f 开关提供任何选项。 但是,您可以提供通常会提供给 msiexec.exe 的任何其他可选参数。 这是命令行参数受约束的唯一情形:对于非 MSI 操作,您可能提供任何您想要的参数。 对于非 MSI 操作,平台不会解析或验证参数。 该平台只是将它们传递到可执行文件。 确保参数正确无误是您的责任。
不直接支持 MST(MSI 转换)。 如果您需要 MSI/MST,则一个可能的解决方法是生成独立的 .exe,用于包装 msiexec 以便运行 MSI 并应用 MST。
游戏配置文件更改
以下 XML 示例演示向 MicrosoftGame.config 文件适当地添加内容以允许自定义安装操作。
<!-- Include CustomInstallActions element. Declare InstallActions,
RepairActions and/or UninstallActions as appropriate for your app. -->
<DesktopRegistration>
<!-- ... -->
<!-- Other entries omitted for brevity. -->
<!-- ... -->
<CustomInstallActions>
<Folder>MyInstallers</Folder>
<InstallActionList>
<InstallAction File="CustomInstaller.exe" Name="TaskName" Arguments="/silent /example" />
</InstallActionList>
<RepairActionList>
<RepairAction File="CustomInstaller.exe" Name="TaskName" Arguments="/silent /repair" />
</RepairActionList>
<UninstallActionList>
<UninstallAction File="CustomInstaller.exe" Name="TaskName" Arguments="/silent /remove" />
</UninstallActionList>
</CustomInstallActions>
</DesktopRegistration>
在上述示例中,所有可执行文件和任何依赖项必须放在包的根目录下由您指定的 MyInstallers 文件夹中。 在该文件夹中,您可以创建适合您的应用的任何子文件夹结构。 在此示例中,MySetup.exe 的路径将是 <package root>\MyInstallers\Banana\MySetup.exe。 如果该可执行文件具有任何依赖项,它们也必须放在相应的文件夹或子文件夹中。
以下顺序介绍您如何编写多个版本的清单。
<!-- v1 of the game. -->
<CustomInstallActions>
<Folder>MyInstallers</Folder>
<InstallActionList>
<InstallAction File="CustomInstaller.exe" Name="TaskName_1" Arguments="/silent /example" />
<!-- The platform records the successful install of TaskName_1. -->
</InstallActionList>
<RepairActionList>
<RepairAction File="CustomInstaller.exe" Name="TaskName_1" Arguments="/silent /repair" />
</RepairActionList>
<UninstallActionList>
<UninstallAction File="CustomInstaller.exe" Name="TaskName_1" Arguments="/silent /remove" />
<!-- The platform records the successful uninstall of TaskName_1. -->
</UninstallActionList>
</CustomInstallActions>
<!-- v2 of the game, where the redist is NOT updated. -->
<CustomInstallActions>
<Folder>MyInstallers</Folder>
<InstallActionList>
<InstallAction File="CustomInstaller.exe" Name="TaskName_1" Arguments="/silent /example" />
<!-- The platform detects that we've already installed TaskName_1, so we don't
run it again. Therefore, there's no UAC prompt. -->
</InstallActionList>
<RepairActionList>
<RepairAction File="CustomInstaller.exe" Name="TaskName_1" Arguments="/silent /repair" />
</RepairActionList>
<UninstallActionList>
<UninstallAction File="CustomInstaller.exe" Name="TaskName_1" Arguments="/silent /remove" />
</UninstallActionList>
</CustomInstallActions>
<!-- v2 of the game, where a redist IS updated. -->
<CustomInstallActions>
<Folder>MyInstallers</Folder>
<InstallActionList>
<InstallAction File="CustomInstaller.exe" Name="TaskName_2" Arguments="/silent /example" />
<!-- The platform detects that we haven't previously run TaskName_2, so we need
to run it this time and show a UAC prompt. -->
</InstallActionList>
<RepairActionList>
<RepairAction File="CustomInstaller.exe" Name="TaskName_1" Arguments="/silent /repair" />
</RepairActionList>
<UninstallActionList>
<UninstallAction File="CustomInstaller.exe" Name="TaskName_1" Arguments="/silent /remove" />
</UninstallActionList>
</CustomInstallActions>