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

时间:2015-09-08 15:20:33

标签: c++ windows-installer installshield

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

卸载期间出现问题。 CreateFSRegistryLink似乎在非回滚模式下再次运行。从MSI日志中我可以看到它在安装期间正在运行,但它也在 uninstall 期间运行:

我用以下方式检查模式:

if (!MsiGetMode(hInstall, MSIRUNMODE_ROLLBACK))

当条件为真时,我记录一条消息," CreateFSRegistryLink以非回滚模式运行。"如果为false,我会记录一条消息," CreateFSRegistryLink正在回滚模式下运行,因此被跳过了。"我从未见过第二条消息显示在日志中。

我已经CreateFSRegistryLink设置了In-Script执行"系统上下文中的延迟执行。"我还设置了另一个自定义操作DeleteFSRegistryLink,在系统上下文中使用In-Script执行"回滚执行"。我看到它在安装过程中被跳过,但在卸载期间没有(我怀疑它在卸载期间正常运行,但没有添加日志记录来确认这一点)。

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

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

我还没有添加日志记录到这个功能,所以我不知道它是否运行,但是当卸载完成后,注册表中的链接仍然存在。这并不奇怪,因为在此之后我立即看到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.

我遵循&#34; https://msdn.microsoft.com/en-us/library/aa371369(v=vs.85).aspx的规则;回滚自定义操作必须始终在它在动作序列中回滚的延迟自定义操作之前#34;看到这个日志输出和结果,这对我来说真的没有意义。我想我在这里错过了一些关键点。

2 个答案:

答案 0 :(得分:1)

这是我要求的阅读&#39; 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. RollReFSRegistryLink在CountOtherFSSystems之后的系统上下文中使用回滚执行运行,条件为FS_SystemCount<1 And $FSRegistry = 3(当FSRegistry组件在本地安装时)。它调用该函数来删除注册表链接。
  3. CreateFSRegistryLink在条件$FSRegistry=3下的RollbackFSRegistryLink之后在系统上下文中使用延迟执行运行。它调用函数来创建注册表链接。
  4. 序列中的一堆其他函数执行,然后我们进入标准操作WriteRegistryValues。
  5. RollbackDeleteFSRegistryLink在条件$FSRegistry<>3之后的WriteRegistryValues之后在系统上下文中使用回滚执行运行(当删除FSRegistry组件时,但回滚需要将其放回)。它调用函数来创建注册表链接。
  6. DeleteFSRegistryLink在条件FS_SystemCount<1 AND $FSRegistry <> 3之后的RollbackDeleteFSRegistryLink之后在系统上下文中使用延迟执行运行。它调用该函数来删除注册表链接。
  7. 在DeleteFSRegistryLink之后,TestError在系统上下文中使用延迟执行运行。它调用一个只返回错误条件的测试函数,如果用户说它可以(通过MSIProcessMessage)在这里引入错误用于测试目的。 (生成版本将禁用此功能。)
  8. 我测试了以下情况:

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

    如果你看到我错过的任何内容,请发表评论。这对我来说似乎很全面。

答案 1 :(得分:1)

投入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将起作用。

因此,如果您仍然遇到困难,可能有助于发布您的CA定义,特别是条件和类型。