NSIS通过路径杀死指定进程解决方法

当软件安装时,如果有特定的软件正在运行,则新软件无法对其进行覆盖。同样当软件卸载时,如果其正在运行,则有残留的文件删不干净。所以便出现了在安装卸载中杀死指定进程的需求。


一、踩坑记录

1. FindProcDLL和KillProcDLL失效

首先说明!!!网上的大部分例子都是使用NSIS官方下载的插件:FindProcDLL和KillProcDLL,但是基本上都是2012年左右发布的解决办法,在新版的NSIS已经失效了!!!目前使用的NSIS基本都是v3.0以后的版本,但是该插件在v2.46版本之后就不工作了。
在这里插入图片描述
在这里插入图片描述
我在没有看到这条消息之前,还在官方下载了FindProcDLL和KillProcDLL插件并放入NSIS插件列表,最终测试并不好使,并不能找到并杀死进程。
在这里插入图片描述

2. NSIS转义符

在NSIS中定位到指定进程路径时,需要有引号,或者是在PowerShell中用$_.Path查找路径时,这个$同样也是NSIS中的特殊字符,所以均存在转义问题。下边是常见的特殊字符转义。
在这里插入图片描述

3. NSIS中调用PowerShell的Get-Process无法找到所有进程

我最初是在PowerShell中调用如下代码进行杀死进程的,在PowerShell里是可以检测到指定程序并杀死生效的。

Get-Process | Where-Object { $_.Path -eq "D:\Work\TheiaIDE_Test\Clash Test\Clash for Windows.exe" } | Stop-Process -Force

在这里插入图片描述

但是,NSIS调用powershell脚本,用同样的语句,是检测不到指定进程的!!!
在这里插入图片描述
如上图,我在ps1文件中让输出所有检测到的路径到D:\temp\ps_log.txt文件中(这个文件需要自己先手动创建,否则Powershell执行失败),但是没有我需要杀死的指定进程路径!
在这里插入图片描述
或者在NSIS中使用这条执行并判断$process,可知$process为空,会输出:Error: Process not found.

$process = Get-Process | Where-Object { $_.Path -eq "D:\Work\TheiaIDE_Test\Clash Test\Clash for Windows.exe" }
if ($process) {
    "Process found: $($process.Name)" | Out-File -FilePath "D:\temp\ps_log.txt" -Append
} else {
    "Error: Process not found." | Out-File -FilePath "D:\temp\ps_log.txt" -Append
}

原因可能是系统自带的powershell权限更高,而通过NSIS调用的powershell与运行的指定程序不在同一个权限或者层级,所以检测不到。

二、正确实现

1. 法一:调用外部PowerShell命令文件

因为NSIS中直接调用PowerShell指令存在字符转义的问题,会搞得很混乱,所以把PowerShell的多条指令放在外部ps1文件中,然后直接通过NSIS去调用执行整个ps1文件将相对简单。
具体实现如下:
在.nsh文件中使用nsExec::ExecToStack 调用 PowerShell 脚本
在这里插入图片描述

; 卸载页面组件
Section "un.Clash" un_Section_Clash
    ${GetParent} $INSTDIR $5
    StrCpy $Clash_Path "$5\Clash Test"
    StrCpy $PowerShellScript "D:\Work\theia-blueprint_origin\theia-blueprint\applications\electron\resources\customNsi\stop_clash.ps1"

    ; 使用 nsExec::ExecToStack 调用 PowerShell 脚本
    nsExec::ExecToStack 'powershell -inputformat none -ExecutionPolicy RemoteSigned -File "$PowerShellScript"'
    ; 删除目录
    RMDir /r "$Clash_Path"
SectionEnd

stop_clash.ps1文件中定义要执行的PowerShell指令。

try {
    # 列出所有进程及其路径,不生效
    # Get-Process | ForEach-Object {
    #     try {
    #         $_.Path
    #     } catch {}
    # } | Out-File -FilePath "D:\temp\ps_log.txt" -Append

	# 输出所有检测到的路径到D:\temp\ps_log.txt文件中(这个文件需要自己先手动创建,否则Powershell执行失败)
    "Log: 11111111" | Out-File -FilePath "D:\temp\ps_log.txt" -Append
	# 使用Get-WmiObject获取所有进程路径会生效
    Get-WmiObject Win32_Process | ForEach-Object {
        try {
            if ($_.ExecutablePath) {
                $_.ExecutablePath | Out-File -FilePath "D:\temp\ps_log.txt" -Append
            } else {
                "Process: $($_.Name) has no ExecutablePath" | Out-File -FilePath "D:\temp\ps_log.txt" -Append
            }
        } catch {
            "Error accessing path for Process: $($_.Name)" | Out-File -FilePath "D:\temp\ps_log.txt" -Append
        }
    }

    $process = Get-WmiObject Win32_Process | Where-Object { $_.ExecutablePath -eq "D:\Work\TheiaIDE_Test\Clash Test\Clash for Windows.exe" }
    if ($process) {
        "Process found: $($process.Name)" | Out-File -FilePath "D:\temp\ps_log.txt" -Append
        $process | ForEach-Object { Stop-Process -Id $_.ProcessId -Force } # 杀死进程
        "Process stopped." | Out-File -FilePath "D:\temp\ps_log.txt" -Append
    } else {
        "Error: Process not found." | Out-File -FilePath "D:\temp\ps_log.txt" -Append
    }
}
catch {
    # 如果出现错误,记录错误信息
    "Error: $($_.Exception.Message)" | Out-File -FilePath "D:\temp\ps_log.txt" -Append
}

可以看到使用Get-WmiObject获取所有进程路径会生效
在这里插入图片描述
下边是Get-Process和Get-WmiObject的比较,在NSIS调用PowerShell指令中,只有Get-WmiObject是能达到预期功能的。
在这里插入图片描述

2. 法二:使用 WMIC 命令根据路径查找进程并终止

上述法一是在PowerShell脚本中使用Get-WmiObject Win32_Process,本质是PowerShell 通过 Windows Management Instrumentation (WMI) 进行系统查询。其实在NSIS中,可以不用通过PowerShell去调用WMI,可以直接使用nsExec::ExecToStack 执行 wmic 命令删除进程。具体实现如下,直接在.nsh文件中调用即可:

; 卸载页面组件
Section "un.Clash" un_Section_Clash
    ${GetParent} $INSTDIR $5
    StrCpy $Clash_Path "$5\Clash Test"
    
    ; 定义要终止的进程路径
    StrCpy $EXE_PATH "D:\\Work\\TheiaIDE_Test\\Clash Test\\Clash for Windows.exe"
    ; 使用 nsExec::ExecToStack 执行 wmic 命令删除进程,这里用到了NSIS特殊字符转义
    nsExec::ExecToStack 'wmic process where "ExecutablePath=$\'$EXE_PATH$\'" delete'
    
    ; 删除目录
    RMDir /r "$Clash_Path"
SectionEnd

然后编译运行就可以关闭进程并且删干净了!

总结:网络上检索尝试了两天,很多方法都是十几年前的都失效了,或者是只能传递特定的进程名称,无法传递路径,那么不符合我的需求。而且NSIS存在特殊字符转义,调用其它外部指令的时候会搞得很混乱。
上述两种方法在我本地都是可以实现通过路径杀死指定进程,我觉得主要麻烦的地方是获取到正在运行的进程,如果我提供的方法不好使,可以查看NSIS官方提供的文档找出灵感:Check whether your application is running

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值