WiX Bundle初始安装后独立MSI升级的ARP与版本冲突问题咨询
我有一个包含.NET Core运行时安装程序和产品MSI的WiX Bundle。默认情况下,“添加/删除程序(ARP)”中会显示Bundle和产品MSI两个条目。我尝试两种仅显示单个条目的方案,但均存在问题:
- 方案1:在Bundle中设置
MsiPackage的Visible属性为no,ARP仅显示Bundle条目。但如果初始用Bundle安装,后续用独立MSI升级,ARP会出现原Bundle和新产品两个条目。 - 方案2:在Bundle中设置
DisableRemove和DisableModify属性为yes,ARP仅显示产品MSI条目。但按“安装Bundle 10版本→卸载ARP中的产品MSI→安装Bundle 9版本”的流程操作时,第三步会因系统检测到已安装10版本Bundle而失败。
我们的Bundle体积为30MB,产品仅3MB,我不想每次更新都下载Bundle(因.NET已存在无需重复安装)。请问如何实现初始用Bundle安装、后续用MSI升级且避免上述问题?
补充:我想到的唯一方法是在卸载时添加自定义动作,检测MSI是否由Bundle启动,若不是则用MsiEnumRelatedProducts()查找Bundle的产品GUID并内嵌卸载,但该方法繁琐且不确定是否可行,还可能触发“安装程序已在运行”错误。
核心思路
通过让Bundle与产品MSI建立版本关联、精准控制ARP显示规则,同时处理升级和回滚场景的冲突,实现"初始Bundle安装、后续MSI轻量升级"且ARP仅显示单个条目。
1. 调整Bundle配置:隐藏自身ARP条目,仅作为依赖安装器
将Bundle标记为系统组件,彻底隐藏其ARP条目,同时确保产品MSI的安装与Bundle强关联:
<Bundle Name="MyApp Bundle" Version="1.0.0.0" Manufacturer="MyCompany" UpgradeCode="YOUR_BUNDLE_UPGRADE_CODE"> <!-- 标记Bundle为系统组件,不在ARP显示 --> <Property Id="ARPSYSTEMCOMPONENT" Value="1" /> <Chain> <!-- .NET Core运行时安装包,添加存在性检测避免重复安装 --> <ExePackage SourceFile="dotnet-runtime.exe" InstallCommand="/quiet" DetectCondition="DotNetCoreInstalled" /> <!-- 产品MSI:设置Visible=no隐藏自身ARP条目,Vital=yes确保安装失败时Bundle回滚 --> <MsiPackage SourceFile="MyApp.msi" Visible="no" Vital="yes" /> </Chain> </Bundle>
2. 优化产品MSI:关联Bundle升级逻辑,清理旧条目
让独立MSI具备检测并清理旧Bundle的能力,同时确保升级后ARP仅显示产品条目:
2.1 添加Bundle关联检测
在MSI中通过Upgrade元素关联Bundle的UpgradeCode,检测系统中是否存在旧版本Bundle:
<Upgrade Id="YOUR_BUNDLE_UPGRADE_CODE"> <UpgradeVersion Minimum="0.0.0.0" Maximum="99.99.99.99" Property="BUNDLE_INSTALLED" /> </Upgrade>
2.2 自定义动作卸载旧Bundle
添加C#自定义动作,在MSI安装前启动独立进程卸载旧Bundle(避免进程冲突):
<!-- 自定义动作配置 --> <Binary Id="CustomActions" SourceFile="CustomActions.CA.dll" /> <CustomAction Id="UninstallOldBundle" BinaryKey="CustomActions" DllEntry="UninstallBundle" Execute="immediate" /> <InstallExecuteSequence> <!-- 检测到旧Bundle时,在安装初始化前执行卸载 --> <Custom Action="UninstallOldBundle" Before="InstallInitialize">BUNDLE_INSTALLED</Custom> </InstallExecuteSequence>
自定义动作核心代码:
[CustomAction] public static ActionResult UninstallBundle(Session session) { string bundleUpgradeCode = "YOUR_BUNDLE_UPGRADE_CODE"; StringBuilder productCode = new StringBuilder(39); uint index = 0; // 枚举所有关联的Bundle产品 while (MsiInterop.MsiEnumRelatedProducts(bundleUpgradeCode, 0, index, productCode) == 0) { // 启动独立进程执行卸载,避免"安装程序已在运行"错误 Process.Start(new ProcessStartInfo { FileName = "msiexec.exe", Arguments = $"/x {productCode.ToString()} /qn", CreateNoWindow = true, UseShellExecute = false }); index++; productCode.Clear(); } return ActionResult.Success; }
2.3 卸载产品时同步清理Bundle
在MSI卸载序列中添加自定义动作,检测系统中是否仅当前产品依赖该Bundle(无其他关联产品),若是则卸载Bundle:
<CustomAction Id="UninstallBundleOnProductRemove" BinaryKey="CustomActions" DllEntry="UninstallBundleOnRemove" Execute="immediate" /> <InstallExecuteSequence> <Custom Action="UninstallBundleOnProductRemove" Before="RemoveFiles">REMOVE="ALL"</Custom> </InstallExecuteSequence>
核心逻辑:通过MsiEnumProducts检测是否有其他产品使用该Bundle的依赖,无则执行卸载。
3. 解决回滚场景冲突
调整Bundle的检测条件,当产品已被卸载时,允许安装旧版本Bundle:
<util:ProductSearch Id="CheckProductExists" ProductCode="YOUR_PRODUCT_MSI_GUID" Variable="PRODUCT_EXISTS" /> <!-- 仅当产品已安装且存在更高版本Bundle时,阻止安装 --> <DetectCondition>NOT (WixBundleUpgradeDetected AND PRODUCT_EXISTS = 1)</DetectCondition>
关键注意事项
- 确保Bundle与MSI的UpgradeCode逻辑统一,避免版本识别混乱。
- 所有卸载操作必须通过独立进程执行,禁止在MSI进程内直接调用卸载API。
- 覆盖测试所有流程:初始Bundle安装→独立MSI升级→ARP显示验证;高版本Bundle安装→产品卸载→低版本Bundle安装。
内容的提问来源于stack exchange,提问作者Jeff

