卸载与回滚期间的 MSI 自定义操作

MSI custom actions during uninstall vs rollback

我很难理解如何正确安装和卸载自定义操作,以及回滚的目的是什么。我有一个名为 CreateFSRegistryLink 的自定义操作,它创建了一个 REG_LINK 注册表项(AFAIK 不能直接由 MSI/InstallShield 创建)。我想我在大多数情况下都正确地使用了 运行ning,因为如果 link 已经存在,它只是 returns ERROR_FUNCTION_NOT_CALLED,MSI 似乎可以优雅地处理它,继续安装的其余部分。这确保可以干净地安装产品的多个实例(我们有一个多实例产品)。

问题出现在卸载过程中。 CreateFSRegistryLink 似乎在非回滚模式下再次 运行ning。从 MSI 日志中,我可以看到它在 install 期间是 运行ning,但在 uninstall[] 期间也是 运行s =46=]:

我正在检查模式:

if (!MsiGetMode(hInstall, MSIRUNMODE_ROLLBACK))

当条件为真时,我记录一条消息,"CreateFSRegistryLink is running in non-rollback mode."当条件为假时,我记录一条消息,"CreateFSRegistryLink is running in rollback mode, so was skipped."我从未在日志中看到第二条消息。

我已经 CreateFSRegistryLink 设置了脚本内执行 "Deferred Execution in System Context." 我还有另一个自定义操作 DeleteFSRegistryLink 设置了脚本内执行 "Rollback Execution in System Context"。我看到它在安装过程中被跳过,但在卸载过程中没有(我怀疑它在卸载过程中正常 运行ning,但没有添加日志记录来确认这一点)。

我还有一个自定义操作 CountOtherFSSystems,它将 FS_SystemCount 设置为除当前实例之外的系统(实例)数量。我将 DeleteFSRegisryLink 设置为在 Exec 序列中 FS_SystemCount<1 时只有 运行 的条件。这就是我如何知道它在安装过程中被跳过,因为 MSI 报告不满足条件,因此 DeleteFSRegistryLink 被跳过。我希望这有助于确保在卸载最后一个实例时仅 运行s。我认为这种情况基于日志输出是有效的,但我不知道如何在卸载期间将此 DeleteFSRegistryLink 自定义操作正确地设置为 运行 而无需重新安装 CreateFSRegistryLink 操作link。我在日志中看到的对 DeleteFSRegistryLink 的最后引用是:

MSI (s) (08:CC) [09:42:23:708]: Executing op: CustomActionSchedule(Action=DeleteFSRegistryLink,ActionType=3329,Source=BinaryData,Target=DeleteFSRegistryLink,)

我还没有给这个功能添加日志记录,所以我不知道是否运行,但是当卸载完成后,注册表中的link仍然存在那里。这并不完全令人惊讶,因为紧接着我又看到 CreateFSRegistryLink 运行:

MSI (s) (08:CC) [09:42:23:708]: Executing op: ActionStart(Name=CreateFSRegistryLink,,)
Action 9:42:23: CreateFSRegistryLink. 
MSI (s) (08:CC) [09:42:23:708]: Executing op: CustomActionSchedule(Action=CreateFSRegistryLink,ActionType=3073,Source=BinaryData,Target=CreateFSRegistryLink,)
MSI (s) (08:0C) [09:42:23:739]: Invoking remote custom action. DLL: C:\windows\Installer\MSI37E1.tmp, Entrypoint: CreateFSRegistryLink
MSI (s) (08:70) [09:42:23:739]: Generating random cookie.
MSI (s) (08:70) [09:42:23:739]: Created Custom Action Server with PID 7640 (0x1DD8).
MSI (s) (08:18) [09:42:23:786]: Running as a service.
MSI (s) (08:18) [09:42:23:786]: Hello, I'm your 32bit Elevated custom action server.
CreateFSRegistryLink is running in non-rollback mode.

我遵循了 "A rollback custom action must always precede the deferred custom action it rolls back in the action sequence" 的 https://msdn.microsoft.com/en-us/library/aa371369(v=vs.85).aspx 中的规则,看到此日志输出和结果对我来说仍然真的没有意义。我想我在这里遗漏了一些关键点。

这是我的 'required reading' MSI 列表,也是一个不错的起点:

Installation Phases and In-Script Execution Options for Custom Actions in Windows Installer

