实现ExePackage与其他MSI的依赖,保障卸载流程正常完成
Wix Bundle 卸载场景下服务处理ExePackage依赖问题解决
背景
项目包含服务器、客户端、微服务托管三个独立组件,各组件打包为单独MSI。其中服务器和微服务托管组件会安装Windows服务,且服务器服务依赖微服务托管服务。所有组件通过Bundle.exe链式部署,用户可选择安装客户端、服务器或两者。更新采用MajorUpgrade实现,要求更新失败时能回滚至先前版本。
问题起因
Wix v3的Bundle回滚逻辑不符合预期:仅回滚失败的MSI,已安装的前置MSI会被移除。因此采用两个变通方案:
- 优先安装最易出错的组件
- 通过
ExePackage手动处理服务器服务的启停,避免MSI间的依赖配置限制
现有实现代码
Bundle.wxs 链式部署配置
<Chain> <PackageGroupRef Id="SqlExpress"/> <PackageGroupRef Id="vcredist"/> <!-- 其他依赖组件 --> <PackageGroupRef Id="ServiceHandlingStop" /> <PackageGroupRef Id="Server"/> <PackageGroupRef Id="MicroServiceHost"/> <PackageGroupRef Id="ServiceHandlingStart" /> <PackageGroupRef Id="Client"/> </Chain>
服务处理片段
<Fragment> <PackageGroup Id="ServiceHandlingStop"> <ExePackage Id="ServiceHandlingStopCall" Compressed="yes" DisplayName="Service Management" SourceFile="redist\Runner.bat" InstallCommand="net stop WCFHostService" UninstallCommand="net start WCFHostService" DetectCondition="WixBundleInstalled = 1 AND InternalInstallType <> "Client"" InstallCondition="InternalInstallType <> "Client"" Permanent="no" Vital="no" PerMachine="yes"> <ExitCode Behavior="success" /> </ExePackage> </PackageGroup> <PackageGroup Id="ServiceHandlingStart"> <ExePackage Id="ServiceHandlingStartCall" Compressed="yes" DisplayName="Service Management" SourceFile="redist\Runner.bat" InstallCommand="net start WCFHostService" UninstallCommand="net stop WCFHostService" DetectCondition="WixBundleInstalled = 1 AND InternalInstallType <> "Client"" InstallCondition="InternalInstallType <> "Client"" Permanent="no" Vital="yes" PerMachine="yes"> <ExitCode Value="0" Behavior="success" /> <ExitCode Behavior="error" /> </ExePackage> </PackageGroup> </Fragment>
预期目标
- 仅安装服务器组件时,在微服务托管组件安装完成后启动服务器服务
- 更新失败回滚后自动启动服务器服务
- 卸载时无需关注服务状态
当前问题
卸载Bundle时若意外终止,部分MSI可能残留。再次启动卸载时,Burn会执行ServiceHandlingStartCall,但此时服务器MSI已卸载,服务不存在,net start命令失败。由于该ExePackage设置了Vital=yes,导致卸载流程终止,用户无法完全移除产品。
解决方案建议
1. 调整ExePackage的条件与退出码处理
通过Wix内置属性检查服务是否存在,并区分安装/更新与卸载动作的行为:
<PackageGroup Id="ServiceHandlingStart"> <ExePackage Id="ServiceHandlingStartCall" Compressed="yes" DisplayName="Service Management" SourceFile="redist\Runner.bat" InstallCommand="net start WCFHostService" <!-- 卸载时不执行实际服务操作 --> UninstallCommand="echo Skip service operation on uninstall" <!-- 仅当服务存在且非客户端安装时检测 --> DetectCondition="WixBundleInstalled = 1 AND InternalInstallType <> "Client" AND ServiceExists(WCFHostService)" <!-- 安装/更新时需服务存在才执行;卸载时允许跳过 --> InstallCondition="InternalInstallType <> "Client" AND (WixBundleAction <> 3 OR ServiceExists(WCFHostService))" Permanent="no" <!-- 仅在安装/更新时设为Vital,卸载时不阻塞流程 --> Vital="!(wix.WixBundleAction <> 3)" PerMachine="yes"> <ExitCode Value="0" Behavior="success" /> <!-- 卸载时,服务不存在的错误码视为成功 --> <ExitCode Value="2" Behavior="success" Condition="WixBundleAction = 3" /> <!-- 安装/更新时其他错误视为失败 --> <ExitCode Behavior="error" Condition="WixBundleAction <> 3" /> </ExePackage> </PackageGroup>
核心调整点:
- 使用
ServiceExists(WCFHostService)检查服务是否存在,避免执行无效命令 - 卸载时替换
UninstallCommand为无操作命令 - 动态设置
Vital属性,仅在安装/更新时阻塞流程 - 针对卸载动作单独处理退出码,服务不存在的错误不终止卸载
2. 由Windows服务管理器自动处理依赖(推荐)
放弃Bundle层面的手动服务启停,在服务器MSI的ServiceInstall中配置服务依赖:
<!-- 服务器MSI中的ServiceInstall节点 --> <ServiceInstall Id="WCFHostServiceInstall" Name="WCFHostService" Type="ownProcess" Start="auto" ErrorControl="normal"> <!-- 添加对微服务托管服务的依赖 --> <Dependency Id="MicroServiceHostDependency" Name="MicroServiceHostServiceName" /> </ServiceInstall>
这种方式让Windows服务管理器自动处理启动顺序:服务器服务会等待依赖的微服务托管服务启动后再启动,无需Bundle介入。同时MSI的ServiceControl可以正常处理服务的启停,避免Bundle中ExePackage带来的卸载问题。
3. 优化ServiceHandlingStop的卸载逻辑
同样调整ServiceHandlingStop的UninstallCommand和条件,避免卸载时执行无效的服务启动命令:
<ExePackage Id="ServiceHandlingStopCall" ... UninstallCommand="echo Skip service start on uninstall" DetectCondition="WixBundleInstalled = 1 AND InternalInstallType <> "Client" AND ServiceExists(WCFHostService)" ...> </ExePackage>
内容的提问来源于stack exchange,提问作者ManuHArt
相关产品推荐
相关产品推荐

