为什么在我的 WiX/MSI 设置中限制自定义操作的使用是个好主意?

Why is it a good idea to limit the use of custom actions in my WiX / MSI setups?

为什么在我的 WiX/MSI 设置中限制自定义操作的使用是个好主意?


部署是大多数开发的关键部分。请给这个内容一个机会。我坚信,通过对应用程序设计进行微小更改,使部署更合理、更可靠,可以显着提高软件质量 - 这就是 "answer" 的全部内容 - 软件开发.


这是一个 Q/A-style 问题,从一个太长的答案中拆分出来:

Core: Essentially custom actions are complex to get right because of complexity arising from:

  • Sequencing-, Conditioning- (when it runs - in what installation mode: install, repair, modify, uninstall, etc...) and Impersonation-issues (security context it runs in).
  • It results in overall poor debugability - very hard hard work to test in all permutations.

Application Launch: Solution? Prefer application launch code. You get a familiar debugging context and can usually keep the code in the main application source - very important. The issue is the same for: licensing in setups (take it out if you can).


自定义操作调试:包含所有警告(您将阅读下面的简短“民间”版本 - 是的,您会 - Jedi技巧) - 在可用时使用 built-in 结构 - 这里是你如何改进调试自定义操作的方法:


简短的“民间”版本:

总体:现代设置应该“声明”,而不是编码。 “思考 SQL 查询,而不是脚本”。应用您刚刚调用的 well-tested 逻辑来完成工作。稍后会详细介绍。

  • 尽可能避免自定义操作
    • 他们会拧你的头(并谋杀你的少女身材)!
    • 说真的:它们是部署失败的第一大原因
    • 它们具有“密谋的复杂性”,在您部署设置一段时间后,事情经常会在没有警告的情况下意外中断,并且它“处于野外”状态。
    • 部署是一个过程 - 您将不得不为每个新版本处理“过去的罪过”。在部署失败后清理东西可能会成为一场真正的噩梦。每次迭代都会添加新的潜在错误源(我对失败的修复、失败等的修复)。
    • 托管代码脚本自定义操作的运行时要求非常容易出错(. NET 尤其是在我看来)。设置 - 如果有的话 - 应该具有“最小依赖性” - 因为它用于完全安装依赖性并且应该 运行 在“任何机器”在“任何州”在“任何语言”。
    • 除了使用 built-in MSI 构造或 WiX 自定义功能外,通常可以通过对应用程序设计进行微小更改来避免自定义操作,这样复杂的自定义操作就不会发生需要更长的时间(下面的示例)。
  • 花时间 查找和查看来自 WiX, Installshield, Advanced Installer (and other vendor tools 的 built-in MSI 功能和扩展
    • 这些扩展 由可用的最佳部署专家制作,并且在大多数情况下 已经过数千人的测试(数百万人和在 built-in MSI 功能的情况下为数十亿)。
    • 你怎么能想到将它与你自己的专有代码相匹配?如果现有解决方案足够好(通常情况下是这样),那么您自己的代码完全增加了风险而没有任何收益。 ready-made 解决方案甚至支持 回滚 - 一个众所周知被忽视的 hard-to-implement 功能 - 这实际上是设计所需要的。
    • 换句话说:当你使用现有的解决方案时,你借用的不仅仅是实现,而且至关重要的是 QAUAT测试 的扩展,当你使用它们。 这可以节省数周的工作并且对部署稳定性有很大贡献
    • 所需要的只是花一些时间搜索和阅读已经可用的内容,而不是 jump-starting 使用您自己的代码的内容。哎呀,甚至您自己的经理也可能参与搜索......在梦想世界中。
    • 设置一般不应该编码,应该声明!(想想SQL)。这就是 Windows 安装程序的全部意义所在:您声明要安装的内容,安装程序引擎本身会处理顺序。收益结果 - 如果做得好。
  • 真的就是这样。如果你敢,请继续阅读!这里是严肃咆哮中的龙。至关重要的是:real-life 经验 处理设置开发以及在企业环境中大规模部署此类设置。常见问题变得更加清晰 - 它们更容易识别。你可能会被你真正必须回答的问题所困扰——你觉得你永远无法预见(进行公司包装和重新包装,你会看到一个包裹可能有多少错误——可能需要几天时间才能解决这些问题有时会变形)。
  • 请记住,这并不像听起来那么糟糕:部署通常是一项地位低下的技术努力,但却是一个引人注目的失败。错误确实显示。并且有人会为它负责,并且支持会真正感受到它。
  • 在开发过程中调试很困难,但对于部署来说有时几乎是不可能的,因为您通常无法访问问题系统,并且每个问题系统都将处于完全不同的状态。
  • 老实说:未能将软件正确部署到最终用户可能接近于软件开发中犯下的最昂贵的错误 - 没有人能见证您的解决方案的伟大之处 - ad 它会影响销售、支持、营销和进一步的解决方案开发
  • 部署不当肯定会让产品沉没。不幸的是 良好的部署不足以挽救产品

充实的咆哮版本:-)

