为什么最好在WiX / MSI设置中限制使用自定义操作?


部署是大多数开发的关键部分。请给这个内容一个机会。我坚信,可以通过在应用程序设计中进行很小的更改来显着提高软件质量,从而使部署更合理,更可靠-这就是“答案”的全部意义-软件开发。


这是一个Q / A样式的问题,与答案变得太长:我如何避免WiX / MSI部署解决方案中常见的设计缺陷?。

评论

robmensching.com/blog/posts/2007/8/17/…

我投票结束这个问题是因为脱离话题,因为尽管我同意它的意图,但我认为它不适合Stack Overflow的格式。如所写,该问题没有任何研究,涉及面很广,并且由于缺乏事实依据,因此会提出基于意见的答案。提供的答案很奇怪,因为它以编号6.1开头的项目开始,并且只能概括地说。 (此外,如果规范问题的答案太长,则可能有多个答案。)

足够公平,但是我们如何与负责他们非常不熟悉的事情的忙碌的开发人员交流最佳实践,或更确切地说是技术陷阱?我们是否通过回答已知由不良设计引起的技术问题而使人们误入歧途?任何对我有帮助的事情都对我有利-我不知道这个答案是否有帮助-这是一个实验。部署很容易出错,并且过度使用自定义操作可能是最常见的问题-对于托管代码,情况可能更糟。也许博客是最佳实践的合适之地?会读吗?

就个人而言,除了在虚弱的时刻,我不会再尝试了。它的结局很少。

会读吗?打在头上的钉子。我会说不,那不会。他们不是在遇到问题之前就解决问题,也不是一旦遇到问题就不会解决问题,因为他们搜索的字词将与这些不匹配。有人将要完成一项任务,寻找执行该任务的方法,然后尝试使用他们遇到的第一个CA,无论它使用哪种语言。然后,一旦他们发现它不起作用并提出问题,也许您可​​以通过链接到他们的伤口上擦盐。我宁愿将一个技术问题的答案与为什么不这样做的指导相结合。 (因为有时他们没有更好的选择。)

#1 楼


核心:由于以下原因而导致的复杂性,从本质上来说,自定义操作很复杂:


Sequencing-Conditioning-(运行时-以哪种安装模式运行:安装,修复,修改,卸载等...)和Impersonation-issues(运行它的安全上下文)。
总的来说,它会导致poor debugability的出现-很难在所有排列中进行测试。

应用程序启动:解决方案?首选应用程序启动代码。您将获得熟悉的调试上下文,通常可以将代码保留在主应用程序源中-非常重要。问题与以下问题相同:设置中的许可(如果可以,请删除)。


自定义操作调试:带有所有警告(您将阅读下面的简短“ folksy”版本) -是的-Jedi技巧)-请在可用时使用内置构造-尽管您可以通过以下方法改进自定义操作的调试:


自定义操作运行时失败的常见原因

调试自定义操作

对于本机代码/ C ++,只需将调试器附加到msiexec.exe

高级安装程序的Debug C#自定义操作视频教程






简短的“ Folksy”版本:
总体:应“声明”现代设置,而不要编码。 “考虑SQL查询,而不是脚本”。应用经过测试的逻辑,您只需调用它们即可完成工作。


如果可能的话,请避免执行自定义操作。

它们会拧紧你的头(并谋杀你的少女形象)!
:严重的是,它们是部署失败的首要原因。它们具有“阴谋的复杂性”,一旦部署了一段时间后,事情往往会意外中断,而不会发出警告,这是“野蛮的” 。

部署是一个过程-每个新版本都必须处理“过去的过失”。部署中断后清理工作可能成为一场噩梦。每次迭代都会添加新的潜在错误源(我的修复程序导致失败的修复程序,失败的故障等)。

托管代码和脚本自定义操作的运行时要求非常容易出错(尤其是.NET)在我看来)。安装程序-如果有的话-应该具有“最小依赖项”-因为它用于直接安装依赖项,并且应以“任何语言”以“任何状态”在“任何计算机”上运行。
除了使用内置MSI构造或WiX自定义功能,通常可以通过对应用程序设计进行较小的更改来避免自定义操作,从而不再需要复杂的自定义操作(以下示例)。


