卸载与回滚期间的 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() 功能,那么您必须在提交阶段执行时进行更改。考虑到可以通过禁用回滚来禁用提交阶段,您必须在这些情况下尽早进行。
多读几遍白皮书,稍微消化一下,然后继续解决您仍有的其他问题。
编辑: 这就是我最终设置自定义操作的方式:
- CountOtherFSSystems 在任何情况下都在 InstallInitialize 后立即执行以将 FS_SystemCount 设置为已安装的其他实例的数量。
- RollbackFSRegistryLink 在 CountOtherFSSystems 之后的条件
FS_SystemCount<1 And $FSRegistry = 3
下在系统上下文中使用回滚执行运行(当 FSRegistry 组件正在本地安装时)。调用函数删除注册表link.
- CreateFSRegistryLink 在条件
$FSRegistry=3
下在 RollbackFSRegistryLink 之后在系统上下文中延迟执行运行。它调用创建注册表的函数 link.
- 执行序列中的一堆其他函数,然后我们到达标准操作 WriteRegistryValues。
- RollbackDeleteFSRegistryLink 在条件
$FSRegistry<>3
下 WriteRegistryValues 之后在系统上下文中运行回滚执行(当 FSRegistry 组件被删除时,但回滚需要将其放回原处)。它调用创建注册表的函数 link.
- DeleteFSRegistryLink 在条件
FS_SystemCount<1 AND $FSRegistry <> 3
下在 RollbackDeleteFSRegistryLink 之后在系统上下文中延迟执行运行。调用函数删除注册表link.
- 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 的定义可能会有所帮助,尤其是条件和类型。
我很难理解如何正确安装和卸载自定义操作,以及回滚的目的是什么。我有一个名为 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() 功能,那么您必须在提交阶段执行时进行更改。考虑到可以通过禁用回滚来禁用提交阶段,您必须在这些情况下尽早进行。
多读几遍白皮书,稍微消化一下,然后继续解决您仍有的其他问题。
编辑: 这就是我最终设置自定义操作的方式:
- CountOtherFSSystems 在任何情况下都在 InstallInitialize 后立即执行以将 FS_SystemCount 设置为已安装的其他实例的数量。
- RollbackFSRegistryLink 在 CountOtherFSSystems 之后的条件
FS_SystemCount<1 And $FSRegistry = 3
下在系统上下文中使用回滚执行运行(当 FSRegistry 组件正在本地安装时)。调用函数删除注册表link. - CreateFSRegistryLink 在条件
$FSRegistry=3
下在 RollbackFSRegistryLink 之后在系统上下文中延迟执行运行。它调用创建注册表的函数 link. - 执行序列中的一堆其他函数,然后我们到达标准操作 WriteRegistryValues。
- RollbackDeleteFSRegistryLink 在条件
$FSRegistry<>3
下 WriteRegistryValues 之后在系统上下文中运行回滚执行(当 FSRegistry 组件被删除时,但回滚需要将其放回原处)。它调用创建注册表的函数 link. - DeleteFSRegistryLink 在条件
FS_SystemCount<1 AND $FSRegistry <> 3
下在 RollbackDeleteFSRegistryLink 之后在系统上下文中延迟执行运行。调用函数删除注册表link. - 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 的定义可能会有所帮助,尤其是条件和类型。