当软件安装时,如果有特定的软件正在运行,则新软件无法对其进行覆盖。同样当软件卸载时,如果其正在运行,则有残留的文件删不干净。所以便出现了在安装卸载中杀死指定进程的需求。
文章目录
- 一、踩坑记录
- 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