花时间在查找并查看来自WiX,Installshield,Advanced Installer(和其他供应商工具)的内置MSI功能和扩展。

这些扩展是由现有的最佳部署专家提供的,并且在大多数情况下已经过测试成千上万的人(在内置MSI功能的情况下成千上万)。
可以想象如何将其与自己的专有代码相匹配?如果现有的解决方案足够好(通常是这样),那么您自己的代码将毫无风险地被完全冒险。现成的解决方案甚至还支持回滚(这是众所周知的被忽略且难以实现的功能),这实际上是设计所需的。
换句话说:当您使用现有解决方案时,您不仅会借用实现,而且还会借用使用扩展时,至关重要的是扩展的质量检查,UAT和测试。这样可以节省数周的工作,并极大地有助于部署的稳定性。
所需要的就是花一些时间搜索和阅读已经可用的内容,而不是用自己的代码来开始。哎呀,甚至您自己的经理也可以参与搜索……在一个梦想的世界中。

安装程序通常不应该编码,应该声明! (想想SQL)。这就是Windows Installer的全部意义:声明要安装的内容,然后由安装程序引擎自己处理该序列。好处就是结果-正确完成。


就是这样。继续阅读吧!这是在大声咆哮中的巨龙。至关重要的是:处理设置开发以及在企业环境中大规模部署此类设置的真实经验。常见问题变得更清楚-它们更容易识别。您可能会被真正要解决的问题所困扰-觉得自己永远无法预料到(进行公司包装和重新包装后,您会发现单个包装可能有多大问题-击败它们可能需要几天的时间
请记住,这并不像听起来那么糟糕:部署通常是一项低状态的技术尝试,但却是一项引人注目的失败。错误确实显示出来。有人会对此做出回应,支持会真正感受到它。
在开发过程中进行调试很困难,但是对于部署来说,有时几乎是不可能的,因为您通常无法访问问题系统,并且每个问题系统都会处于完全不同的状态。
老实说:未能正确地将软件部署给最终用户可能是在软件开发中犯下的最昂贵的错误-没有人可以看到您的解决方案的强大之处-而且影响销售,支持,市场营销和进一步的解决方案开发。

部署不当肯定会导致产品下沉。不幸的是,良好的部署不足以保存产品。


完善的Ranting版本:-)
如上所述,本节与现有答案分开,涉及范围更广:如何避免WiX / MSI部署解决方案中的常见设计缺陷? (旨在帮助开发人员做出更好的部署决策的答案)。


错误或不必要地使用了自定义操作。


过多的部署问题可能是自定义操作的使用引起的-其中大多数非常严重。如果您的设置无法完成或崩溃,可以肯定地认为错误的自定义操作存在错误。
因此,显而易见的解决方案是尽可能限制您对自定义操作的使用。自定义操作(通常)是“黑匣子”(隐藏代码),而大多数MSI具有很大的透明度。可以轻松查看MSI格式(COM结构的存储文件),并且从MSI数据库文件中可以推断出MSI文件所做的一切-除了已编译的自定义操作(脚本自定义操作仍为白框)之外,您还可以
在这个恶意软件时代,这些以提升权限运行的黑匣子自定义操作可能会以一种以上的方式被皱眉。它们不仅是稳定性和可靠性问题,而且是全面的安全问题。
6.1自定义操作的过度使用


请忽略下面列出的“琐事”。其中很多可能是平庸的,但也许可以使用列出的参数为自己辩护,以避免自定义操作并寻求更好的解决方案-通常涉及更智能的应用程序启动序列和应用程序自配置。相信我们,应用程序编码将比处理Windows Installer针对自定义操作的复杂条件,排序和模拟更为有趣。


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


开发人员擅长编码-因此,在充分尊重的前提下-您倾向于过度使用自定义操作来完成使用内置MSI功能或现成的MSI扩展解决方案(例如WiX中可用于XML等高级功能的MSI扩展解决方案)更好地完成的工作文件更新,IIS,COM +,防火墙规则,驱动程序安装,对磁盘和注册表项的自定义权限,修改NT特权等...在商业工具(例如Installshield和Advanced Installer)中也可以找到这种支持。


