WiX / MSI部署反模式
在WiX / MSI文件中经常会出现几种部署反模式。以下是一些最常见的粗略草案。
在进入问题之前,另一方面here is a quick reminder of the things that have made MSI an overall success!(尽管有问题)。
这个答案是一项正在进行的工作
你怎么知道我达到答案的最大尺寸。我想这已经足够了:-)。有些部分需要澄清和改进。
如果您认识到其中一些问题,您可能需要继续阅读 - 这些都是众所周知的开发人员对Windows Installer / MSI的厌恶和烦恼:
- 您无法使用最新设置可靠地覆盖较低版本的文件。
- 您无法可靠地覆盖非版本化文件(例如,对于IIS)。
- 尝试安装MSI升级后,文件神秘丢失。
- 在(主要)升级方案期间数据被清除。例子包括:
您的注册表存储许可证密钥
配置文件中的数据,如config.xml,settings.ini等......
您未作为LocalSystem运行的服务的服务凭据。
- 数据未更新:
使用要强制执行的新设置在安装期间无法可靠地更新设置文件。
更新每个用户(或HKCU)存储的数据文件中的设置时出现问题。您为安装用户更新,如何更新其他用户?
- 你看到你的包裹意外地进行了自我修复。
- 您的自定义操作会使设置炸弹出现神秘错误。
- 这是一个很大的问题:您不必要对Windows Installer本身已经完全支持的事情使用自定义操作。这是一个巨大的部署反模式,也是部署失败的主要原因。
您可以通过自定义操作安装Windows服务。使用内置结构在MSI本身中做得更好。
您可以通过自定义操作将.NET程序集安装到GAC。 Windows Installer本身完全支持这一点,而没有一行(有风险的)代码。
您运行自定义.NET程序集安装程序类。这些仅用于开发和测试。它们永远不应该作为部署的一部分运行。相反,您的MSI应使用内置构造来部署和注册您的程序集。
您可以通过自己的MSI中的自定义操作运行必备设置和运行时安装程序。这应该完全不同。见第6节。
- 部署共享运行时文件时遇到问题。
- 无提示安装MSI似乎导致与以交互方式运行设置不同的安装状态。
以下部分根本没有特别的顺序 - 截至目前。
这些部分不断寻求改进。请添加有关不明确或有用的评论。
待补充:
1. Self-repair problems
一个特别恼人的问题与经常为您安装的应用程序触发不必要的自我修复的构造有关。
2. Incorrect installation of shared, vendor or Microsoft runtime files
虽然在上面的链接(自修复问题)中对此进行了详细解释,但在此还应注意,任何设置中最常见的错误之一是包含共享运行时文件的“本地副本” - 有时也会全局注册在系统上,如果它们是COM文件。旧VB6应用程序的安装程序有时会针对它们所需的常见控件执行此操作,从而破坏了其他应用程序的系统。
- 如果您需要特定版本的共享文件供COM使用,并且您无法更新应用程序以使用正确安装的共享组件,那么您可以使用无注册COM。基本上安装所需二进制文件的本地副本,并通过为二进制文件提供的清单文件强制加载共享文件。
- 有关此主题的更多详细信息,请参阅上面第1项中的自修复问题链接。
3. Incorrect handling of (your own) shared files and data
如果您创建一套MSI文件来部署不同的产品,他们可能会在它们之间共享某些文件。如果您从多个MSI文件(每个使用不同的组件GUID)定位相同的文件位置(绝对路径),那么每个安装程序都会将文件视为“拥有它” - 很高兴在卸载时将其卸载,或者再次将其放置到位通过自我修复。
- 对此的正确解决方案是要意识到,对于您定位的每个绝对路径,必须有单个组件GUID。绝对路径是由组件GUID引用的 - 它必须在所有设置之间共享才能使其正常工作。
- 要在所有设置中使用相同的组件GUID,您应该创建一个合并模块以包含在每个设置中,或者使用WiX中的高级构造(例如“包含文件”) - 其中包含组件的硬编码GUID。
- 如果有问题的文件是更新后永远不应该卸载或更换的数据文件,您还应该考虑将其安装为“永久组件”,以便在主要升级或手动卸载期间不会将其卸载。
4. Component creation errors - not following best practice
不遵循组件创建的最佳实践。 MSI组件是文件和注册表设置的基本安装单元。
- 有关如何“组件化”应用程序文件的最佳实践规则。违反这些规则可能会导致修补和升级出现问题,并出现神秘症状,例如升级后丢失文件和设置,或者出现无意义错误的补丁。
- 为了解决这个问题,过度简化是你应该为每个组件使用一个文件,除非你的设置中的文件数量真的很大。 This avoids all kinds of problems(阅读该链接以获得更全面的组件引用计数说明)。
5. Upgrade problems relating to user data being overwritten or reset
这不亚于非常普遍。我已经回答了关于这个主题的几个stackoverflow问题,并且它不断出现。
- 请阅读“过度使用每用户文件和注册表部署”一节,以获取有关如何最大程度地减少对Windows Installer的依赖以进行用户数据部署的说明。如果你问我这是持续存在的“数据回复”问题的真正答案。
- 由于MSI中的升级很复杂,因此许多标准化主要升级(最简单的升级形式)。主要升级基本上是卸载和重新安装相同的产品(在不同版本中)。
- 有几种方法可以配置这样的主要升级,但如果在安装新版本之前完全卸载以前的版本,则可以卸载自安装以来已修改的用户数据文件。 MSI不检查自安装以来是否已修改数据文件,并且会毫不犹豫地高兴地卸载它们,除非您已将主机组件标记为“永久”(它永远不会被卸载)或为托管组件设置空白组件GUID(a安装文件然后完全忽略它的特殊功能)。
- 需要注意的一个特例是,即使您使用合并模块或WiX包含文件正确共享此类文件(以保持安装组件GUID稳定) - 如果存在重大升级,它仍可能会被卸载并重新安装只是盒子上的一个产品,它当时引用它(引用计数为1)。
- 主要升级完成后,看起来好像数据文件已被覆盖或还原,但实际上修改后的数据文件只是被卸载,然后重新安装在他们的“新版本”中(将尽快更新一些可能的修复) 。
- 在我看来,您应该只安装在安装后以只读方式使用的数据文件。如果要写入文件,则应由应用程序本身在我看来生成,并存储在用户配置文件中。这是一个如何更改应用程序设计以使部署更可靠的示例。在我看来,“真正的解决方案”。
- 如果使用组件安装读/写数据文件,请将其设置为永久(或使用空GUID)。文件覆盖规则将确保在安装过程中不会覆盖磁盘上的文件(除非您执行一些愚蠢的操作,例如将REINSTALLMODE设置为amus以强制覆盖所有文件 - 这绝不应该被允许。它可以降级由合并模块安装的共享文件以及 - 旧式DLL地狱)。如果您确实想要擦除文件并覆盖它,那么也可以使用各种方法,其中最好的方法可能是使用配套文件。 (稍后将添加更多细节)。
- Wix: Windows Service sometimes uninstalled when upgrading
6. Erroneous or unnecessary use of custom actions
(过度)使用MSI文件的自定义操作是一个很大的主题,这个部分太大了,被分成了一个单独的答案:Why is it a good idea to limit the use of custom actions in my WiX / MSI setups?。
基本上自定义操作通常是不必要的,因为MSI中的内置支持可以实现相同的效果,或者在免费框架(如WiX或商业工具,如Advanced Installer或Installshield)中提供现成解决方案。
自定义操作本质上是容易出错的,也是部署失败和错误的主要原因。请阅读以上链接了解详情。成千上万的人,成千上万的人,甚至数百万人已经测试了这些内置结构。为什么你自己做呢?
一些“besserwissing”(我应该遵循自己的建议):专注于让你的产品与众不同的东西 - 新的东西,并消除所有其他错误来源。良好的部署不会使您的产品,但错误的部署可以打破它。
7. Failure to properly merge INI files
可以通过File表安装INI文件 - 就像任何其他文件一样。如果目标位置存在现有INI文件,则不允许合并。
- 如果将INI条目导入相应的MSI表,则可以使用“合并”现有值来更新现有INI文件,而不仅仅是执行文件覆盖“擦除”现有条目,或者根本不更新文件。
- “INI合并”是“自动魔术”,它允许适当的回滚支持和任何现有INI文件中的值的“针对性”更新。如果安装程序已中止,则INI文件将正确恢复为其初始状态。
- 这是一个非常出色的功能,几乎可以用于我见过的任何INI文件。但是,我确实看到过一些INI文件具有非标准格式的情况。有时他们有你想要安装的大型评论部分(开发人员工具)或MSI合并不能支持的奇怪格式(逗号分隔的三重文件和类似的东西)。在这些情况下,您必须将其安装为文件而不是“更改事务”,以保留唯一格式的INI文件。
- 如果您正在开发和使用非标准INI文件,请考虑为文件提供与* .INI不同的扩展名,以指示其唯一性和特殊处理需求。它实际上不再是INI文件(键值格式)。反之亦然:您有一个唯一的扩展,如果文件内容是键值对,您可以将其更改为INI以将其作为正确的INI文件处理。
8. Erroneously using self-registration for COM files
或者通过Registry表安装他们的注册。使用适当的COM广告表。有很多原因,如下所述:Self-registration considered harmful。
- 我见过自我注册执行其他操作的情况,而不是相关系统上的实际COM注册。这通常是来自相关开发人员的可怕设计,但我知道人们选择使用自注册而不是重新实现在自注册期间完成的操作作为适当的自定义操作的情况。
- 允许个人观点:当我看到网络设置受到自我注册的影响时,我立即希望该软件被拒绝使用。这就是在自我注册等标准化操作中做一些“hacky”的事情是多么严重。要问的理智问题是“如果给予狡猾的COM注册,他们还有什么其他的东西”。依靠非标准,黑客的东西,这不是一个信心建设者。
9. Overuse of per-user file and registry deployment
升级:与此主题相关的新答案:Create folder and file on Current user profile, from Admin Profile。
这部分太大了,被分成了一个单独的答案:Why is it a good idea to limit deployment of files to the user-profile or HKCU when using MSI?
HKCU中的文件或设置的基本用户配置文件部署是可以容忍的,但它可能不是最好的设计,并且确保所有设置和文件都可以放入盒子上的每个用户配置文件和用户注册表中,这可能很麻烦。在上面的链接答案中讨论了导致的部署问题和一些提出的解决方案。
实际上,可以使用MSI自我修复,Microsoft Active Setup或对相关应用程序或解决方案的逻辑设计更改来支持用户部署(首选选项 - 有关详细信息,请参阅链接的答案)。通常,部署不应干扰用户数据和设置,因为它实际上是用户数据,不应该部署,而是由应用程序在运行时生成。
10. Silent installation fails to complete or is incomplete
Windows Installer的内置功能是可以以静默方式安装任何MSI文件。这是该技术的核心功能,旨在帮助企业部署 - 通常始终以静默模式运行。确保您的MSI能够在静默安装后完成并成功运行,这一点非常重要。根据我的经验,自定义操作通常会导致静默安装出现问题。
- 切勿从InstallUISequence(从设置对话框)中对计算机进行更改。上面描述了这个问题。交互式GUI中使用的自定义操作是立即模式(对于常规用户没有提升),应该只收集并验证用户输入(只读)。对计算机所做的所有非标准更改都应该在InstallExecuteSequence中的InstallInitialize和InstallFinalize之间完成 - 事务处理的提升操作只能运行延迟模式和提升的自定义操作。
当您以静默模式运行时,InstallUISequence中所做的所有更改也将完全跳过,然后安装可能不完整。静默安装对于企业部署非常重要 - 通常始终忽略GUI,并通过在命令行中使用转换和/或设置属性来强制执行更改。
以下是对静默和交互式安装和卸载如何产生不同结果(以及它如何成为严重的MSI设计缺陷)的冗长讨论:Uninstall from Control Panel is different from Remove from .msi
- 永远不要在InstallExecuteSequence中的自定义操作中显示对话框。这样做会导致静默安装完全失败,因为这些对话框不会自动遵循正在运行的安装的UILevel设置。当设置通过部署系统以静默模式运行时,模式对话框可以显示并阻止设置完成,当然没有用户可以关闭对话框。您可以使用属性UILevel来确定安装是否以静默方式运行,然后禁止显示对话框 - 但显示这样的对话框只是错误的设计。
11. You try to "force overwrite" files with your MSI installer
MSI具有一些非常复杂的“file versioning rules”,旨在最大限度地减少“DLL Hell”的影响。它们通常会导致文件无法按预期覆盖 - 这是一个典型的MSI问题。结果,人们觉得他们找不到一种可靠的方法来在安装过程中始终强制覆盖磁盘上的文件。
- 有一些方法可以强制覆盖文件,但不能像大多数人认为的那样合乎逻辑。坦率地说,文件替换设计即使在被理解时也常常不受欢迎。
- 对于版本化文件和数据文件(文本,图像,没有版本属性的任何内容),覆盖文件的工作方式完全不同。实质上,更高版本的文件会在文件版本化时覆盖较低版本的文件。如果相关文件的创建和修改日期不同,则不会替换数据文件。然后在安装后进行了修改。
- 可以通过在msiexec.exe命令行级别设置的REINSTALLMODE property的自定义设置稍微调整文件覆盖行为(覆盖旧版本,覆盖相同版本,覆盖任何版本等...)。设置REINSTALLMODE属性会更改整个设置中所有文件的文件替换逻辑 - 包括使用可能以共享位置中的文件为目标的合并模块部署的文件。因此,您可以降级共享文件和组件 - 正是“DLL Hell”的含义。
- 然而,理解“文件覆盖规则”以及它们如何受设置影响至关重要,但它是一个适用于整个安装中所有文件的设置。还有一些“黑客”只能覆盖特定文件。
- Check this article for how you can force overwrite a file that won't upgrade。
- 此部分尚未完成。
12. You install services that run with user credentials
在我看来,这不是一个好的做法,通常人们在主要的升级场景中也会消除凭据 - 在某些情况下,还会设置服务使用的设置文件。
13. Your application requires extensive, custom NT privileges
NT privileges不同于自主访问控制(文件系统和注册表对象的访问控制),包括SeServiceLogonRight“登录即服务”(必须为尝试运行服务的任何用户帐户设置) - 这是一个非常常见的设置问题尝试使用用户凭据运行服务的设置)。
在某些情况下,运行应用程序或更可能是服务需要过多的此类权限。一种非常强烈的“部署气味”或实际上是“解决方案气味” - 如果有的话就会出现反模式。
几乎所有这些特权都是浪费的危险。
- 我认为SeSystemtimePrivilege - 设置系统时间并不是太关键 - 至少在面值上,但我并没有真正看到任何完全无害的特权,除了上面提到的服务登录权限之外,很少需要它。
- 根据我的经验,所要求的特权倾向于围绕“Logon User Rights”。 SeNetworkLogonRight(从网络访问计算机),SeInteractiveLogonRight(本地登录),SeBatchLogonRight(作为批处理作业登录)和大的:SeServiceLogonRight(作为服务登录)。
- 某些NT权限(如SeAssignPrimaryTokenPrivilege,SeBackupPrivilege,SeDebugPrivilege,SeIncreaseQuotaPrivilege,SeTchPrivilege(作为操作系统的一部分))和其他几个特权永远不应该被任何合理的包应用。
- 用于运行服务的LocalSystem帐户具有大多数权限(包括危险权限),应该用于运行解决方案,而不是创建单独的用户帐户并为其分配这些权限。认真。
- 这是一个很好的“grouped list of NT privileges”,它提供了一些更多的上下文来理解每个权限的含义以及它们之间的关系。
14. You apply a lot of custom disk and registry permissioning
这是一种明确的“部署气味”或部署“反模式”。几乎在所有情况下,都可以通过重新设计相关应用程序来避免这种情况。
- 传统上,使用各种命令行工具来应用自定义权限。 MSI中还有内置功能,但它们缺乏灵活性。
- 随着WiX的出现,应用权限现在相对可靠,因为它是由了解MSI的开发人员制作的经过适当测试的解决方案。商业工具当然也支持自定义权限。
- 在我看来,自定义权限仍然表明您正在安装的软件出现问题,但我自己也应用了很多自定义权限。
- 我经常看到由应用于磁盘或注册表的错误权限引起的重复自修复问题:How do I avoid triggering MSI self-repair with my WiX / MSI package?(第5节)。
- 我还看到了几种情况,其中应用了错误的权限会导致卸载变得不可能而没有对失败的ACL权限进行严重调整。非常粗糙的工作,并且通过尝试部署和自动修复很容易变得更糟。
- 另一个明显的问题是您通过打开计算机上每台计算机位置的写入权限而引入的安全风险。
15. Your license key in the registry is reset on upgrade
一种非常常见的设计是使用MSI组件将许可证密钥写入注册表。这可以是HKCU,也可以是HKLM,以使其成为同一台机器上所有用户的共享许可。
如果使用MSI公共属性设置此许可证密钥,则应在全新安装时读取此值,以确保不使用空字符串覆盖现有数据。在主要升级方案中,MSI公共属性(令人惊讶地)不会被持久化并由升级设置自动回读。忘记这样做是人们在重大升级期间看到许可证密钥被清除的一个非常常见的原因。
我很少(如果有的话)推荐读/写自定义操作。它们容易出错并且可能很复杂 - 大多数人从未实现正确的回滚(如果设置崩溃并需要回滚)。但是,您还可以使用自定义操作检查系统的“当前状态”,并且可以调整自定义操作以使其始终运行,即使在修补序列期间也是如此,并且您可以在不同的序列中执行不同的操作你需要。大多数情况下,自定义操作在非预期时运行实际上可能会出现问题 - 例如在修补程序安装期间。很少有人记得使用NOT PATCH调整自定义操作(以防止在修补期间运行)。
尽管如此,如果我在安装过程中被指示编写许可证,我可能会使用自定义操作在安装期间将许可证密钥写入HKLM。然而,这很重要,我宁愿完全从设置中删除整个许可问题,原因如下所述:Installer with Online Registration for Windows Application(推荐阅读 - 有很多理由让许可证不受你的设置限制)。
16. Undesirable Hard Coded GUIDs
某些GUID可以在您的WiX源文件(或其他MSI创建工具)中进行硬编码。例如,组件GUID - 除非您更改安装位置,否则它们应对每个组件保持稳定。这里解释了这个的基本原理:Change my component GUID in wix?
但是,不要硬编码包代码。应始终为每个构建自动生成MSI的包代码。它应该是独一无二的。更详细;包GUID的想法是它对于每个编译的MSI文件应该是唯一的。它只是用于唯一标识文件。根据定义,Windows Installer将两个具有相同程序包GUID的不同MSI文件视为同一文件。导致各种X文件问题。因此,应始终自动生成包GUID,因为它应该是唯一的。
许多人还自动生成产品代码 - 因为他们只使用主要升级来升级他们的应用程序。对于这个用例自动生成的产品代码工作得很好。但是,如果您还需要支持Windows Installer次要升级,则应该对产品代码进行硬编码并在适当时进行更新。升级代码通常应该是硬编码和手动管理的。 See this answer。
17. Erronous Inclusion of Sensitive Data
现在有一个关于防止敏感数据在最终安装程序中结束的主题的单独Q / A:How do I avoid distributing sensitive information in my MSI by accident?
基本上,建议是为硬编码的开发箱版本提供一次性文件。怎么检查?我不喜欢它,用Orca打开MSI - 然后浏览表格。最脆弱的表可能是:Registry
,Property
,IniFile
,也许是Directory
,如果你使用MSI GUI:all tables relating to GUI
。任何脚本(CustomAction
表或Binary
表 - 后者要求您流出任何脚本 - 或在源位置检查它们)。