如上所述此部分是从范围更广的现有答案中拆分出来的How do I avoid common design flaws in my WiX / MSI deployment solution?(旨在帮助开发人员做出更好的部署决策的答案)。

  1. 错误或不必要地使用自定义操作

自定义操作的使用可能会导致过多的部署问题 - 其中大部分都非常严重。 如果您的设置无法完成或崩溃,可以肯定是错误的自定义操作造成的。

因此,显而易见的解决方案是尽可能限制自定义操作的使用。自定义操作(通常)是“black box”(隐藏代码),而大多数 MSI 具有很高的透明度。 MSI 格式可以很容易地查看 (COM-structured storage file),并且 MSI 文件所做的一切基本上都可以从其 MSI 数据库文件中推断出来——除了 编译的自定义操作 (脚本自定义操作仍然是白框 - 你可以看到发生了什么,除非它们被混淆了)。

在这个恶意软件时代,这些黑盒自定义操作 - 运行 具有更高的权限 - 可能会以多种方式变得不受欢迎。它们不仅是稳定性和可靠性问题,而且是 full-blown 安全问题。

6.1 自定义操作的过度使用

  • 请原谅下面列出的“琐事”。其中很多可能是陈词滥调,但也许可以使用列出的论据为自己辩护,以避免自定义操作并致力于更好的解决方案——通常涉及更智能的应用程序启动顺序和应用程序 self-configuration。相信我们,应用程序编码将比处理 Windows 的安装程序对自定义操作的复杂调节、排序和模拟更有趣。

  • 下面的内容已经更新了很多次,以至于部分内容有点乱序或重复。会在时间允许的情况下进行清理。

  • 开发人员擅长编码 - 所以恕我直言 - 您倾向于 过度使用自定义操作 来完成使用 [=126= 可以更好地完成的事情]built-in MSI 功能 或 ready-made MSI 扩展解决方案 例如 WiX 中可用的高级功能 XML 文件更新, IIS, COM+, 防火墙规则, driver 安装自定义磁盘和注册表项的权限修改 NT 权限 等。 . Installshield 和 Advanced Installer 等商业工具也有这种支持

  • 作为应用程序启动序列的一部分,也可以执行自定义操作中完成的许多操作。主要示例是 初始化用户数据并将设置文件复制到每个用户配置文件 。这里有一个关于这个问题的小总结:Create folder and file on Current user profile, from Admin Profile.

  • 这是显而易见的,但很多时候使用自定义操作是因为不知道通过 ready-made 解决方案已经“开箱即用”的内容。这一直发生在我们所有人身上,不是吗?总有一些更聪明的方法来做一些可以让你免去很多悲伤的事情?总的来说——尽管有时你会开辟新天地。不要在你的设置中开辟新天地——除非你绝对必须这样做!为您的应用程序保存它。

  • 我想强调的是 这些 built-in Windows 来自 WiX 和商业工具的安装程序解决方案和扩展是由可用的最佳部署专家编写的。此外,更重要的是,它们已经被成千上万的人使用和测试过 - 甚至数十亿人 built-in MSI 功能。 你真的认为你可以做得更好吗?故事的寓意:选择你的战斗并使用你出色的编码技能来解决新问题,让部署变得愚蠢尽可能。使用已经有效的方法,不要重新发明轮子。部署中有太多未知数,太多无法控制的变量——你正在处理“任何机器”在“任何状态” ”和“任何语言”。 See the Complexity of Deployment section here 如果您想要示例 - 在页面下方 - 只是一个简短的转储,其中包含您的目标系统在您的包到达时可能在状态上有所不同的所有方式。每个变量都是自定义代码的新陷阱 - 从 OS 版本 ,到 application estatemalware situation 列表如下不断。正如 linked 内容中的结论所读:“部署是一个简单的概念,其中包含可能导致最神秘错误的复杂变量组合 - 包括开发人员最喜欢的:间歇性错误。众所周知,此类错误的严重性怎么强调都不为过,因为它们通常无法正确调试。"

  • 某些高级操作必须在您的设置中完成 - 因为它们需要 提升的权限 并且您的应用程序不应在 运行 期间请求此操作。这就是设置的目的,高级、提升的系统配置 - 所以接受这种复杂性,但使用现成的解决方案! 不要推出自己的脚本和解决方案,使用 built-in,经过良好测试的东西。请问你的脚本运行在韩国正确吗?您的自定义操作是否会被您从未有时间测试脚本的主要 anti-malware 口径解决方案阻止?您是否有时间自行检查计算机上是否安装了某个先决条件 运行time - 它适用于所有区域设置和所有 OS 版本? 这里出现错误的可能性是惊人的 - 有时无法正确测试。你们有阿拉伯文、中文、韩文或日文的测试机吗?也许你会。但是你有终端服务器来测试吗?韩国终端服务器怎么样?您是否使用 MSI 广告测试了您的设置? 要使高级系统管理功能发挥作用,您必须使您的设置尽可能简单和标准在你自己写任何东西之前,花几天时间寻找 ready-made 解决方案 。请记住,使用 ready-made 解决方案,您不仅仅是在借用他们的代码,而且至关重要的是他们的创建者的 QA、测试和 UAT - 您几乎永远不会希望重复这些。

  • Real-world 测试 是唯一重要的衡量标准——没有替代品。不要承担这个痛苦的世界!通过 Windows 更新部署的 Windows 略有变化,您的自定义操作会以多种方式中断。选择一场更好的战斗来发挥你的技能。如果你反对设计,那么 Windows 安装程序会反击。

  • 而且部署过程中发生了很多事情,这使得它变得复杂 - 而且不像我们想要的那样愚蠢(只需复制一些该死的文件),你的努力就是让它成为现实尽可能简单,但不能更简单。下面是一个部署任务列表,显示了为什么很难让您的包“足够笨”:What is the benefit and real purpose of program installation? 尽可能只使用 ready-made 构造 - 这是第一个轻松的胜利。您需要做的就是阅读和搜索一下。

  • 最后我要补充一点,所有自定义操作都应该支持回滚,以便在安装失败时将系统恢复到原始状态。在 real-world 中,这几乎从未在 ad-hoc 自定义操作中完成(根据我的经验)。相信我,处理起来很复杂——我害怕“回滚”这个词,就像我在 C++ 中实现 MSI 回滚后,西伯利亚雪橇犬害怕“洗澡”这个词一样。这不是我有过的最有趣的,但它最终工作正常。如果您希望您的设置没有太多依赖项,那么 C++ 是可行的方法,而且众所周知,处理起来并非易事。 ready-made 解决方案支持开箱即用的回滚——它是一个 easy-win,只需一点阅读和搜索即可启用它们。复杂性是的,但你的基础更坚实。至关重要的是:一旦在日本机器上发生了一个不明显的错误,我们可以为社区修复工作,或者第三方供应商需要对其进行排序。

  • 在所有这些“一般观察”之后,在纯技术层面上,Windows 安装程序中的自定义操作在 实现方面非常复杂 , scheduling, conditioning and rollback, 因此应该在绝对必要的时候使用(通常用于 early-adopter 东西)。更复杂的是 运行 时间要求 - 例如依赖于特定版本的 .NET 运行 时间,或 PowerShell,或 Installscript 运行 时间(Installshield 的脚本语言 - 它有一个 运行 时间依赖性,但现在基本上已经解决了,它曾经是一个问题)。

  • 由于缺少运行时间要求而导致的错误可能是一个需要处理的巨大毛球 - 对于零增益.出于这个原因,我在进行自定义操作时只使用 最小依赖 C++ dll'sInstallscript。碰巧我在用户界面序列中对 read-only 自定义操作使用 VBScriptJavaScript 。这些只是检索数据,不会对系统进行任何更改。这些是唯一不易出错的自定义操作类型。然而,它们应该设置为 运行 而不检查退出代码(以防止它们rom 在设置中触发回滚或中止 运行 - 通常处于主要升级模式)。

  • 此外:Windows 安装程序拥有自己的脚本 运行 时间引擎,因此您知道您的活动脚本(VBScript、Javascript 等...)可以 运行 除非你的目标系统真的坏了(在正常系统上不会丢失 运行time)。这与 托管代码自定义操作 形成对比 - 目标系统此时完全有可能没有 .NET 运行 时间(2018 年 1 月)。现在这将在未来改变,因为 .NET 成为 Windows 中真正的 built-in 和必需的功能(或由 Windows 安装程序本身托管的它的一些最小版本)。由于奇怪的原因,托管自定义操作代码仍然存在问题 - 例如 使用了错误的 .NET 版本 到 运行,或者 错误的版本正在加载 和正在使用的 CLR,等等......我在这里的经验有限,但我认为问题很严重。不过,我认为我们最终都会编写托管代码自定义操作。不过,我仍然会在全球分发场景中使用 C++。

  • Powershell 脚本对于自定义操作来说特别麻烦 因为它们显然 运行 不在进程中并且无法访问 MSI 的 session object(来源:微星专家Chris Painter)。 Powershell 还需要安装 .NET 框架。 我永远不会将 Powershell 脚本用于自定义操作 - 甚至不会用于内部、公司部署。你的来电。作为“示例”,这里是该领域的专家陈述他的意见(老化的博客项目,但如果你问我仍然相关):Don’t use managed code to write your custom actions!

  • 我试图写一篇不同自定义操作类型的优缺点总结。坦率地说,我对此不太满意,但它是:Windows Installer fails on Win 10 but not Win 7 using WIX. What I am not happy with? The recommendation of JavaScript for one thing - I have used it seldomly, and although it is a better language than VBScript - particularly for error handling - I have had lots of problems using Javascript with MSI. It may be a case of the problem existing between the keyboard and the chair :-), but I think there are some real gotchas with JavaScript as well. Try to avoid complex use of scripts. For simple things like retrieving properties they seem to work OK for me. However, the experts in the field are merciless on script custom actions: Don’t use vbscript/jscript to write your custom actions! (Aaron Stebner), VBScript (and Jscript) MSI CustomActions suck(Rob Mensching - WiX 仁慈)。

  • 我还要补充一点,自定义操作的复杂性是“愚蠢的阴谋复杂性”,而不是fun-to-deal-with。它咬你。陷阱。你会在适当的时候发现你所犯的所有错误——随着你的前进(然后你需要做一些解释)——它们通常不会立即显现出来(升级、打补丁、卸载时突然出现问题,你的自定义操作运行s 在修复期间出错,因为它没有正确调节并清除某些注册表设置,静默安装无法正常工作 - 它使安装不完整,因为自定义操作仅存在于用户界面序列中,有 运行 Windows XP 上的时间错误,您从未测试过您的自定义操作代码,目标系统上有您没有屏蔽的损坏的东西,有人打电话给您并告诉您您的包不起作用使用活动目录,你的包不能被公布,它在所有韩文和日文系统上失败,self-repair 触发 运行 时间警告和拒绝普通用户访问等...)。一旦它击中你,你将没有时间正确解决这个问题,你可能不得不继续 sub-standard 解决方案,让自己在下一个障碍中领先。不,我自己没有经历过这一切。事实上,我在为企业部署修复第三方供应商 MSI 文件时发现了其中的大部分问题。当您查看、比较、审查数百个包并将其调整为企业标准以进行大规模部署时,您确实会发现很多潜在的错误来源。老实说,除了最简单的软件包外,所有软件包都存在严重的缺点和错误。很明显,几年前进行此类供应商设置时,您会发现自己做错了什么。 猜猜排在首位的是什么?在 built-in 构造可用时过度使用自定义操作。

  • 除此之外,部署的内在复杂性及其要求在任何机​​器上、任何地方、任何状态下工作,存在 MSI 技术边界问题anti-patterns。部分 MSI 技术导致重复部署问题,因为人们对它们知之甚少,并且有时在 real-world 使用中很脆弱。在这个答案中对此有一个简短的总结(接近底部):How to make better use of MSI files (along with a list of the very important corporate benefits of MSI - and a random dump of common technical problems seen in real-world packages)。尤其是后者 link 在我写完之后让我觉得不太理想。接受它的本来面目:杂乱无章的 real-world 建议,没有太多其他用处。它发现了太多问题,但没有在某些地方的修复行中显示太多。

  • 很大一部分软件支持电话往往来自部署过程中出现的问题。正如故事所说:“...未能正确安装您的优秀软件可能接近成为软件开发中 最昂贵的错误。你永远不能希望销售一个永远无法测试的软件”(来自上面 linked 的答案之一)。坏习惯a此类问题的背后往往是解决方案。

  • 我认为我们看到的最常见的不必要的自定义操作使用:

    • 您通过自定义操作安装 Windows 服务。使用 built-in 构造在 MSI 本身中做得更好。

    • 您通过自定义操作将 .NET 程序集安装到 GAC。 Windows 安装程序本身完全支持,没有一行(有风险的)代码。

    • 您 运行 自定义 .NET 程序集安装程序 类。这些将用于开发和测试。作为部署的一部分,它们应该 永远不会 运行。相反,您的 MSI 应该使用 built-in 构造来部署和注册您的程序集。

    • 您 运行 先决条件设置和 运行time 安装程序通过您自己的 MSI 中的自定义操作。这应该完全不同地完成。您需要的是一个引导程序,它可以按顺序启动您的设置及其先决条件。具有此类功能的商业部署工具,例如 Advanced Installer and Installshield have features for this, but free frameworks such as WiX has support via its Burn feature, and there are also free GUI applications such as DOTNetInstaller(我未经测试)。

    • 在这种特殊情况下请相信我的话:运行通过自定义操作进行嵌入式设置最终会失败 - 通常是立即失败. MSI 在其调度、模拟、事务、回滚和整体 运行 时间架构方面过于复杂,无法实现这一点。根据设计,一次只能有一个 MSI 事务 运行,这使事情变得非常复杂。过去,您可以 运行 嵌入 MSI 文件作为 MSI 中的一个概念,但这已被弃用 - 它无法正常工作。您必须按顺序 运行 每个设置。正确的顺序。有 bootstrappers / chainers 可用,可让您定义这样的安装顺序。这是一个描述其中一些问题的答案(不要让问题标题让您失望 - 它是关于引导程序/链接器和其他部署注意事项):Wix - How to run/install application without UI.

