【问题标题】:Wix Installer : Setting component condition property when doing a MSIEXEC admin install at command lineWix 安装程序:在命令行执行 MSIEXEC 管理员安装时设置组件条件属性
【发布时间】:2018-03-23 12:45:04
【问题描述】:

我们的产品有三种类型/口味,但只有一种 MSI 是用 WiX 编写的。当我们构建安装程序时,我们通过定义的常量传递风味:

Call MSBUILD.bat ..\MSIs\CoreProduct\OurProduct.sln /p:DefineConstants="FLAVOUR=%_Flavour%"

并且常量是在 Visual Studio 中的 Build -> Define preprocessor variables as FLAVOUR=50 下设置的。构建过程,传入值 50、200 或 LITE 作为风味。

在 WiX 代码中,我们的组件有很多条件,根据风格告诉它要安装哪个文件;例如

      <Component Id="cmp7F45920B1AA100729BAE37FC846B3FC5" Guid="*">
    <File Id="fil238A776D9294E14671E012472F9F7196"
          KeyPath="yes"
          Source="$(var.MenusPath)\ClientListView 200.r5m"  
    <Condition>$(var.FLAVOUR)=200</Condition>
  </Component>

      <Component Id="cmp8BFF42B232724DC4BA5B8F87994DEF21" Guid="*">
    <File Id="fil808D6428D67248DDB8CA65DBC5978283" 
          KeyPath="yes" 
          Source="$(var.MenusPath)\ClientListView Lite.r5m"
    <Condition>$(var.FLAVOUR)=LITE</Condition>
  </Component>

因此,如果 FLAVOR 为 LITE,上面的示例将安装一个名为“ClientListView Lite.r5m”的文件,如果 FLAVOR 为 200,它将安装一个名为“ClientListView 200.r5m”的文件。

这一切都按预期工作并且已经做了多年!

但是现在,我们有一个网络版本的产品,我们需要一个 zip 文件来包含文件夹结构和为每种口味安装的文件。我发现您可以使用 MSIEXEC 和 /a 参数在命令行运行 msi,然后将所有已安装的内容重定向到文件夹中,并认为这正是我想要的……但可惜它不能像我一样工作会预料到的。

它似乎在做的是运行 MSI 并将文件提取到目标文件夹中,但它忽略了味道,所以你最终得到了“ClientListView Lite.r5m”和“ClientListView 200.r5m”文件被提取到文件夹中;这显然不是我想要的。

阅读 MSIEXEC 上的文档后,您似乎可以传入公共属性的值,例如 msiexec.exe /a "C:\Example.msi" MY_PROP="myValue" - 所以我认为这可能对我有帮助;所以在我的 WiX 代码中我添加了以下行:

    <Property Id='PRODTYPE' Value="$(var.FLAVOUR)"/>

然后将我的组件条件更改为:

  <Component Id="cmp7F45920B1AA100729BAE37FC846B3FC5" Guid="*">
    <File Id="fil238A776D9294E14671E012472F9F7196"
          KeyPath="yes"
          Source="$(var.MenusPath)\ClientListView 200.r5m"  
    <Condition><![CDATA[PRODTYPE=200]]></Condition>
  </Component>

  <Component Id="cmp8BFF42B232724DC4BA5B8F87994DEF21" Guid="*">
    <File Id="fil808D6428D67248DDB8CA65DBC5978283" 
          KeyPath="yes" 
          Source="$(var.MenusPath)\ClientListView Lite.r5m"
    <Condition><![CDATA[PRODTYPE=LITE]]></Condition>
  </Component>

但虽然编译正常,但通过以下方式运行它:

msiexec /a OurProduct.msi /qb PRODTYPE=200 TARGETDIR="C:\InstalledFiles200"

仍然为 200 和 LITE 风格提取两个文件,而我只想要 200 的文件。

所以,我是在尝试做一些不可能的事情......还是我做错了什么 - 任何帮助都将不胜感激,因为可以替代在批处理文件中模仿该过程来创建我的 zip;会很可怕!!

干杯,

克里斯。

