MSI卸载期间文件的删除方式究竟如何?

时间:2018-01-05 15:38:50

标签: wix windows-installer uninstall msdn

我想知道卸载过程中安装的文件/组件究竟发生了什么。

对于安装和升级过程,MSDN中存在可靠的文档(例如,请参阅File Versioning RulesDefault File Versioning)。

无论如何,我在MSDN或WiX的文档中找不到卸载删除逻辑的文档。

所以,我的问题很简单:我想知道何时从系统中删除了一个文件(情况并非总是如此 - 例如,如果该文件存在SharedDLLRefCount,则该文件仍然存在。

我发现的最接近的是以下MSDN link,它给出了一些建议,但基本上说:“自己测试一下”。 这对我来说并不令人满意,因为在我将使用此行为的任何安装程序发送给客户之前,我想知道我是否可以依赖MSI的行为 - 可能是当前行为。

我正在寻找以下问题的可靠答案:

  • 在哪种情况下 - 除了明确的“永久”定义或使用SharedDllRefCount - 文件/组件是否会在卸载操作后继续存在?

  • 如果DLL现在的版本高于安装时的版本(因为热补丁等),它会被安全删除吗?注意:我测试了这个并且当前的答案是肯定的,但是我需要知道这是否是预期的行为以及我是否可以依赖它。

2 个答案:

答案 0 :(得分:3)

时间不多,但我会尝试简要总结一下。 MSI文件的组件引用是基于Windows Installer组件完成的 - 而不是基于此处注册表中找到的旧SharedDLL引用计数:HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\SharedDLLs。奇怪的是,这个SharedDLL ref-counter有时也与MSI一起使用,但这只是为了提供与传统安装程序和部署技术的兼容性 - 我稍后会澄清。传统技术使用此SharedDLL计数器作为确定是否可以卸载文件的唯一方法。一旦ref-count降至0,就可以删除该文件。

Windows Installer的实际引用计数是基于Windows安装程序组件而非共享dll引用计数器完成的。这些组件是"原子安装包"的文件和注册表设置。它总是作为一个整体安装或卸载。组件基本上可以包含"任何",但是在分解要部署到组件集合中的文件和注册表设置时,有关于最佳实践的规则。我个人总是每个组件使用一个文件,因为这可以避免在Windows Installer升级过程中出现各种问题。

基本上每个组件都有一个"关键路径" - 单个文件或注册表项/值,用于确定组件是否已安装。 MSI的总体概念是该绝对组件密钥路径与唯一组件GUID之间存在一对一映射。 GUID本质上引用计数绝对路径。我在几年前的一个答案中对此进行了解释,这对人们来说似乎是一个可理解的解释,或许可以快速阅读一下,以便更详细地理解这个组件: Change my component GUID in wix?

针对该特定绝对磁盘或注册表位置的此组件GUID应由寻求部署相关文件或组件的所有设置使用。 Window Installer允许这种情况的机制被称为"合并模块"。这是一个部分MSI数据库,可以在构建时合并到几个MSI文件中 - 允许在MSI文件之间共享相同的组件,并在所有这些文件中使用正确的组件GUID,以便可以进行引用计数。 这允许这些共享组件在每次由不同产品安装时递增引用计数,然后该组件将保留在系统上,直到ref-count减少到0,因为使用它的产品被卸载序列即可。应该注意的是,如果HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\SharedDLLs处的传统参考计数器不是同时为0,则组件将被卸载。如果组件被设置为永久性或者如果它安装了空白组件GUID(特殊功能和#34;安装并忘记"组件 - 它永远不会再次处理)也不会被卸载。

所以要再次重复,一个绝对路径的一个GUID (一个GUID来统治它们) - 一个GUID有一个关联的ref计数器,它计算已注册组件的产品数量,及其相关的绝对密钥路径,供其使用。因此,作为示例,三个产品可能会注册使用某个组件GUID,使其引用计数为3,从而使其关联的密钥路径文件或注册表值保持不变,直到卸载所有3个产品。

请注意,不一定为MSI组件启用旧版SharedDLL引用计数器。某些工具(例如 Installshield )启用一个标志来增加所有已安装文件的旧版共享DLL引用计数器,实际上您必须为每个组件关闭它以消除此行为。这与其他工具形成对比,例如 WiX ,它不会默认所有文件的共享dll引用计数器(我不确定它们启用了哪些文件 - 如果有的话) )。 高级安装程序也不会为所有组件启用SharedDLL引用计数标志(感谢 Bogdan Mitrache 验证这一点 - 请参阅下面的评论)。

混淆旧版引用计数器 - 在开发和测试安装期间可能发生 - 可能导致应该卸载的Windows Installer组件意外地保留在磁盘上。如果您看到这一点,请检查一个干净的系统,以确定主机上是否存在混乱的传统参考计数器问题。然后,您需要手动调整注册表以修复开发计算机的引用计数。这将涉及此密钥下的所有适用项目:HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\SharedDLLs。这不是一个有趣的工作 - 我在当天使用Installshield Developer 7时遇到过这种情况。

未能为每个绝对密钥路径保留一致的组件GUID将导致神秘和不可预测的问题,例如MSI卸载删除仍与其他产品共享的文件,但引用计数已被搞砸。 MSI文件错误地认为他们拥有"共享组件并愉快地删除它们。错误标识的情况(相同的绝对路径具有指向它的多个组件GUID - 每个引用计数为1)。这是人们使用Windows Installer时遇到的关键问题之一 - 因此建议每个组件坚持使用一个文件。

我现在会发布这个答案来坚持下去,然后稍微调整一下。

更新:让我们具体了解您的具体问题。

  1. 你已经回答了很多问题。如果组件的重新计数(对于组件GUID)在MSI文件减少其注册"之后,该文件将保留在磁盘上。它在卸载时。如果其MSI组件设置为永久性,或者如果它具有空白组件GUID,或者如果为组件启用了旧版SharedDLL引用计数(它可能不是)并且此处的ref-count更高,它也将保留比0:HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\SharedDLLs。这些都是我所了解的条件。我想还有其他方面,比如广告产品,但老实说我不确定它们会如何影响卸载。广告产品并未真正安装,但可安装"由用户按需提供。阅读Phil的回答我还记得transitive components也可以按照他在答案中描述的方式卸载 - 在重新安装过程中将相关条件评估为假。

  2. 是的,只要组件GUID在特定绝对路径的生命周期(文件的完整安装路径)中保持稳定,那么该文件可以经历任意数量的更新,并且参考如果具有其他产品代码的另一个MSI也安装它,则count仅递增。换句话说:如果您已经为原始MSI提供了4次更新,并且您为特定文件维护了一致的组件GUID,并且每次使用新版本更新它,则此文件的组件引用数仍为1 - 只要没有其他MSI也安装了该组件 - 在这种情况下它将是2或更多,并且您的产品的卸载将卸载它,但将ref-count减少1。 / p>

  3. 请尝试阅读此答案,因为它似乎为其他人澄清了一些事情: Change my component GUID in wix? (与上面推荐的相同)。

    最后,我应该注意共享组件也可以通过 WiX包含文件安装 - 这是WiX全新推出的新概念。这些就像C ++中的常规包含文件一样,只需定义一次,它们就可以在编译时包含在几个WiX源文件中。我老实说从来没有使用过这个,但从概念上讲它类似于合并模块 - 内置的Windows Installer概念来处理共享组件。但是有一个重要的区别:合并模块作为一个整体进行版本化,而WiX包含文件包含来自源文件夹的动态文件。我发现这更好,但这肯定是一个大讨论和优先事项。我不会在这里详细说明。

    如果您使用的是WiX,我建议您尝试使用WiX包含文件来管理共享组件。对我来说,他们似乎是一个更灵活的合并模块实现。从主观角度来说,我从未成为合并模块的忠实粉丝,但如果你有很多共享文件可以安装不同的产品,它们是必不可少的。为什么我不喜欢合并模块?它们看起来像是一个额外的复杂功能和需要额外维护的二进制blob。实际上,它们相当于一种奇怪的静态链接形式 - 我们通过常规静态链接知道所有问题。这可能过于主观,所以我将以此说明结束,但对于共享文件和组件,使用合并模块或WiX包含文件或任何其他构造存在以实现我不知道的相同。

答案 1 :(得分:2)

实际上只有一些规则适用,但困难在于它们适用于许多有时复杂的场景。

如果资源(文件)的组件ID引用计数为零并且:

,则将其删除

a)没有剩余的SharedDllRefcount。

