Featured image of post bat 脚本监控与排查:wmic + taskkill 进程管理实战

bat 脚本监控与排查:wmic + taskkill 进程管理实战

Windows 批处理脚本常用技巧:查后台 bat 进程 PID、taskkill 强杀、PowerShell 等价命令、监控重启脚本、批处理编码与中文乱码

一、为什么 2024 年还在写 bat 脚本

2014 年的 Windows 自动化基本是 bat + VBScript 双雄;2018 年之后 PowerShell Core 跨平台,bat 看起来"该退休"了。但实际场景里:

  • 老旧 Windows Server 2008 R2 / 2012 R2 默认没装 PowerShell 5,bat 仍是首选
  • 计划任务里挂的"老脚本"没人敢动,出问题了又得维护
  • NSSM 注册服务的某些参数只能传 bat 路径
  • 第三方工具的安装脚本(很多 CAD / 工控软件)就是 bat,你绕不开

所以"bat 找 PID + 杀进程"是必须会的硬功夫。本文以两个高频需求切入:

  1. 找到后台跑的 bat 进程(常见:监控 / 重启脚本卡死)
  2. 结束指定进程(常见:清残留)

二、找后台运行的 bat 进程

2.1 用 wmic 找 bat 进程

1
wmic process get commandline,processid | findstr 监控重启.bat | findstr /v findstr

