当软件安装时,如果有特定的软件正在运行,则新软件无法对其进行覆盖。同样当软件卸载时,如果其正在运行,则有残留的文件删不干净。所以便出现了在安装卸载中杀死指定进程的需求。
 
文章目录
- 一、踩坑记录
- 1. FindProcDLL和KillProcDLL失效
- 2. NSIS转义符
- 3. NSIS中调用PowerShell的Get-Process无法找到所有进程
 
- 二、正确实现
- 1. 法一:调用外部PowerShell命令文件
- 2. 法二:使用 WMIC 命令根据路径查找进程并终止
 
一、踩坑记录
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













![[PHP]Undefined index错误只针对数组](https://i-blog.csdnimg.cn/direct/71d1ed2906c6465f90d6ceb7dd09b5f2.png)