6.2 自定义操作选项

  • 在 WiX 和其他工具(如 Installshield 和 Advanced Installer)中找到的 ready-made 解决方案已经过试用和测试,并且至关重要的是还实现了 正确的回滚支持 - 自定义操作实现中几乎总是缺少的功能 - 即使在其他良好的供应商 MSI 设置中也是如此。回滚是一个非常重要的 MSI 功能。您无法与在各种环境中积极使用和测试的大型用户社区提供的质量竞争。充分利用这些功能。

  • 除了使用 built-in MSI 构造或 WiX 自定义功能外,自定义操作通常可以通过对应用程序设计进行微小更改来避免,这样复杂的自定义操作就不会发生不再需要。我将 licensing 视为如何简化部署以提高可靠性的示例。

    • 这是非常重要的一点,也是很容易被忽略的一点。有时,一些高质量的编码时间或数天以及一些高质量的 UAT/QA 时间可以防止长期存在的部署问题。再多的支持时间也无法解决您可能遇到的最糟糕的部署问题。

    • 此答案简要介绍了部署的总体复杂性Windows Installer and the creation of WiX(接近底部)。部署是一个复杂的“交付过程”,面临着几个严峻的挑战:1) 部署错误的累积性2) 正确调试的难度、和 3) 影响目标机器的外部因素和变量的范围几乎是无限的 world-wide - 结论是部署必须尽可能简单才能可靠。有太多的干预因素让您成为开发人员的最爱:间歇性错误。建议使用上面的 link 以获得更充实的解释。