逐步拆解

  1. wmic process get commandline,processid —— 列出所有进程的"完整命令行"和"PID"
  2. findstr 监控重启.bat —— 过滤出命令行里含"监控重启.bat"的行
  3. findstr /v findstr —— 排除掉 “findstr 自身” 那一行(否则你会看到 findstr 自己的命令行

输出形如:

1
cmd.exe /c "D:\scripts\监控重启.bat"    4620

第二列的 4620 就是 bat 对应的 PID(其实是 cmd.exe 的 PID,因为 bat 是 cmd 解释执行的)。

2.2 用 PowerShell 等价(推荐)

1
2
3
Get-CimInstance Win32_Process |
    Where-Object { $_.CommandLine -like '*监控重启.bat*' } |
    Select-Object ProcessId, Name, CommandLine

PowerShell 的优势:

  • 对象化输出,可以直接 | Stop-Process 一气呵成
  • 不像 wmic 那样返回字符串不依赖解析列宽
  • Unicode 中文文件名不会乱码

wmic 在 Win 10 21H1 起开始被标记为 deprecated(最终会从 Windows 中移除),强烈建议新写的脚本用 PowerShell CIM。

2.3 用 Get-Process 按名字找(不推荐)

1
Get-Process cmd | Where-Object { $_.MainWindowTitle -like '*监控*' }

缺点:cmd.exe 的所有实例都返回(10 个 cmd 窗口就有 10 条),需要再过滤窗口标题,对纯后台运行的 bat 不适用。

三、结束进程

3.1 taskkill(cmd 原生)

1
taskkill /f /pid 4620

参数说明:

  • /f —— 强制结束(不弹确认
  • /pid —— 按 PID
  • /im —— 按镜像名(taskkill /f /im cmd.exe 会杀掉所有 cmd 窗口,慎用

3.2 杀进程并确认

1
2
3
4
5
6
taskkill /f /pid 4620
if %errorlevel% == 0 (
    echo 杀进程成功
) else (
    echo 杀进程失败,可能权限不够
)

3.3 PowerShell 一行杀

1
2
3
Get-CimInstance Win32_Process |
    Where-Object { $_.CommandLine -like '*监控重启.bat*' } |
    ForEach-Object { Stop-Process -Id $_.ProcessId -Force }

或者用更直观的 Get-Process

1
2
3
Get-Process -Name cmd -ErrorAction SilentlyContinue |
    Where-Object { $_.Path -eq 'C:\Windows\System32\cmd.exe' } |
    Stop-Process -Force

四、实战:监控重启脚本

4.1 需求

写一个 bat 监控某个进程(如 myapp.exe),如果挂了自动重启。

4.2 朴素版(轮询)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@echo off
set INTERVAL=30
:loop
tasklist | findstr /i "myapp.exe" >nul
if %errorlevel% == 0 (
    echo %date% %time% myapp.exe 正常运行
) else (
    echo %date% %time% myapp.exe 已挂,重启中...
    start "" "C:\apps\myapp.exe"
)
timeout /t %INTERVAL% /nobreak >nul
goto loop

关键点

  • tasklist | findstr /i —— 查进程是否存在
  • if %errorlevel% == 0 —— 找到就是 0
  • start "" —— 空标题必须,否则会报错
  • timeout /t N /nobreak —— 等 N 秒,不响应 Ctrl+C(ping -n 更精确

4.3 改进版(按需触发)

如果只想要"挂了再启动,不轮询",可以用 WaitForSingleObject 思路,但 bat 实现不了,这种场景请用 nssm 注册成 Windows 服务(详见 0.4 批次的 2022-12-15《Windows 系统管理员实战》)。

4.4 用 PowerShell 写监控脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$target = 'myapp'
$interval = 30
$exe = 'C:\apps\myapp.exe'

while ($true) {
    if (-not (Get-Process -Name $target -ErrorAction SilentlyContinue)) {
        Write-Host "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') $target 已挂,重启"
        Start-Process -FilePath $exe
    } else {
        Write-Host "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') $target 正常"
    }
    Start-Sleep -Seconds $interval
}

PowerShell 优势:

  • Get-Processtasklist | findstr 更可靠(处理特殊字符不挂
  • Start-Sleeptimeout 更可编程
  • 异常可以用 try/catch 包裹

五、批处理编码与中文乱码

5.1 bat 文件默认编码

bat 在 中文 Windows 下默认是 GBK 编码。如果你用 UTF-8 编码的 bat 直接运行,中文会乱码

两种处理方式

  1. 保存为 GBK / ANSI 编码(最稳)—— 用 VS Code 右下角"UTF-8"点开选"Save with Encoding"→“GBK"或"Chinese Simplified (GB2312)”
  2. 保存为 UTF-8 BOM 编码 —— Windows 10 1809+ 能识别 BOM 自动用 UTF-8 解释
  3. 在 bat 顶部加 chcp 65001 >nul —— 切换控制台代码页到 UTF-8,但这不会改变 bat 源文件的解释编码,对 bat 里的中文 echo 没有帮助,仅对 bat 调用的外部命令的输出有影响

5.2 输出 UTF-8 到文件

1
2
chcp 65001 >nul
echo 你好,世界 > output.txt

注意> 重定向输出的 output.txt 会带 UTF-8 BOM(如果 cmd 默认按 ANSI 解释 echo 命令后再输出)。最稳的办法是用 PowerShell:

1
"你好,世界" | Out-File -FilePath output.txt -Encoding UTF8

5.3 bat 里包含中文路径

1
2
cd /d "D:\我的文档\脚本"
call 启动.bat

中文路径用双引号包起来就不会出错。/d 开关让你不用先 D: 切盘符。

六、批处理常见错误与排查

6.1 错误级别(%errorlevel%

每条命令执行后,cmd 设置一个 errorlevel:

  • 0 —— 成功
  • 非 0 —— 失败(具体值看命令)
1
2
3
4
5
6
@echo off
xcopy C:\source D:\dest /E /I
if %errorlevel% neq 0 (
    echo 拷贝失败,错误码 %errorlevel%
    exit /b %errorlevel%
)

6.2 延迟扩展(setlocal enabledelayedexpansion

bat 里的 if 块、for默认不实时展开变量。典型坑:

1
2
3
4
5
6
@echo off
set COUNT=0
for /l %%i in (1,1,5) do (
    set /a COUNT+=1
    echo 当前 COUNT=%COUNT%
)

输出

1
2
3
4
5
当前 COUNT=0
当前 COUNT=0
当前 COUNT=0
当前 COUNT=0
当前 COUNT=0

因为 %COUNT% 在 for 块开始时就被展开为 0,后续 set /a 修改的 COUNT 没被重新读取

正确写法

1
2
3
4
5
6
7
@echo off
setlocal enabledelayedexpansion
set COUNT=0
for /l %%i in (1,1,5) do (
    set /a COUNT+=1
    echo 当前 COUNT=!COUNT!
)

关键点

  1. 顶部加 setlocal enabledelayedexpansion
  2. 块内用 !VAR! 而非 %VAR% 来读取最新值

6.3 路径里的空格

1
2
cd "C:\Program Files\MyApp"
cd %PROGRAMFILES%\MyApp

用双引号包路径最稳。第二种用环境变量展开也 OK,但路径里有 & 之类特殊字符必须用引号

七、批处理的替代方案

场景替代方案
简单文件操作 / 启停服务bat 仍是最简方案
跨平台脚本PowerShell Core / pwsh
复杂文本处理PowerShell / Python
长任务调度Windows 任务计划程序 + bat/ps1
常驻服务nssm 注册成服务(详见 2022-12-15 那篇)
配置中心Ansible(WinRM 协议)

八、常见 5 个坑

  1. wmic 在新 Windows 上 deprecated——新写脚本用 Get-CimInstance
  2. 中文乱码——bat 源文件保存为 GBK / ANSI 编码最稳。
  3. for 块内 %VAR% 不刷新——用 setlocal enabledelayedexpansion + !VAR!
  4. taskkill /f /im cmd.exe 把所有 cmd 窗口都杀了——按 PID 杀更精准。
  5. bat 里的"后台进程"实际是 cmd.exe——wmic 找到的 PID 是 cmd 解释器的,杀它才能停 bat。

九、总结

  • 找后台 bat 进程wmic process get commandline,processid | findstr xxx.bat(老方案);Get-CimInstance(推荐)
  • 杀进程taskkill /f /pid PID(cmd);Stop-Process(PowerShell)
  • 监控 + 自动重启tasklist | findstr 轮询(朴素);nssm 注册服务(推荐)
  • 中文乱码:bat 源文件保存为 GBK / ANSI最稳
  • 延迟变量setlocal enabledelayedexpansion + !VAR!
  • 新写脚本优先用 PowerShell(pwsh 跨平台),bat 留给"老系统 / 老脚本维护"

参考资料

使用 Hugo 构建
主题 StackJimmy 设计