作为应用程序启动序列的一部分,还可以执行许多自定义操作。主要示例是初始化用户数据并将设置文件复制到每个用户配置文件。这里有一个有关此问题的小结:从管理员配置文件在当前用户配置文件上创建文件夹和文件。


很明显,但是很多时候使用自定义操作是出于对信息的无知。通过现成的解决方案“开箱即用”已可用的东西。这一直在我们所有人身上发生,不是吗?总是有一些更聪明的方式来做事情,这些事情会为您省去很多麻烦?总的来说,尽管有时您正在开创新。不要在您的设置中取得新突破-除非绝对必要!将其保存为您的应用程序。


我想强调的是,这些内置的Windows Installer解决方案以及WiX和商业工具的扩展已经由可用的最佳部署专家编写。而且,甚至更重要的是,它们已被成千上万的人使用和测试-数十亿人使用内置的MSI功能。您是否真的认为自己可以做得更好?故事的寓意:选择自己的战斗方式,并使用出色的编码技能来解决新问题,并尽可能简化部署。使用已经可以使用的东西,不要重新发明轮子。部署中的未知因素太多,无法控制的变量太多-您正在以“任何状态”和“任何语言”处理“任何计算机”。如果需要示例,请参见此处的“部署的复杂性”部分(在页面下方),仅简要介绍目标系统在您的软件包受到攻击时其状态可能不同的所有方式。每个变量都是一个自定义代码的新陷阱,从操作系统版本到应用程序范围再到恶意软件情况。清单一直在继续。正如结论在链接内容中所读到的:“部署是一个简单的概念,复杂的变量组合可能导致最神秘的错误-包括开发人员喜欢的间歇性错误。众所周知,此类错误的严重性不能被夸大了,因为它们通常无法正确调试。“


某些高级操作必须在您的设置中完成-因为它们需要提升的权限,并且您的应用程序在运行时不应要求这样做。这就是用于高级,高级系统配置的设置-接受这种复杂性,但要使用现成的解决方案!不要滚动自己的脚本和解决方案,而要使用内置的,经过良好测试的东西。您的脚本可以在韩国正确运行吗?您的自定义操作是否会受到主要的反恶意软件口径解决方案的阻碍,而您从来没有时间来测试脚本?您是否有时间编写自己的检查,以确定计算机上是否安装了某些必备的运行时-在所有语言环境和所有OS版本中都可以运行?这里潜在的错误是惊人的-有时无法正确测试。您是否有阿拉伯文,中文,韩文或日文的测试机?也许你会。但是,您是否有要测试的终端服务器?韩国的终端服务器怎么样?您是否使用MSI广告测试了您的设置?为了使高级系统管理功能正常工作,您必须使设置尽可能的简单和标准。在您自己编写任何内容之前,请花几天时间寻找现成的解决方案。记住,使用现成的解决方案,您不仅要借用他们的代码,而且要借用他们创建者的质量保证,测试和UAT,这几乎是您永远都希望重复的。


真实世界的测试是唯一重要的准绳-没有替代品。不要承受这个痛苦的世界!通过Windows Update部署的Windows中的微小变化以及您的自定义操作以多种方式中断。选择更好的战斗以利用您的技能。如果您对设计提出异议,则Windows安装程序将进行反击。


部署中有很多事情使其变得复杂-而不是像我们想要的那样愚蠢(只需复制一些织补文件),您的奋斗目标是使其尽可能简单,但不要简单。以下是部署任务列表,显示了为什么很难使软件包“足够笨”:程序安装的好处和真正目的是什么?只要有可能,请仅使用现成的结构-这是第一个轻松的胜利。您需要做的只是阅读和搜索一些内容。