想法是 MSI 所做的每个更改都应该是事务性的。在安装、升级、修复或卸载过程中,您应该能够在失败时回滚状态更改。

有时您会遇到 API 这是不可能的。例如删除用户帐户或与旧的 IIS 元数据库交互 API。如果 API 不支持 .commit() .rollback() 功能,那么您必须在提交阶段执行时进行更改。考虑到可以通过禁用回滚来禁用提交阶段,您必须在这些情况下尽早进行。

多读几遍白皮书,稍微消化一下,然后继续解决您仍有的其他问题。

编辑: 这就是我最终设置自定义操作的方式:

  1. CountOtherFSSystems 在任何情况下都在 InstallInitialize 后立即执行以将 FS_SystemCount 设置为已安装的其他实例的数量。
  2. RollbackFSRegistryLink 在 CountOtherFSSystems 之后的条件 FS_SystemCount<1 And $FSRegistry = 3 下在系统上下文中使用回滚执行运行(当 FSRegistry 组件正在本地安装时)。调用函数删除注册表link.
  3. CreateFSRegistryLink 在条件 $FSRegistry=3 下在 RollbackFSRegistryLink 之后在系统上下文中延迟执行运行。它调用创建注册表的函数 link.
  4. 执行序列中的一堆其他函数,然后我们到达标准操作 WriteRegistryValues。
  5. RollbackDeleteFSRegistryLink 在条件 $FSRegistry<>3 下 WriteRegistryValues 之后在系统上下文中运行回滚执行(当 FSRegistry 组件被删除时,但回滚需要将其放回原处)。它调用创建注册表的函数 link.
  6. DeleteFSRegistryLink 在条件 FS_SystemCount<1 AND $FSRegistry <> 3 下在 RollbackDeleteFSRegistryLink 之后在系统上下文中延迟执行运行。调用函数删除注册表link.
  7. TestError 在 DeleteFSRegistryLink 之后在系统上下文中以延迟执行运行。如果用户表示可以(通过 MSIProcessMessage)在此处引入用于测试目的的错误,它会调用一个测试函数,该函数只是 returns 一个错误条件。 (生产版本将禁用此功能。)

我测试了以下情况:

  • 安装第一个实例时出错 - 没有创建注册表项或 link。
  • 安装第二个实例时出错 - 只保留第一个实例的注册表项,link 也保留。
  • 卸载第二个实例时出错 - 两个实例和 link 都保留在注册表中。
  • 卸载最后一个剩余实例时出错 - 虽然错误仍然显示(在回滚发生之前),但我可以看到注册表项和 link 都消失了,在继续之后,我看到回滚已恢复注册表项和 link.
  • 成功卸载第二个实例 - link 并且保留第一个实例的注册表项。
  • 成功卸载最后一个剩余实例 - link 并删除所有注册表项。

如果您看到我在这里遗漏的任何内容,请发表评论。对我来说似乎很全面。

投入我的 2 美分:这个问题似乎与自定义操作条件有关。关于您调用 MsiGetMode 以查看是否回滚,何必呢?您在实际的延迟 CA 之前对回滚自定义操作进行排序,根据定义,只有在调用原始自定义操作时才会调用它,并且它被定义为回滚 CA,并且不需要任何条件。您的卸载 CA 可能与回滚 CA 相同,但严格来说,卸载 CA 可以假定其对应的安装 CA 如果编码正确并且故障导致安装失败,则可以正常工作,而回滚 CA 可能需要假设安装 CA 可能只部分工作并且需要检查更多系统状态。

如果在卸载时调用了 CreateRegistryFSLink,那么您在该 CA 上的条件不正确。

如果您的代码做了或没有做某事,那么由您记住它做了什么,回滚 CA 会撤消它。

其余部分似乎与您的自定义操作的条件有关。如果您希望仅在产品卸载时调用,请使用 REMOVE="ALL"。如果您有专门与功能或组件卸载相关的 CA,那么(正如 Chris 所说)使用组件或功能条件,它们在这里:

https://msdn.microsoft.com/en-us/library/aa368012(v=vs.85).aspx

如果您希望卸载 CA 以 运行 只要产品未升级,则 REMOVE="ALL" 和 NOT UPGRADINGPRODUCTCODE 将起作用。

因此,如果您仍然遇到困难,post 您对 CA 的定义可能会有所帮助,尤其是条件和类型。