b)组件在安装MSI时从未被标记为永久性(因为它使其粘住并且无法关闭)。

c)将组件设置为Transitive,并进行安装/维护操作,将关联的属性值设置为" false"。同样,这对系统来说很粘,而不是项目设置。

d)安装时未将其标记为msidbComponentAttributesShared。

可以在组件上设置永久,传递,组件共享和共享Dll。

如果产品A安装版本1且产品B安装版本2,然后卸载产品B,则版本化共享文件不会还原为先前的二进制文件。根据定义(并非总是在实践中),共享文件需要支持较旧的客户端。

在" end"中使用RemoveExistingProducts(REP)进行重大升级期间,升级会应用文件的文件版本控制规则,并增加已安装组件的引用计数(如果&和,则安装组件) #39;新的,引用计数为1)。在这种升级中,组件可以与旧产品已安装的相同组件ID有效共享。当REP卸载旧产品时,ref计数会减少。

因此,在包含所有相同组件ID的升级的最简单情况下,不会删除任何文件:如果组件ID A,B,C和D位于较旧的已安装产品中,则组件ID A,B,C和D为在新升级中,然后应用了文件版本控制规则,并且当REP删除旧产品时,递减引用计数会将文件留在那里,可能具有更高版本。这就是为什么组件规则必须遵循此类升级或补丁,或通过重新安装REINSTALLMODE = vomus REINSTALL = ALL进行升级。

如果组件ID A,B,C和D在较旧的已安装产品中,并且组件ID A,B,C和E在新升级中,则会发生相同情况,但D将被删除,因为其引用计数为零现在,假设没有其他客户,上述规则不适用。

使用REP"早期"进行重大升级在最好的情况下是简单的,因为它是旧产品的卸载,然后安装新的,因此可以安装旧版本的文件,并且根据上述规则再次删除文件。在最简单的情况下,其他产品不使用共享文件时,任何组件ID都不需要相同。

常见问题涉及将组件设置为永久或共享Dll(仅当文件与非MSI安装共享时才需要)。似乎有人认为可以通过另外一个更改它们的安装来关闭它们,但这些是系统设置,而不是项目设置。