6.3 高级自定义操作问题(超过 运行 次失败)

  • 一个非常常见的自定义操作问题是他们不正确的调度和试图从“非提升”即时模式操作修改系统。 这些是高级 MSI 设计缺陷 看到其他工作设置的人无法立即识别出来。

  • 切勿在尝试修改系统的 MSI (read/write) 的 InstallExecuteSequence 中的 InstallFinalize 之后插入即时模式自定义操作。如果您的设置是通过 SCCM 等部署系统以静默模式部署的,这些操作将永远不会 运行(同样,我上次检查过)。

  • 切勿尝试使用从设置 GUI 激活的即时模式自定义操作来更改系统。这似乎有效r 管理员用户,但即时模式自定义操作未提升,因此普通用户将无法 运行 他们没有错误。此外,当设置为 运行 静默模式时,从 MSI GUI 对系统所做的任何更改将永远不会完成(然后跳过整个 GUI 序列)。 这是一个非常严重的 MSI 设计缺陷:静默安装和交互式安装会导致不同的结果。此问题在有关静默安装的第 10 节以及此答案中提到:How do I avoid common design flaws in my WiX / MSI deployment solution?。静默安装是企业部署软件的一项重要功能。在控制其部署的公司中,所有部署都是静默完成的。您的设置 GUI 根本看不到。我能想到的只有少数例外,例如某些服务器部署可能以交互方式完成,但所有桌面和客户端软件都是静默部署的。 不支持正确的静默安装 因此是您的 MSI 设置的可怕的缺陷设计缺陷。请听从这个建议。这对于公司接受您的软件至关重要 - 我建议将某些软件从标准系统中删除,因为它的部署对系统来说非常危险,我们只是不想处理它。 虚拟化沙盒APP-V - 虚拟包 - 或完全在虚拟机上)正是因为这些问题(行为不当的应用程序和设置),今天才变得重要。

  • 所有对系统进行更改的自定义操作都必须插入到安装序列的“已处理部分”中。此 Windows 安装程序事务(想想数据库事务提交)运行s 在标准操作 InstallInitializeInstallFinalize 之间 InstallExecuteSequence 和 运行 具有提升的权限。系统的所有更改都将在此交易中发生——其他任何事情都是错误的(但不幸的是很常见)。此处插入的所有自定义操作也应实现相应的回滚自定义操作,这应撤消对系统所做的任何更改,以防安装失败并回滚。