【问题讨论】:

    标签: wix conditional-statements admin windows-installer


    【解决方案1】:

    您的预处理器使用

    当我意识到您只需要一个 MSI(至少一次 - 它似乎),所以我跳过了它。我通常使用这样的结构来编译来自同一个 WiX 源的各种 MSI 文件。我已经抛弃了我在下面写的内容并进行了一些重新散列 - 没有太多评论。我稍后会检查它。

    在您的情况下,我可能遗漏了一些东西,但我看不到 ?if? 语句在安装时如何工作 - 这是一个编译时构造。它在编译和链接之前预处理您的 WiX 源文件。因此听起来您实际上已经编译了三个不同版本的 MSI 文件,然后在每个版本上运行 admin install? 如果是这种情况,那么使用管理映像是无关紧要的,因为您的整个 MSI 不包含任何内容,只包含您在 ?if? 中包含的组件 - 无需传入任何版本属性规范。


    设置风味

    当我需要创建不同的设置风格产品版本时,我使用的常规技术方法如下:

    1. Preprocessor constructs: 用于在编译时编译不同风格或版本(也包括不同语言版本)的 MSI。您提到的?if? 声明就是这样一种构造。如上面直接提供的链接所示,还有其他几个。与您相关的是条件语句ififdefifndefelseelseifendif)和可能的include 语句。

      • 因此,总体信息是预处理器构造允许您从单个 WiX XML 源构建多种不同风格的 MSI 文件

      • 在您的情况下,这将产生一个单个 WiX XML 源三个 MSI 文件 - 每个文件都用于不同版本的应用程序。都相似,但只有他们需要的组件。

      • 通过/i 运行安装或通过/a 运行管理映像(文件提取)只会产生您添加到该安装风格的组件,但您没有您所说的单个 MSI - 但三个不同取决于你的编译方式。

      • 我更喜欢使用 ?include? 语句来包含 WiX 片段文件,而不是像您提到的那样直接使用 ?if? 来调节组件。在“预处理器构造”部分的底部有一个差异示例。

    2. MSI 功能(正如 PhilDW 回答的那样)您还可以依靠 MSI 功能来提供不同风格的单一设置。功能用于将 MSI 分隔为各种用户可选择的安装项目(其中一些您可以强制设置)。

      • 功能允许以不同的风格安装单个 MSI - 同时包含每种风格所需的所有组件。调理组件可以达到类似的效果。
      • 调节组件类似于特征操作,但在更原始和基本的层面上工作。这些不是用户可选择的安装单元,而是要安装和隐藏在用户视图之外的产品的基本和原子的零碎部分 - 而功能是用户可选择的并且通常对用户可见。
      • 要安装哪些功能可以通过自定义操作由用户调整以编程方式操作(以覆盖用户规范并强制执行自动神奇的技术设计 - 例如)。

        • 用户功能控制

          • 用户可以通过在大多数设置(不是全部)中找到的设置的 FeatureTree 对话框以交互方式控制要安装的功能。通常这涉及选择进行“自定义”安装。

          • 1234563 /p>
        • 有时您可以在安装过程中设置自定义属性,这将触发基于指定“版本”安装的标准功能群,这将我们带到下一点。

      • 程序化功能控制

        • 添加指向编程功能控制答案的链接:How to install feature based on the property set in custom action?

        • 如上所述,您可以在运行时使用安装程序中的ADDLOCAL propertyREMOVE property,通过命令行操作要安装的功能。 为此,您可以使用自定义操作

        • 还有一个INSTALLLEVEL property,它与功能的安装状态相关。每个功能都有一个安装级别,它可能会受到Condition table 中设置的条件的影响。

          • 所以Condition表可以用来根据条件表达式修改Feature表中任意条目的选择状态。

          • 换句话说,您可以使用它来根据条件将功能设置为默认安装或不安装。

        • 除了 INSTALLLEVEL 概念之外,您还可以使用自定义操作来控制特征选择状态。

          • 今天这比功能条件更可靠,因为许多应用程序打包程序通常会最大化 INSTALLLEVEL 属性以强制安装所有功能。 这是错误的,因为如果某些功能与您运行的操作系统不兼容,则可能不需要安装它们。我试图将这一点传达给许多充耳不闻的人。
        • 使用这些编程结构,您的设置可以根据用户输入的序列号更改要安装的功能选择状态。

        • 您的设置还可以根据您正在运行的操作系统确定不应该安装某个功能(例如,不要安装Tablet OS 功能)。

        • 以编程方式更改特征选择可能有许多技术和实际原因。

    3. 请务必注意,总体结果是功能选择可以通过命令行或由用户设置,然后您的设置会出于技术原因进行一些“幕后”修改。

    4. 只是要指出:以编程方式进行安装的功能通常是隐藏在视图之外的(但仍然可以通过命令行覆盖 - 这可能是一个问题)。

      李>
    5. 有关功能、组件和安装设置自定义的更多信息,请点击此处:How to make better use of MSI files

    6. Setup.exe 启动器:以不同风格进行复杂部署的“现代”方式可以是使用 WiX 的刻录功能来编译安装在不同的“集合”产生不同的安装状态。

      • 我觉得这对于一般用途来说太复杂了,但它肯定是可能的。我认为有些人觉得它更容易,因为功能操作较少。也许我只是缺乏这方面的经验。
      • 好处是安装速度更快的更小的 MSI 文件,您可以更新单个 MSI 文件并创建新的 setup.exe 包装器,然后对整个解决方案进行全面 QA,而无需重新构建所有设置。
      • 在我的世界里,一个单一的、更新的 MSI 无论如何都需要一个完整的 QA,所以我自己并不总是购买这些“简单的论点”。 每个发布周期都有风险,因此会增加总风险。不过,能够重建一个小型设置并保持大型设置稳定,这可能很棒。

    实际使用

    我更喜欢尽可能少的设置风格,但大多数时候我最终会使用预处理器构造来为不同的语言版本创建不同的设置(英语、德语、俄语)以及不同的产品版本(企业版、专业版、标准版)。我通常将它们都设置为共享升级代码并且不能并排安装。

    我觉得单个 MSI 可以获得更多的 QA 资源,因此会更好地进行测试。 但是,如果设置非常大,这种单个 MSI 方法通常会失效 - 在这种情况下,我喜欢拆分他们。我还喜欢为每种语言 for business- and real-world practical reasons explained here 进行单独设置。实际和法律要求(许可)也可能要求编译相同 MSI 设置的特殊版本。我还被要求为 OEM 供应商构建新的 MSI 风格。

    现在是一个令人惊讶的结论(你从来没有要求过 :-)):话虽如此,在理想的世界中,我喜欢安装所有功能而不需要任何设置中的技术大惊小怪,并使用序列号确定应在应用程序中激活哪些应用程序功能和模块。我还喜欢将序列密钥验证放在应用程序中,而不是设置中。 原因我希望设置尽可能原始,以确保正确安装应用程序而不会出现高部署错误百分比。因此,我在部署中寻找复杂性以消除部署支持问题。 可靠的部署对于产品的成功至关重要。安装后,您的应用程序可以在更可调试和可预测的环境中启动(没有条件、模拟或排序问题),并以交互方式和有意义的方式向用户报告任何错误 - 他们可以致电支持,更有希望成功解决任何问题如果设置只是因为神秘的非交互式错误消息(在系统的事件日志中)而被炸毁,情况会怎样。

    一旦您获得了一个好的、简单的部署解决方案,您作为发布经理和设置开发人员的工作应该扩展到处理应用程序的启动顺序、文件配置和整体配置,以及我认为您可能想要的支持和调试功能添加到应用程序(应用程序的自我检查以及记录和自动收集信息以发送给支持)。尝试扩大您对应用程序本身的责任和影响,以使部署“尽可能原始”。从长远来看,任何可以在应用程序中而不是在设置中编码的东西都会更可靠(并且更容易调试)。

    回到现实:对于真正的部署,你会得到一堆不规则版本控制方案的文件,通过慢速网络共享传递给你,其中包含一堆不连贯的指令离岸并按小时付费的开发人员(您可以确定他也感到痛苦:-))。而且他们会对你的设置有一些部分疯狂的要求来做魔术来掩盖他们不太理想的设计(这是他们付出的全部)。这不是咆哮,这是现实 - 说起来很遗憾(好吧,这是咆哮)。我们必须努力改进应用程序的整体设计,以使部署不易出错。魔术确实可以在设置中完成,但由于the unavoidable and unruly complexity of deployment(最重要的是:1) 错误是累积的 - 每个版本都会增加风险和机会来破坏安装的可维护性(有时您必须把一切都拉回来重新开始——代价高昂),2) 目标系统处于极其多样化和不可预测的状态(任何状态、任何语言、任何操作系统版本、任何硬件、任何恶意软件等......),@当您无法以交互方式访问问题系统时,987654353@ 调试非常困难 - 有关更多详细信息,请参阅链接答案的底部。我将添加第二个中的 4th issue你能保证在充满恶意软件的机器上进行部署吗?这样的系统无法支持和调试 - 只需选择你无法修复的关键问题即可。如果您的软件包在此类恶意软件系统上失败,这是一个自然错误 p这是不可避免的百分比

    总的观点是显而易见的:我们无法解释这些未知数 - 垃圾输入,垃圾输出(对于听起来如何道歉) - 每次部署时都会有一个错误百分比 -即使是完美的包裹。一定要抓住所有可用的并且总是有价值的 QA 人员,并教他们设置测试(测试所有安装模式、测试卸载和与其他应用程序的交互、测试真实世界的升级方案、测试特定的高级设置许可证检查等功能...)。获得你能得到的所有帮助,并重视他们的贡献。如果不出意外,作为同谋:-)。说真的:未能有效利用可用的 QA 资源并帮助他们在部署测试中进行培训是我作为发布经理和设置开发人员最大的疏忽之一。可以肯定的是,您很快就会欠您的 QA 人员一瓶 Horílka - 或绿茶或 Rooibos 茶 - 取决于您在世界的哪个位置 :-)。


    预处理器构造

    我可能会使用 预处理器构造 ?if??include? 根据您在顶部的 ?define? 语句中定义的版本选择性地包含 WiX XML 源代码/标记的不同部分样本:

    <?define EditionType = “LITE” ?>
    
    <!-- You can put your edition-specific components in an include file  -->
      <?if $(var.EditionType) = "200" ?> 
        <?include "200Features.wxi" ?>
      <?endif ?>
      <?if $(var.EditionType) = "LITE" ?>
        <?include "LiteFeatures.wxi" ?>
      <?endif ?>
    

    除了使用包含文件之外,您还可以在主源代码中内联执行此操作(这可能是您所做的):

     <?if $(var.EditionType) = "200" ?> 
        <Component>
           <File Source="$(var.MenusPath)\ClientListView 200.r5m" /> 
        </Component>
    <?endif?>
    <?if $(var.EditionType) = "LITE" ?>
        <Component>
           <File Source="$(var.MenusPath)\ClientListView Lite.r5m" />
        </Component>    
    <?endif?>
    

    在上述选项中,我更愿意将特定于版本的组件保存在单独的包含文件中(第一个选项) - 这样您的 WiX 源中的“线路噪音”就会更少(预处理器结构的重复次数更少)。

    包含文件基本上只是 WiX XML 文件,它们就像 C++ 头文件一样在编译时包含到父 WiX XML 文件中。 正如 Phil 所说,我会为不同的版本使用不同的功能名称,因此您在 200Features.wxi 包含文件中定义了一个功能 200Features,但您不需要使用功能 - 尽管强烈建议您使用。

    还要注意the fragment element 的 WiX 概念。本质上是一种交叉引用 WiX 源文件中的内容的方法。 An example here.

    我应该提一下,MSI 中 merge modules 的原始概念提供了一种在不同设置之间共享组件的不同方式,但我更喜欢包含文件的方法。合并模块在某种程度上感觉像 COM 对象(二进制 blob 作为一个版本化的整体包含在内),而包含文件似乎更具动态性,因为您可以为每个构建获得链接到的最新文件,而无需任何合并模块的重新构建和版本控制。毫无疑问,有些人会认为这是非常错误的。

    【讨论】:

    • 哇,多么好的答案!非常感谢您花时间写这篇文章,我学到了很多东西!我不使用功能,因为这 3 种风格的功能大致相同(当然没有用户可以选择不安装的任何可选部分),但它们有不同的菜单文件、不同的报告等 - 但要点代码/业务逻辑是相同的——我的意思是,它们使用相同的 dll 并且其中有逻辑,决定根据风格做不同的事情。但是说我确实喜欢使用包含文件的想法!谢谢。
    【解决方案2】:

    msiexec 命令行中的 /a 开关不是通常意义上的安装。它实际上只是将文件解压缩到某个位置。如果您想要实际安装,您必须使用 /i 或只需双击 MSI 文件。这将为您提供正确的完整安装,并在 Programs&Features 等中添加一个条目。

    安装内容的选择通常通过将设置划分为功能来处理,每个功能都包含一组包含所需功能的组件。在 WiX UI 中,您可以看到一个对话框来选择功能,维护模式将让您返回并更改它们。在命令行安装中,您只需说 /i [msi file] ADDLOCAL=Feature1,Feature2 等等。如果您真的想使用“风味”,那么您可以在内部将其转换为 ADDLOCAL 列表。

    【讨论】:

      【解决方案3】:

      感谢您提供的信息,但我认为您没有理解我想要实现的目标;我不想通过 /i 运行安装程序,因为正如您所说,这实际上会安装文件并更改注册表等。我想做的是运行安装程序两次 - 一次用于风味 200,然后再次用于风味 Lite;所以我最终会得到两个文件夹,其中包含“将”安装的文件,如果我使用 /i 运行它。显然我不能用 /i 运行它两次,因为我第二次运行它时,它会卸载第一个。

      无论如何,我发现在管理员“安装”期间组件内的条件被忽略,而 ?If?语句不会被忽略 - 不知道为什么会这样,但我现在已经删除了所有条件语句并用 IF 语句替换它们 - 所以现在我可以做我想做的事 - 使用 /a 运行它两次,每个文件夹将包含该风格独有的文件。

      【讨论】:

      • 我完成并提交并回答了我之前放弃的答案。我不是 100% 确定我完全理解您的需求,但我试图澄清从同一 WiX 源提供不同风格的 MSI 文件的问题。您使用管理安装的方法可能有效,但从概念上讲,如果您每次编译一个新的 MSI 文件然后解压缩到管理映像,这似乎不太好。相反,您应该编译相同 MSI 的三个版本并将它们交付给用户 - 管理员安装旨在让最终用户提取您的安装文件并创建网络安装点。
      猜你喜欢
      • 1970-01-01
      • 2015-11-29
      • 1970-01-01
      • 2023-01-31
      • 1970-01-01
      • 2012-12-29
      • 1970-01-01
      • 1970-01-01
      • 2020-06-04
      相关资源
      最近更新 更多