You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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:使用批处理脚本等待进程结束

  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,需替换为实际进程名。

  1. 修改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="&quot;[TempFolder]WaitForNSISUninstall.cmd&quot; &quot;[OLDAPPUNINSTALLERCOMMAND]&quot;"
               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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 09:14:55