如何使用自定义Bootstrapper执行WiX升级及相关问题咨询
关于WiX自定义Bootstrapper升级的问题解答
问题2:你的WiX升级理解是否正确?
你的基础WiX升级配置是正确的,但需要补充Bundle层面的细节:
- Product ID设为
*:每次构建生成新的ProductCode,这是MSI主升级的核心要求(主升级必须使用新ProductCode、相同UpgradeCode) - 版本升级到2.0.0:版本号前三位至少有一位高于旧版本,符合WiX主升级的版本规则
- UpgradeCode保持一致:这是WiX识别同系列产品的唯一标识,确保
MajorUpgrade能定位到旧版本 MajorUpgrade标签配置:AllowDowngrades="no"防止用户安装旧版本覆盖新版本,Schedule="afterInstallInitialize"的时机合理(初始化后卸载旧版本),AllowSameVersionUpgrades="no"也符合预期(同版本无需升级)
需要注意:MajorUpgrade是处理MSI包的主升级,而你使用的是自定义Bootstrapper(BA),Bundle层面的升级还需要单独协调BA的流程——这也是你当前问题的核心所在。
问题1:如何让主BA知晓旧包卸载完成并继续安装新版本?
你当前的问题是:旧包卸载完成后,主BA直接退出了,没有衔接后续的安装流程。原因是UpgradeUninstall类的OnApplyComplete方法直接调用了BootstrapperDispatcher.InvokeShutdown(),导致整个BA进程终止。
下面提供两种可行的解决方案:
方案1:给UpgradeUninstall添加回调,衔接安装流程
改造UpgradeUninstall类,增加卸载完成的事件通知,让主BA在卸载结束后重新启动安装流程:
- 修改
UpgradeUninstall.cs,添加卸载完成事件:
public class UpgradeUninstall { private BootstrapperApplicationModel _bootStrapperModel; // 新增卸载完成事件 public event EventHandler UninstallCompleted; public UpgradeUninstall(BootstrapperApplicationModel model) { _bootStrapperModel = model; } public void PerformSequence() { this.WireUpEventHandlers(); _bootStrapperModel.BootstrapperApplication.Engine.Detect(); } // ... 保留其他原有方法 ... private void OnApplyComplete(object sender, ApplyCompleteEventArgs e) { // 触发卸载完成事件 UninstallCompleted?.Invoke(this, EventArgs.Empty); BootstrapperDispatcher.InvokeShutdown(); } }
- 修改
Bootstrapper.cs的Run方法,订阅事件并衔接安装:
protected override void Run() { BootstrapperDispatcher = Dispatcher.CurrentDispatcher; try { _model = new BootstrapperApplicationModel(this); var uninstall = new UpgradeUninstall(_model); if (uninstall.IsUpgradeUninstallation()) { // 订阅卸载完成事件,触发安装流程 uninstall.UninstallCompleted += (s, args) => { this.WireUpEventHandlers(); _model.BootstrapperApplication.Engine.Detect(); Dispatcher.Run(); }; uninstall.PerformSequence(); } else { // 正常显示安装/卸载UI this.WireUpEventHandlers(); _model.BootstrapperApplication.Engine.Detect(); Dispatcher.Run(); } } catch (Exception ex) { _model.BootstrapperApplication.Engine.Log(LogLevel.Error, $"运行错误: {ex.Message}"); BootstrapperDispatcher.InvokeShutdown(); } }
方案2:整合卸载与安装流程到主BA事件处理中
不再拆分UpgradeUninstall,直接在主BA的事件链中处理“先卸载旧包、再安装新包”的逻辑:
- 在主BA中记录旧Bundle信息:
private Bundle _oldUpgradeBundle; private void WireUpEventHandlers() { // ... 保留其他事件订阅 ... _model.BootstrapperApplication.DetectRelatedBundle += OnDetectRelatedBundle; _model.BootstrapperApplication.DetectComplete += OnDetectComplete; _model.BootstrapperApplication.ApplyComplete += OnApplyComplete; } private void OnDetectRelatedBundle(object sender, DetectRelatedBundleEventArgs e) { // 标记需要升级的旧Bundle if (e.Action == RelatedBundleAction.Upgrade) { _oldUpgradeBundle = e.Bundle; } }
- 在
DetectComplete中先计划卸载旧包:
private void OnDetectComplete(object sender, DetectCompleteEventArgs e) { if (_oldUpgradeBundle != null) { // 先计划卸载旧Bundle _model.BootstrapperApplication.Engine.Plan(LaunchAction.Uninstall, _oldUpgradeBundle.BundleId); } else { // 正常计划安装 _model.PlanAction(LaunchAction.Install); } }
- 在
ApplyComplete中触发新版本安装:
private void OnApplyComplete(object sender, ApplyCompleteEventArgs e) { if (_oldUpgradeBundle != null) { // 旧包卸载完成,清除标记并计划安装新版本 _oldUpgradeBundle = null; _model.PlanAction(LaunchAction.Install); _model.ApplyAction(); } else { // 安装完成,关闭UI BootstrapperDispatcher.InvokeShutdown(); } }
额外注意点
你提到的“出现另一个BA实例”的处理逻辑是正确的:通过IsUpgradeUninstallation()判断旧Bundle是静默卸载场景(Display.None/Display.Embedded),直接执行卸载不显示UI,避免弹出旧BA界面。
另外请务必确保Bundle的UpgradeCode也和第1版保持一致——Bundle层面的升级同样依赖UpgradeCode识别同系列包,很多开发者容易忽略这一点。
内容的提问来源于stack exchange,提问作者Mark U
相关产品推荐
相关产品推荐

