WiX安装前卸载旧NSIS应用:安装目录为空问题排查
问题与解决方案:WiX MSI静默卸载NSIS旧版本时的同步问题
背景
我们计划用WiX制作的MSI替换旧NSIS安装包,实现新版本安装时自动静默卸载旧版本,当前WiX配置如下:
<!-- Custom action to uninstall old app if it's already installed --> <!-- 1.Check if old app is installed by checking if it exists in the registry --> <Property Id="OLDAPPUNINSTALLERCOMMAND"> <RegistrySearch Id="OldAppUninstallerRegistrySearch" Type="raw" Root="HKLM" Key="SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\12345" Name="QuietUninstallString" Win64="yes"/> </Property> <!-- 2. Create a custom action to uninstall old app --> <SetProperty Id="RemoveOldApp" Value="[OLDAPPUNINSTALLERCOMMAND]" Sequence="execute" Before="RemoveOldApp" /> <CustomAction Id="RemoveOldApp" BinaryKey="WixCA" DllEntry="WixQuietExec64" Execute="deferred" Impersonate="no" Return="check" /> <!-- 3. Schedule the custom action after installer initialisation, if old app was found --> <InstallExecuteSequence> <Custom Action="RemoveOldApp" After="InstallInitialize">RemoveOldApp</Custom> </InstallExecuteSequence>
遇到的问题
- 无旧应用时,新应用安装完全正常
- 存在旧应用时,卸载完成后新应用虽出现在添加/删除程序(ARP)中,但安装目录为空,仅手动修改安装后文件才会显示内容
- NSIS卸载未阻塞WiX流程:NSIS卸载弹窗与安装结束弹窗同时出现,WiX提前完成安装流程,而NSIS卸载仍在后台运行
- 根因:NSIS卸载器会将自身复制到临时目录,携带参数
_?=ABC执行后立即返回原进程,导致WiX误以为卸载已完成,提前继续安装步骤,引发文件写入冲突
解决方案
针对NSIS卸载的异步返回机制,需让WiX等待卸载进程(包括其临时子进程)完全结束后再继续安装,以下是两种可行方案:
方案1:使用批处理脚本等待进程结束
- 创建批处理脚本
WaitForNSISUninstall.cmd,内容如下:
@echo off :: 执行NSIS卸载命令 %~1 :: 等待NSIS相关进程结束(替换为实际旧卸载器的进程名) :WAIT_LOOP tasklist | find /i "nsis.exe" >nul if errorlevel 1 goto UNINSTALL_DONE timeout /t 1 /nobreak >nul goto WAIT_LOOP :UNINSTALL_DONE
注意:若旧NSIS卸载器的进程名不是nsis.exe,需替换为实际进程名。
- 修改WiX配置,调用该批处理执行卸载:
<!-- 1. 检查旧应用卸载命令 --> <Property Id="OLDAPPUNINSTALLERCOMMAND"> <RegistrySearch Id="OldAppUninstallerRegistrySearch" Type="raw" Root="HKLM" Key="SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\12345" Name="QuietUninstallString" Win64="yes"/> </Property> <!-- 2. 设置自定义动作参数,调用批处理脚本 --> <SetProperty Id="RemoveOldApp" Value=""[TempFolder]WaitForNSISUninstall.cmd" "[OLDAPPUNINSTALLERCOMMAND]"" Sequence="execute" Before="RemoveOldApp" /> <!-- 3. 自定义动作执行批处理 --> <CustomAction Id="RemoveOldApp" BinaryKey="WixCA" DllEntry="WixQuietExec64" Execute="deferred" Impersonate="no" Return="check" /> <!-- 4. 调度自定义动作:仅当旧应用存在且是首次安装时执行 --> <InstallExecuteSequence> <Custom Action="RemoveOldApp" After="InstallInitialize">(OLDAPPUNINSTALLERCOMMAND AND NOT Installed)</Custom> </InstallExecuteSequence>
需将WaitForNSISUninstall.cmd添加到WiX的文件组件中,确保安装时复制到临时目录或安装目录。
方案2:使用C#自定义动作等待进程
编写C#自定义动作,直接调用卸载命令并等待所有相关进程结束:
using System; using System.Diagnostics; using Microsoft.Deployment.WindowsInstaller; public class CustomActions { [CustomAction] public static ActionResult WaitForNSISUninstall(Session session) { string uninstallCmd = session["OLDAPPUNINSTALLERCOMMAND"]; if (string.IsNullOrWhiteSpace(uninstallCmd)) { return ActionResult.Success; } // 拆分命令行和参数 int argStartIndex = uninstallCmd.IndexOf(' '); string exePath = argStartIndex > 0 ? uninstallCmd.Substring(0, argStartIndex) : uninstallCmd; string arguments = argStartIndex > 0 ? uninstallCmd.Substring(argStartIndex + 1) : string.Empty; // 启动卸载进程 using (Process uninstallProcess = Process.Start(new ProcessStartInfo(exePath, arguments) { CreateNoWindow = true, WindowStyle = ProcessWindowStyle.Hidden, UseShellExecute = false })) { uninstallProcess.WaitForExit(); } // 等待可能存在的NSIS临时子进程 foreach (Process childProcess in Process.GetProcessesByName("nsis")) { childProcess.WaitForExit(); } return ActionResult.Success; } }
编译该自定义动作后,在WiX中引用并替换原有的WixQuietExec64动作:
<Binary Id="CustomActions.dll" SourceFile="path/to/CustomActions.dll" /> <CustomAction Id="RemoveOldApp" BinaryKey="CustomActions.dll" DllEntry="WaitForNSISUninstall" Execute="deferred" Impersonate="no" Return="check" /> <InstallExecuteSequence> <Custom Action="RemoveOldApp" After="InstallInitialize">(OLDAPPUNINSTALLERCOMMAND AND NOT Installed)</Custom> </InstallExecuteSequence>
内容的提问来源于stack exchange,提问作者Rajveer
相关产品推荐
相关产品推荐