最后,我将添加所有自定义操作都应该支持回滚,以便在安装后将系统恢复到原始状态失败。在现实世界中,这几乎是从未执行过的即席自定义操作(以我的经验)。相信我处理起来很复杂-我担心“回滚”一词,因为在C ++中实现MSI回滚后,西伯利亚爱斯基摩人担心“沐浴”一词。这并不是我玩过的最开心的事情,但是最终它可以正常工作。如果您希望安装程序没有太多的依赖关系,那么C ++是解决之道,众所周知,处理C ++并非易事。现成的解决方案支持开箱即用的回滚-仅需少量阅读和搜索就可以轻松实现。复杂性是的,但是您的立场更加坚定。至关重要的是:一旦在日本的计算机上发生了模糊的错误,我们就可以为社区修复工作,或者需要第三方供应商对其进行分类。


经过所有这些“一般观察”,在纯粹的技术层面上,Windows Installer中的自定义操作在实现,计划,调节和回滚方面非常复杂,因此应在绝对必要时使用(通常用于早期采用者)。进一步的复杂性是运行时要求-例如,对特定版本的.NET运行时,PowerShell或Installscript运行时(Installshield的脚本语言)的依赖关系,它具有运行时依赖关系,但是现在已经很大程度上解决了,这曾经是一个问题)。


缺少运行时要求而导致的错误可能是一个巨大的难题,要实现零收益。因此,在进行自定义操作时,我仅使用最小依赖性C ++ dll或Installscript。碰巧我在用户界面序列中使用VBScript或JavaScript进行只读自定义操作。这些仅检索数据,而无需进行系统更改。这些是不容易出错的唯一类型的自定义操作。但是,应将它们设置为不检查退出代码而运行(以防止它们在正在运行的安装程序中触发回滚或中止-通常在主要升级模式下)。


也:Windows安装程序将托管自己的脚本运行时引擎,因此,除非目标系统实际上已损坏(正常系统中不会缺少运行时),否则您可以运行活动脚本(VBScript,Javascript等)。这与托管代码自定义操作相反-目标系统此时完全没有.NET运行时是完全可能的(2018年1月)。现在,随着.NET成为Windows中真正的内置功能和必需功能(或Windows Installer本身托管的某些最低版本),这种情况将来会改变。由于某些奇怪的原因,托管的自定义操作代码仍然存在失败的问题-例如用于运行它的.NET版本错误,或者正在加载和使用的CLR版本错误,等等。我在这里的经验有限,但我认为问题很严重。不过,最终,我们都会编写我认为的托管代码自定义操作。不过,我仍将C ++用于全球发行方案。


Powershell脚本对于自定义操作特别麻烦,因为它们显然已耗尽进程,并且无法访问MSI的会话对象(来源:MSI专家Chris Painter)。 Powershell还要求安装.NET框架。我永远不会将Powershell脚本用于自定义操作-甚至不用于内部公司部署。您的来电。就像一个“样本”一样,这是该领域的专家,他表达了自己的观点(有关老年博客的文章,但如果您问我仍然很重要):不要使用托管代码来编写您的自定义操作!


我尝试编写各种自定义操作类型的优缺点摘要。坦白地说,我对它不太满意,但实际上是这样:Windows Installer在Win 10上失败,但在使用WIX的Win 7上失败。我不满意吗?对JavaScript的建议是一件事-我很少使用它,尽管它比VBScript语言更好-特别是对于错误处理-我在将Javascript与MSI结合使用时遇到了很多问题。可能是键盘和椅子之间存在问题的一种情况:-),但我认为JavaScript也有一些实际问题。尽量避免复杂地使用脚本。对于简单的事情,例如检索属性,它们似乎对我来说工作正常。但是,该领域的专家对脚本自定义操作毫不留情:请勿使用vbscript / jscript编写您的自定义操作! (Aaron Stebner),VBScript(和Jscript)MSI CustomActions吸(Rob Mensching-WiX慈善)。