自定义操作应该受到限制的想法源于这样一个事实,即很容易编写出执行不佳的自定义操作。一个实施不当的自定义操作是在安装失败的情况下无法回滚已经对系统所做的更改(例如,安装 windows 服务)。

话虽这么说,如果编写的自定义操作具有适当的免费回滚自定义操作,那么我认为这不是问题。

我喜欢遵循的模式是,对于我认为可能需要的每个 "custom action"(例如,FoobarCustomAction),它实际上有 3 个包含自定义操作:

  • FoobarCustomActionImmediate - 这是一个准备 2 个有效负载的即时自定义操作:
    • 首先,要传递给 FoobarCustomActionDeferred 的有效负载,它指示系统更改要进行的更改。
    • 其次,要传递给 FoobarCustomActionRollback 的有效载荷,它指示如何回滚系统更改。
  • FoobarCustomActionRollback - 这是一个回滚自定义操作,按顺序安排在 FoobarCustomActionDeferred 之前,它使用 [=24] 提供的负载=]FoobarCustomActionImmediate 在发生严重故障时回滚 FoobarCustomActionDeferred 所做的更改。
  • FoobarCustomActionDeferred - 这是一个延迟的自定义操作,它使所有系统更改对系统的更改。

与许多其他技术一样,我认为总有一种方法可以在 WiX 中编写难以维护的代码。以上面的例子为例,如果这是我想在许多不同的安装程序中使用的自定义操作,我应该为开发人员提供一个 .wxs 文件,其中包含一个 fragment 确保 all 需要的自定义操作被正确引用和排序。相反,如果 WiX 代码只是粗心地复制粘贴到许多不同的安装程序中,则自定义操作被错误实施的可能性就会增加。作为开发人员,我们的工作是帮助我们代码的消费者陷入成功的陷阱,我认为 WiX 通过谨慎使用 fragmentwixlibs.

来提供这一点

但是安装程序的开发人员必须关心这些事情,WiX 不强制执行!