我还要补充一点,自定义动作的复杂性是“愚蠢的,阴谋的复杂性”,而不是玩玩的东西。咬你陷阱您会在适当的时候发现自己犯的所有错误-继续前进(然后需要做一些解释)-这些错误通常不会立即显而易见(升级,修补,卸载,自定义操作运行时突然出现问题)在修复过程中错误地进行了调试,因为它的安装条件不正确,并且擦除了某些注册表设置,静默安装无法正常工作-由于自定义操作仅存在于用户界面序列中,因此安装不完整,您在Windows XP上存在运行时错误从未使用您的自定义操作代码进行测试,目标系统上有损坏的东西,您没有躲开自己,有人打电话给您,并告诉您您的程序包不适用于活动目录,您的程序包无法发布,失败在所有韩语和日语系统上,自我修复都会触发运行时警告,普通用户无法访问等。)一旦遇到问题,您将没有时间正确解决此问题,并且您可能必须继续采用不合标准的解决方案,以使自己在下一个障碍中领先一步。不,我自己没有经历过所有这些。实际上,在为公司部署修复第三方供应商MSI文件时,我发现了大多数这些问题。当您查看,比较,查看数百个软件包并将其适应企业标准以进行大规模部署时,您确实会发现许多潜在的错误源。老实说,除了最简单的软件包以外,所有软件包都存在严重的缺陷和错误。显然,几年前进行此类供应商设置时,您会发现自己做错了什么。猜猜什么是最重要的?当内置构造可用时,过度使用自定义操作。


更重要的是,部署固有的复杂性以及要求它可以在任何计算机上,在任何状态下的任何地方工作的问题,存在着MSI技术边界反模式的问题。 MSI技术的某些部分会导致重复部署问题,因为人们对其了解不多,有时在实际使用中也很脆弱。在此答案中有一个简短的摘要(朝下):如何更好地利用MSI文件(以及MSI的非常重要的公司收益列表-以及在实际操作中常见的常见技术问题的随机转储)世界套餐)。特别是在我写完后一个链接使我感到不理想。把它当作是什么:杂乱无章的,现实世界中的建议,没有太多其他建议。它可以识别太多问题,而无需在现场进行大量修复。


软件支持呼叫的很大一部分往往来自于部署过程中出现的问题。正如故事所言:“ ...未能正确安装您的优质软件可能接近是软件开发中最昂贵的错误。您永远都无法希望出售无法测试的软件。”上面链接的答案)。错误的自定义操作通常是导致此类问题的原因。

我认为最常见的不必要的自定义操作用法是:


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


通过自定义操作将.NET程序集安装到GAC。 Windows Installer本身完全支持此功能,而无需一行(风险)代码。


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


您可以通过自己的MSI中的自定义操作来运行必备安装程序和运行时安装程序。这应该完全不同地完成。您需要的是一个引导程序,可以按顺序启动设置及其先决条件。商业部署工具(例如Advanced Installer和Installshield)具有此功能,但是WiX等免费框架通过其Burn功能提供支持,并且还有具有此类功能的免费GUI应用程序(例如DOTNetInstaller(未经我试用))。 />

在这种特殊情况下,请相信我:通过自定义操作运行嵌入式设置最终会失败-通常会立即失败。 MSI的调度,模拟,事务,回滚和整体运行时体系结构过于复杂,因此无法实现。根据设计,一次只能运行一个MSI事务,这使事情变得非常复杂。过去,您可以将嵌入式MSI文件作为MSI中的一个概念运行,但是已弃用了–它不能正常工作。您必须按顺序运行每个设置。正确的顺序。有可用的引导程序/链接程序,可让您定义这种安装顺序。这是描述其中一些问题的答案(不要让问题标题让您失望-它涉及引导程序/链接程序和其他部署注意事项):Wix-如何在没有UI的情况下运行/安装应用程序。




6.2自定义操作替代方法


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


除了使用内置的MSI构造或WiX自定义功能外,通常可以通过对应用程序设计进行较小的更改来避免自定义操作,从而不再需要复杂的自定义操作。我以许可为例,说明如何简化部署以提高可靠性。


这是非常重要的一点,并且经常被忽略。有时,一些高质量的小时或几天的编码以及一些高质量的UAT / QA时间可以防止长期存在的部署问题。没有任何支持时间就能解决您可能会遇到的最严重的部署问题。


此答案提供了有关部署总体复杂性的模糊信息:Windows Installer和WiX的创建(底部) )。部署是一个复杂的“交付过程”,面临几个严峻的挑战:1)部署错误的累积性质; 2)调试困难; 3)影响全球目标计算机的外部因素和变量的范围几乎是无限的-结论是,必须使部署尽可能简单以便可靠。有很多干预因素使您不喜欢开发人员:间歇性错误。建议使用上面的链接以获得更充实的解释。




6.3高级自定义操作问题(超出运行时故障)


一个非常常见的自定义操作问题是它们的调度不正确,并试图通过“非提升”立即模式操作来修改系统。这些是高级MSI设计缺陷,对于看到其他方法可以正常运行的安装程序的人来说,是无法立即识别的。


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


切勿尝试使用通过设置GUI激活的即时模式自定义操作来更改系统。这似乎对管理员用户有效,但是即时模式自定义操作不会提升,因此普通用户将无法正确运行它们。此外,当在静默模式下运行安装程序时,从MSI GUI对系统所做的任何更改都将永远不会完成(然后将跳过整个GUI序列)。这是一个非常严重的MSI设计缺陷:以静默方式和交互式方式安装会导致不同的结果。关于静默安装的第10节也提到了此问题:我如何避免WiX / MSI部署解决方案中的常见设计缺陷?静默安装是企业部署软件的关键功能。在控制其部署的公司中,所有部署都是以静默方式完成的。您的安装程序GUI从未被看到过。我只有少数例外,例如可以交互进行的某些服务器部署,但是所有台式机和客户端软件均以静默方式部署。因此,不正确支持静默安装会导致MSI设置的严重缺陷和设计缺陷。请注意这个建议。对于公司接受您的软件来说,这至关重要。我建议您将某些软件从标准系统中剔除掉,​​因为它的部署对于系统来说是如此危险,以至于我们不想对其进行处理。正是由于这些问题(应用程序和设置不正确),虚拟化和沙箱(APP-V-虚拟程序包-或在虚拟机上完全可用)今天很重要。


必须将对系统进行更改的所有自定义操作插入安装顺序的“事务部分”中。此Windows Installer事务(认为数据库事务提交)在InstallExecuteSequence中的标准操作InstallInitialize和InstallFinalize之间运行,并以提升的权限运行。系统的所有更改都将在此事务中进行-其他任何事情都是错误的(但不幸的是很普遍)。在此插入的所有自定义操作也应实现相应的回滚自定义操作,如果安装失败并被回滚,这应该撤消对系统所做的任何更改。



#2 楼

应限制自定义动作的想法来自这样一个事实,即编写一个实施不佳的自定义动作很容易。实施不当的自定义操作是在安装失败的情况下无法回滚已对系统进行的更改(例如,安装Windows服务)的自定义操作。

话虽如此,如果编写了一个自定义操作,使其具有适当的回滚自定义操作,那么我认为这不是问题。

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



FoobarCustomActionImmediate-这是立即准备的自定义操作,准备了2个有效负载:


首先,要传递给FoobarCustomActionDeferred的有效负载,它指示系统更改更改以进行。
第二,传递给FoobarCustomActionRollback的有效载荷,它指示如何回滚系统以更改更改。




FoobarCustomActionRollback-这是一个回滚自定义操作,它是按顺序安排在FoobarCustomActionDeferred之前,该方法使用FoobarCustomActionImmediate提供的有效负载来在发生严重故障时回滚FoobarCustomActionDeferred所做的更改。

FoobarCustomActionDeferred-这是一个延迟的自定义操作,它使整个系统更改系统更改。

与许多其他技术一样,我认为总有一种方法可以在WiX中编写可维护性差的代码。以上面的示例为例,如果这是我想在许多不同的安装程序中使用的自定义操作,则应为开发人员提供一个包含.wxsfragment文件,以确保正确引用了所有必需的自定义操作并对其进行了排序。相反,如果将WiX代码简单地粗心地复制粘贴到许多不同的安装程序中,则错误执行自定义操作的机会就会增加。我们作为开发人员的工作是帮助代码的使用者成功,我认为WiX通过谨慎使用fragmentwixlibs来提供此功能。

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