Windows 11 移除 WMIC 后,如何让依赖 wmic 的老程序继续工作

一套通用的 wmic 兼容垫片方案:不装回废弃二进制,让老程序零改动继续跑。

适用系统:Windows 11 24H2 / 25H2 / 26H1(2026 年 8 月更新及以后)
整理日期:2026-09-17


目录


一、问题:wmic 没了,倒下的不止一个程序

1.1 现象

2026 年 8 月起,Windows 11 的 24H2 / 25H2 / 26H1 更新彻底删除了 wmic.exe
而且不再作为「可选功能」提供 —— 你在「设置 → 系统 → 可选功能」里已经搜不到它了。

麻烦在于:wmic 曾经是过去十几年里最顺手的系统查询命令行,
大量老程序把它当成理所当然的存在。它一消失,这些程序会以各种奇怪的方式失败:

表现 说明
直接报「不是内部或外部命令」 最直白的一种,程序会把命令的错误输出打出来
静默拿到空结果 最坑的一种。程序不报错,只是某个功能「检测不到」
把退出码 1 当成失败并中断 有些程序只看退出码,直接走进错误分支
功能入口消失 / 按钮变灰 检测失败的连带结果

关键在于:第二种最难查。程序看起来一切正常,就是不工作。

本文用蓝凌 KMSS 的 IDEA 插件「代码合并工具」作为贯穿案例 —— 它就属于第二种,
表现为「idea 程序位置检测不出来」。完整复盘见附录 C
正文部分尽量与具体程序解耦。

1.2 谁会中招

只要程序内部 shell out 到 wmic,就会中招。实际遇到过的类别:

类别 典型场景
IDE / 编辑器插件 检测 IDE 安装路径、枚举进程、取硬件信息
构建与发布脚本 .bat / .cmd / .ps1 里查进程、查磁盘、查服务
运维 / 巡检脚本 采集 CPU、内存、磁盘、服务状态
安装包 / 静默部署脚本 前置检查、卸载旧版本、检测进程占用
监控采集器 / 资产盘点工具 上报硬件序列号、系统版本、补丁列表
老版本业务系统客户端 启动时做环境自检

判断方法很简单:在你自己的脚本或程序目录里搜一下 wmic(见 2.2)。

1.3 报错长什么样

英文系统:

1
2
'wmic' is not recognized as an internal or external command,
operable program or batch file.

中文系统:

1
2
'wmic' 不是内部或外部命令,也不是可运行的程序
或批处理文件。

静默型(没有报错,只有程序自己的日志):

1
2
ideaPath64 为空
检测失败:未获取到目标信息

1.4 关键认知:WMI 还在,只是外壳没了

这是整件事里最重要的一点,也是后面所有方案成立的基础:

被移除的只是 wmic.exe 这个命令行外壳。WMI 服务本身完全正常。

wmic 只是一层「把命令行翻译成 WMI 查询」的皮。剥掉它,
底层能力(Win32_ProcessWin32_OperatingSystemWin32_Service …)
一个都没少,用 PowerShell 的 Get-CimInstance 查得到的东西和以前完全一样。

所以问题不是「能力没了」,而是「调用约定变了」。
既然我们无法改老程序,那就把老的调用约定补回来 —— 这就是本方案的全部思路。


二、先判断:是不是中了这一枪

2.1 三步定位法

别急着动手,先花十分钟确认三件事:

1
2
3
4
5
6
7
8
第 1 步  它到底在调什么?
└─ 拿到程序执行的那条完整命令行(关键证据)

第 2 步 wmic 是真的没了,还是 PATH / 权限问题?
└─ 排除「其实只是环境变量被改坏了」

第 3 步 它需要什么输出?有没有绕过的口子?
└─ 决定是「改配置」还是「补命令」

第 3 步决定方案选择:

第 3 步的结论 走哪条路
程序提供了手工指定路径的入口(可编辑输入框) 直接填,收工
程序有配置文件,且代码真的会读回 改配置文件,收工
只能自动检测,没有绕过口子 第四章的垫片方案

⚠️ 「有配置文件」不等于「改了有用」。案例里那个插件就把路径写进
.idea/kmss_dev_config.xml,但全 jar 没有任何代码读回它 ——
是只写不读的缓存,改了白改,还会被程序退出时覆盖。
所以第 3 步必须看代码或做实测,不能只看「有没有这个字段」。

2.2 找出程序执行了哪条命令

按可行性从高到低:

手段 适用 怎么做
程序自己的日志 首选,最省事 搜关键字 wmicexeccommandbat、厂商名
进程监控 程序无日志、需要看进程调用 Process Monitor,过滤 Operation = Process Start,看 Command Line 列
ETW / Sysmon 不方便装工具 Sysmon 事件 ID 1(进程创建)带完整命令行
字符串搜索 程序是脚本、jar、exe 在文件里直接搜 wmic 明文
反编译 jar / .NET 程序 javap(Java)、dnSpy(.NET)
手动复现 已知命令 cmd 里原样敲一遍,看真实报错

案例中的证据就是这样拿到的 —— 插件日志里直接写着它执行的命令,
连报错都一起打出来了(见附录 C)。

💡 拿到命令行后,先把它原样在 cmd 里跑一遍
这一步能同时完成第 1、2 步:既确认命令内容,也确认 wmic 到底在不在。

2.3 确认 wmic 的真实状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1. wmic 是否还存在(两个可能的位置都看)
Test-Path "$env:SystemRoot\System32\wmic.exe"
Test-Path "$env:SystemRoot\System32\wbem\wmic.exe"
# 都返回 False → wmic 已被移除

# 2. 它在 PATH 里还解析得到吗
cmd /c "where wmic"
# 返回「信息: 用提供的模式无法找到文件。」→ 确实没有

# 3. WMI 服务本身是否正常(正常 ≠ wmic 还在)
Get-Service Winmgmt | Format-List Name, Status, StartType
# 期望:Running / Automatic

# 4. 确认 WMI 能力没丢(这条能跑通,垫片方案就一定可行)
Get-CimInstance Win32_OperatingSystem | Select-Object Caption, Version

如果第 3、4 步异常,那是另一类问题(WMI 仓库损坏),不在本文范围。

2.4 微软官方时间线

摘自微软 KB5067470《Windows Management Instrumentation Command-line (WMIC) removal from Windows》:

时间 状态
2021 Windows 10 21H2 起标记为弃用
2022 Win11 22H2 作为「可选功能」预装并默认启用
2024 Win11 23H2 / 24H2 默认禁用,仍可作可选功能安装
2025 升级到 25H2 时被移除,仍可作可选功能装回
2026-08 从 24H2 / 25H2 / 26H1 彻底移除,不再作为可选功能提供

所以「可选功能里搜不到 wmic」是正常现象,不是机器有问题
网上那些「设置 → 可选功能 → 搜索 wmic 下载安装」的教程,
对 2026 年 8 月之后的系统已经全部失效

2.5 先别急着 DISM

网上流传一条命令,号称能把 WMIC 装回来:

1
DISM /Online /Add-Capability /CapabilityName:WMIC~~~~

实测:会显示「操作成功完成」,但实际什么都没装,wmic 依然找不到。

原因(已逐项验证):

  • 组件存储里还留着 Microsoft-Windows-WMIC-FoD-Package* 的元数据(.mum / .cat),
    所以 DISM 校验能过、不报错;
  • wmic.exe实际载荷已被删除 —— 全盘 WinSxS 里只剩 wmi-wmiclnt
    (WMI 客户端 DLL,跟 wmic.exe 没关系),没有任何 wmic.exe
  • 也没有待重启标记(RebootPending = False),所以不是「重启一下就好」。

这与 KB 的说法一致:WMIC 已不再作为 Feature on Demand 提供
那条 DISM 命令只对 23H2 / 24H2 早期有效。

⚠️ 这个坑值得单独记一笔:DISM 报「操作成功完成」不代表装上了
元数据残留会让它误判为「已安装」。


三、方案对比

# 方案 需要管理员 优点 缺点 适用
1 程序界面里手工指定路径 成本最低 多数程序没这个入口(案例中是只读标签) 先试
2 改程序的配置文件 不用装东西 必须确认代码真的读回该值 视程序而定
3 微软官方临时包 wmic_dlc.zip 官方出品,装回真 wmic.exe所有程序零改动 塞回已弃用二进制;微软声明只是临时方案 想让流程与现有文档保持一致
4 wmic 兼容垫片(本方案) ✅ / 可选免管理员 不引入废弃二进制;可控、可扩展、可卸载 自制件需自己维护;只覆盖常见语法 推荐
5 推动上游改用 Get-CimInstance 治本 依赖厂商排期 建议同步推进
6 降级 / 更换系统 立刻可用 代价大,且不解决长期问题 不推荐

方案 3 的官方包在 KB5067470 里,链接指向 download.microsoft.com
解压后管理员 PowerShell 执行 Set-ExecutionPolicy Bypass -Scope Process.\install.ps1

方案 3 与方案 4 怎么选
如果组内有一堆说不清的脚本都在用 wmic、且语法五花八门 → 选 3,最省心;
如果只是个别程序、用的语法很固定 → 选 4,不引入废弃二进制、更干净。


四、部署 wmic 兼容垫片

4.1 原理

一句话:做一个假的 wmic,把命令翻译成 PowerShell 执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
老程序 / 插件
│ cmd /c wmic process where name='xxx.exe' get processid,executablepath

cmd.exe 沿 PATH 解析 wmic
│ System32\wmic.exe ✗ 已不存在 → 继续往后找

命中 C:\Windows\System32\Wbem\wmic.cmd ← 转接头(该目录本来就在 PATH 上)
│ 把参数原样转发

wmic-shim.ps1
│ 解析别名 / where / get → Get-CimInstance 查 WMI

按 wmic 原格式输出 → 老程序解析到它要的东西

为什么 cmd 会选中我们的 wmic.cmd

cmd.exe 解析 wmic 时的顺序是:逐个 PATH 目录,在每个目录里按 PATHEXT
顺序找 wmic.comwmic.exewmic.batwmic.cmd

System32\C:\Windows\ 里都没有 wmic.*,于是继续往后找,
最终命中 System32\Wbem\wmic.cmdPATHEXT 默认含 .CMD)。

前提是 wmic.exe 已经被删干净 —— 正是这个方案的成立条件。
反过来说,如果哪天微软把 wmic.exe 还回来,.EXEPATHEXT 里排在
.CMD 前面,垫片会自动「让位」,不会打架。这个设计是刻意留的。

性能:每次调用多一次 PowerShell 启动,实测 90–500 ms。
对交互式功能无感;如果是高频循环调用(比如脚本里查上千次进程),会明显变慢。

4.2 支持的命令范围

命令形态 支持 说明
<别名> where <条件> get <属性,...> 主力形态
<别名> get <属性> 无过滤条件
<别名> where <条件> 输出全部属性,list 格式
<别名> 输出全部属性,list 格式
/value/format:list Prop=Value 形式
/format:csv Node,Prop1,... + 每行前缀主机名
wmic / wmic.exe / 完整路径前缀 有些调用方会把整条命令一起传进来
<别名> call <方法>(如 process call create 不处理,静默返回空
/node: /user: /password: 远程查询 静默返回空,不会用本机数据冒充远程结果
其他子命令(wmic /? 等) 静默返回空

设计原则:不支持的语法一律「静默返回空 + 退出码 0」,绝不报错。
因为调用方通常只看输出,报错反而会打断它的主流程。

已知别名见 5.1。不在表里的别名会按 Win32_<首字母大写> 猜一次,
猜不到就返回空。需要支持更多时按 5.2 加一行即可。

4.3 文件清单

文件 作用
wmic.cmd 核心。转接头,把参数转发给下面的脚本
wmic-shim.ps1 核心。翻译逻辑:解析 → 查 CIM → 按 wmic 格式输出
安装-wmic垫片.cmd 安装到 System32\Wbem(需管理员),自带自检
安装-wmic垫片-免管理员.cmd 装到 %USERPROFILE%\wmic-shim + 用户 PATH(免管理员)
卸载-wmic垫片.cmd 删除已安装的文件,恢复原状
检查安装状态.cmd 一键自检:文件是否到位 / where wmic / 实测查询

全部 6 个脚本源码见附录 A,也可以直接取用本目录下的同名文件。

4.4 方式一:需要管理员(推荐)

做法 A:双击脚本

右键 安装-wmic垫片.cmd以管理员身份运行。脚本会自动复制文件并做自检。

做法 B:管理员 PowerShell 里粘贴

1
2
3
4
$dst = "$env:SystemRoot\System32\Wbem"
Copy-Item "<本目录>\wmic.cmd" $dst -Force
Copy-Item "<本目录>\wmic-shim.ps1" $dst -Force
cmd /c "wmic process where name='explorer.exe' get processid,executablepath"

优点:不需要改 PATH,不需要重启程序,装完直接重开那个功能即可。

4.5 方式二:免管理员

右键 安装-wmic垫片-免管理员.cmd 运行(无需提权)。它会:

  1. 把两个核心文件复制到 %USERPROFILE%\wmic-shim\
  2. 通过 setx PATH 把该目录追加到用户级 PATH(不动系统 PATH)

⚠️ 必须完全退出目标程序再重新启动 —— 只有新进程才会带上更新后的 PATH。
如果程序是 IDE,记得连 JetBrains Toolbox 一起退。
这也是为什么推荐方式一:System32\Wbem 本来就在 PATH 上,省掉重启这一步。

4.6 验证

双击 检查安装状态.cmd,或手动执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1. 文件是否到位
Test-Path "$env:SystemRoot\System32\Wbem\wmic.cmd" # 期望 True
Test-Path "$env:SystemRoot\System32\Wbem\wmic-shim.ps1" # 期望 True

# 2. cmd 能否解析到 wmic
cmd /c "where wmic"

# 3. 查一个「一定在运行」的进程 —— 用 explorer.exe,它永远在
cmd /c "wmic process where name='explorer.exe' get processid,executablepath"
# → ExecutablePath ProcessId
# → C:\Windows\Explorer.EXE 21572

# 4. 查目标程序自己的进程(程序没开时为空是正常的)
cmd /c "wmic process where name='idea64.exe' get processid,executablepath"
# → D:\SoftWare\JetBrains\Apps\IntelliJ IDEA Ultimate\bin\idea64.exe 26660

判定标准:

  • 第 3 步有路径 = 垫片工作正常
  • 第 4 步有路径 = 目标程序在运行,它也能检测到

💡 两个坑

  1. 在 PowerShell 7 会话里直接敲 wmic 可能仍报「找不到」——

    PowerShell 会缓存命令查找结果。自检请用 cmd /c "wmic ...",或新开一个终端。
    2. 第 4 步为空时先确认程序是否真的开着(Get-Process <名字>),别误判成失败。

4.7 卸载

右键 卸载-wmic垫片.cmd → 以管理员身份运行,删除 System32\Wbem 下的两个文件即可。
免管理员版同理,再手动把用户 PATH 里的 %USERPROFILE%\wmic-shim 去掉。


五、扩展垫片

垫片是为常见语法做的通用翻译器,不是完整的 wmic 复刻。
遇到它不支持的语法时,按下面几节自己加。

5.1 别名映射表

脚本里内置了这些别名(wmic 别名 → CIM 类):

别名 CIM 类 别名 CIM 类
process Win32_Process printer Win32_Printer
os Win32_OperatingSystem printjob Win32_PrintJob
computersystem Win32_ComputerSystem share Win32_Share
logicaldisk / filesystem Win32_LogicalDisk netuse Win32_NetworkConnection
volume Win32_Volume nic Win32_NetworkAdapter
diskdrive Win32_DiskDrive nicconfig Win32_NetworkAdapterConfiguration
partition Win32_DiskPartition netprotocol Win32_NetworkProtocol
cdrom Win32_CDROMDrive useraccount Win32_UserAccount
service Win32_Service group Win32_Group
sysdriver Win32_SystemDriver environment Win32_Environment
bios Win32_BIOS startup Win32_StartupCommand
cpu Win32_Processor timezone Win32_TimeZone
baseboard Win32_BaseBoard qfe Win32_QuickFixEngineering
memorychip Win32_PhysicalMemory product Win32_Product
sounddevice Win32_SoundDevice desktop Win32_Desktop
shadowcopy Win32_ShadowCopy

不在表里的别名,会先按 Win32_<首字母大写> 猜一次(如 win32_biosWin32_BIOS),
猜不到就返回空。

5.2 加一个别名

找到脚本里的 $AliasMap,加一行即可:

1
2
3
4
5
$AliasMap = @{
'process' = 'Win32_Process'
# ...
'idecontroller' = 'Win32_IDEController' # ← 新增
}

怎么知道别名对应哪个类? 两种办法:

1
2
3
4
5
6
7
# 办法一:凭经验猜 + 验证
Get-CimClass -ClassName Win32_IDEController | Select-Object -ExpandProperty CimClassName

# 办法二:全量列出 Win32_* 类,用关键字筛
Get-CimClass -ClassName Win32_* |
Where-Object { $_.CimClassName -like '*Network*' } |
Select-Object -ExpandProperty CimClassName

改完直接测:

1
cmd /c "wmic idecontroller get deviceid,name"

5.3 where 条件的写法

where 后面的内容会原样透传给 WQLGet-CimInstance -Filter),所以:

写法 支持 备注
name='x.exe' 属性名大小写不敏感name / Name 都行
processid=1234 数字不用加引号
state='Running' 字符串要加引号
name like '%chrome%' % 是通配符
a='1' and b='2' and / or
(a='1' or a='2') 括号
where "name='x'" 整体被引号包住会自动剥掉

补救机制:如果首次查询因属性名大小写或拼写失败,
脚本会读一次类定义、把属性名纠正成规范大小写后自动重试一次
仍失败就返回空。

⚠️ where 里引号内的 / 不会被当成开关 ——
所以 where caption='a/b' 是安全的,/b 不会被误删。

5.4 输出格式

格式 触发方式 样式
table(默认) 无开关 表头 + 数据行,列宽对齐,列之间两个空格
list /value/format:list 每行 Prop=Value,实例之间空一行
csv /format:csv Node,Prop1,... + 每行前缀计算机名

两个刻意做的保真细节,都是为了「让老程序解析得出来」:

  1. 列按属性名字母序排列 —— 这是 wmic 默认表格输出的行为。
    所以 get processid,executablepath 得到的是 ExecutablePath ProcessId(字母序),
    而不是你写的顺序。垫片保持一致,避免调用方按位置解析时错位。
  2. 属性名输出规范大小写 —— 你写 get processid,输出的是 ProcessId
    调用方如果按表头字符串匹配,这一步是必须的。

属性名写错时(如 get bogusproperty),垫片返回空而不是吐一堆空行 ——
真 wmic 此时会报 Invalid property,返回空是更安全的降级。

5.5 调试方法

单独测一条命令(不经过 cmd,直接看脚本行为):

1
2
3
powershell -NoProfile -ExecutionPolicy Bypass `
-File "C:\Windows\System32\Wbem\wmic-shim.ps1" `
"process where name='explorer.exe' get processid,executablepath"

直接对照 CIM 的原始结果,确认是垫片的问题还是 WMI 本来就没数据:

1
2
Get-CimInstance Win32_Process -Filter "Name='explorer.exe'" |
Select-Object ProcessId, ExecutablePath

看退出码(正常应始终是 0):

1
2
wmic process get name >nul
echo %ERRORLEVEL%

脚本语法自检(改完垫片后强烈建议跑一次):

1
2
3
4
$src = Get-Content -Raw "C:\Windows\System32\Wbem\wmic-shim.ps1"
$e = $null
[void][System.Management.Automation.Language.Parser]::ParseInput($src, [ref]$null, [ref]$e)
if ($e) { $e | ForEach-Object { "$($_.Extent.StartLineNumber): $($_.Message)" } } else { "语法 OK" }

六、常见问题

Q:为什么不直接让程序手工填路径?
A:得看程序有没有给你这个入口。案例里那个字段绑定的是 JLabel(只读标签),
根本填不了。先按 2.1 第 3 步确认有没有绕过口子,再决定。

Q:改程序的配置文件行不行?
A:必须确认代码真的会读回这个值。案例里的 ideaPath 是只写不读的缓存,
改了不生效,还会被程序退出时用内存里的值覆盖掉。

Q:为什么垫片放在 System32\Wbem 而不是别的地方?
A:那个目录本来就在系统 PATH 上,而且正是 wmic.exe 原来的家。
放这里不用改 PATH、不用重启程序,命中优先级也自然。

Q:装了垫片之后,以后系统更新会冲突吗?
A:不会。如果将来 wmic.exe 被恢复,.EXEPATHEXT 中优先于 .CMD,垫片自动让位。

Q:会影响其他用 wmic 的工具吗?
A:会 —— 它们现在拿到的是本垫片的输出。好处是覆盖面一下变广了(所有走 PATH 调 wmic 的程序都受益);
代价是如果某个工具用了垫片不支持的语法,它会拿到空结果
组内如果有多个工具依赖 wmic,建议先摸清各自用了什么语法,
不够用就按第五章扩展,或改用方案 3(官方包)。

Q:怎么知道还有哪些程序在偷偷调 wmic?
A:装完垫片后开一次 Process Monitor,过滤 Operation = Process Start
看谁的命令行里有 wmic。或者直接搜你的脚本仓库。

Q:安全软件会报毒吗?
A:垫片只是两个纯文本脚本,不含二进制、不联网、不写注册表(免管理员版会写用户 PATH)。
如果企业 EDR 对「往 System32 写文件」敏感,可能触发告警,需要加白或改用免管理员版。

Q:能用 Get-WmiObject 代替 Get-CimInstance 吗?
A:不建议。Get-WmiObject 是老的 PowerShell 5.1 写法,PowerShell 7 里已被移除。
新写的东西统一用 Get-CimInstance


附录 A:脚本源码

A.1 wmic.cmd(转接头)

1
2
3
4
5
6
7
8
@echo off
rem ============================================================
rem wmic shim (drop-in replacement for the removed wmic.exe)
rem Delegates to wmic-shim.ps1 sitting next to this file.
rem ============================================================
setlocal EnableExtensions
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0wmic-shim.ps1" %*
exit /b %ERRORLEVEL%

A.2 wmic-shim.ps1(翻译逻辑)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
# ============================================================
# wmic shim -- a drop-in stand-in for the removed wmic.exe
#
# 背景
# Windows 11 24H2 / 25H2 / 26H1(2026-08 起)已彻底移除 wmic.exe,
# 且不再作为「可选功能」提供。任何 shell out 到 wmic 的老程序
# —— IDE 插件、构建脚本、运维脚本、安装包、监控采集器 ——
# 都会以「'wmic' 不是内部或外部命令」失败。
#
# 做法
# 把 wmic 的命令行翻译成 CIM 查询(Get-CimInstance),
# 再按 wmic 的格式输出,让调用方无感。
# WMI 服务本身并未被移除,所以这条路完全可行。
#
# 支持的语法
# <别名> [where <条件>] get <属性[,属性...]> [/value | /format:...]
# <别名> [where <条件>] (无 get → 全部属性)
# <别名> get <属性>
#
# 输出格式
# 默认 table:表头 + 数据行,列按属性名字母序(与 wmic 一致)
# /value list :每行 Prop=Value
# /format:list list :同上
# /format:csv csv :Node,Prop1,... + 每行前缀计算机名
#
# 不支持的语法
# 静默返回空、退出码 0 —— 不报错,避免打断调用方的主流程。
#
# 输入
# 原始 wmic 参数行,作为脚本参数传入(兼容环境变量 WBD_WMIC_ARGS)
# ============================================================

$ErrorActionPreference = 'SilentlyContinue'

# 让输出尽量贴近 wmic:使用控制台的 OEM 代码页
try {
[Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding(
[System.Globalization.CultureInfo]::CurrentCulture.TextInfo.OEMCodePage)
} catch { }


# ------------------------------------------------------------
# 把 CIM 的值渲染成 wmic 风格的字符串
# ------------------------------------------------------------
function Format-WmicValue($v) {
if ($null -eq $v) { return '' }
if ($v -is [array]) {
$parts = @()
foreach ($item in $v) { $parts += (Format-WmicValue $item) }
return ($parts -join ',')
}
if ($v -is [datetime]) { return $v.ToString('yyyyMMddHHmmss') + '.000000+000' }
if ($v -is [bool]) { if ($v) { return 'TRUE' } else { return 'FALSE' } }
return [string]$v
}


# ------------------------------------------------------------
# CSV 单元格:含逗号/引号/换行时加引号(与 wmic /format:csv 一致)
# ------------------------------------------------------------
function Format-CsvCell([string]$v) {
if ([string]::IsNullOrEmpty($v)) { return '' }
if ($v -match '[",\r\n]') { return '"' + ($v -replace '"', '""') + '"' }
return $v
}


# ------------------------------------------------------------
# 在字符串中查找关键字(作为独立单词、且不在引号内)
# 用于区分 `where` / `get`,避免把 name='get.exe' 里的 get 误判成关键字
# 找到返回下标,找不到返回 -1
# ------------------------------------------------------------
function Find-WmicKeyword([string]$s, [string]$kw) {
$inQuote = $false
$quoteChar = [char]0
$n = $s.Length
$k = $kw.Length

for ($i = 0; $i -lt $n; $i++) {
$c = $s[$i]

if ($inQuote) {
if ($c -eq $quoteChar) { $inQuote = $false }
continue
}
if ($c -eq "'" -or $c -eq '"') { $quoteChar = $c; $inQuote = $true; continue }
if ($i + $k -gt $n) { break }
if ([string]::Compare($s.Substring($i, $k), $kw, $true) -ne 0) { continue }

$before = ' '
if ($i -gt 0) { $before = $s[$i - 1] }
$after = ' '
if ($i + $k -lt $n) { $after = $s[$i + $k] }

$okBefore = -not ([char]::IsLetterOrDigit($before) -or $before -eq '_')
$okAfter = -not ([char]::IsLetterOrDigit($after) -or $after -eq '_')
if ($okBefore -and $okAfter) { return $i }
}
return -1
}


# ------------------------------------------------------------
# 按列宽渲染一行(列之间两个空格,与 wmic 的表格观感一致)
# ------------------------------------------------------------
function Render-WmicRow($cells, $widths) {
$parts = @()
for ($i = 0; $i -lt $cells.Count; $i++) {
if ($i -eq $cells.Count - 1) {
$parts += [string]$cells[$i]
} else {
$parts += ([string]$cells[$i]).PadRight($widths[$i])
}
}
return ($parts -join ' ')
}


# ------------------------------------------------------------
# 去掉 wmic 的开关(/value、/format:list、/node:xxx …)
# 只在「词首」的 / 才当开关,且绝不碰引号内的内容
# —— 否则 where caption='a/b' 里的 /b 会被误删
# ------------------------------------------------------------
function Remove-WmicSwitches([string]$s) {
if ([string]::IsNullOrEmpty($s)) { return $s }

$out = New-Object System.Text.StringBuilder
$inQuote = $false
$quoteChar = [char]0
$n = $s.Length
$i = 0

while ($i -lt $n) {
$c = $s[$i]

if ($inQuote) {
[void]$out.Append($c)
if ($c -eq $quoteChar) { $inQuote = $false }
$i++
continue
}
if ($c -eq "'" -or $c -eq '"') {
$quoteChar = $c; $inQuote = $true
[void]$out.Append($c); $i++
continue
}
if ($c -eq '/' -and ($i -eq 0 -or [char]::IsWhiteSpace($s[$i - 1]))) {
while ($i -lt $n -and -not [char]::IsWhiteSpace($s[$i])) { $i++ }
continue
}
[void]$out.Append($c)
$i++
}
return $out.ToString()
}


# ------------------------------------------------------------
# 修正 where 条件里的属性名大小写(仅在首次查询失败时作为补救)
# name='x' → Name='x'
# ------------------------------------------------------------
function Repair-WmicFilter([string]$ClassName, [string]$Filter) {
$cls = $null
try { $cls = Get-CimClass -ClassName $ClassName -ErrorAction Stop } catch { return $Filter }
if ($null -eq $cls) { return $Filter }

$out = $Filter
foreach ($p in $cls.CimClassProperties) {
$name = $p.Name
if ([string]::IsNullOrEmpty($name)) { continue }
$pattern = '(?i)\b' + [regex]::Escape($name) + '\b(\s*[=<>])'
$out = [regex]::Replace($out, $pattern, ($name + '$1'))
}
return $out
}


# ============================================================
# 主逻辑:把一行 wmic 参数翻译成 CIM 查询并按 wmic 格式输出
# ============================================================
function Invoke-WmicShim([string]$RawArgs) {

if ([string]::IsNullOrWhiteSpace($RawArgs)) { return }
$raw = $RawArgs.Trim()

# ---------- 远程查询:不支持 ---------------------------------
# /node: /user: /password: 这些开关意味着「查别的机器」。
# 本垫片只做本机查询,此时宁可返回空,也绝不把本机数据
# 当成远程结果返回 —— 静默给错数据比报错更危险。
if ($raw -match '(?i)/(node|user|password|implevel|authlevel)\s*:') { return }

# ---------- wmic 别名 → CIM 类 -------------------------------
# 常见别名基本够用;不在此表里的会按 Win32_<首字母大写> 猜测,
# 猜不到就静默返回空。需要更多别名时往这里加一行即可。
$AliasMap = @{
'process' = 'Win32_Process'
'os' = 'Win32_OperatingSystem'
'computersystem' = 'Win32_ComputerSystem'
'logicaldisk' = 'Win32_LogicalDisk'
'filesystem' = 'Win32_LogicalDisk'
'volume' = 'Win32_Volume'
'diskdrive' = 'Win32_DiskDrive'
'partition' = 'Win32_DiskPartition'
'cdrom' = 'Win32_CDROMDrive'
'service' = 'Win32_Service'
'sysdriver' = 'Win32_SystemDriver'
'bios' = 'Win32_BIOS'
'cpu' = 'Win32_Processor'
'baseboard' = 'Win32_BaseBoard'
'memorychip' = 'Win32_PhysicalMemory'
'sounddevice' = 'Win32_SoundDevice'
'printer' = 'Win32_Printer'
'printjob' = 'Win32_PrintJob'
'share' = 'Win32_Share'
'netuse' = 'Win32_NetworkConnection'
'nic' = 'Win32_NetworkAdapter'
'nicconfig' = 'Win32_NetworkAdapterConfiguration'
'netprotocol' = 'Win32_NetworkProtocol'
'useraccount' = 'Win32_UserAccount'
'group' = 'Win32_Group'
'environment' = 'Win32_Environment'
'startup' = 'Win32_StartupCommand'
'timezone' = 'Win32_TimeZone'
'qfe' = 'Win32_QuickFixEngineering'
'product' = 'Win32_Product'
'desktop' = 'Win32_Desktop'
'shadowcopy' = 'Win32_ShadowCopy'
}

# ---------- 切出别名 -----------------------------------------
# 有些调用方会把整条命令(含开头的 wmic / wmic.exe / 完整路径)一起传进来,
# 这里先把它剥掉,只留真正的子命令。
$sp = $raw.IndexOfAny([char[]]@(' ', "`t"))
if ($sp -lt 0) {
# 只有一个词:可能是裸别名(wmic process),也可能是无意义的输入
$alias = $raw.Trim().ToLower()
$rest = ''
} else {
$alias = $raw.Substring(0, $sp).Trim().ToLower()
$rest = $raw.Substring($sp).Trim()
}
if ([string]::IsNullOrEmpty($alias)) { return }

$dropLead = $false
if ($alias -eq 'wmic' -or $alias -eq 'wmic.exe' -or $alias -eq 'wmic.cmd') { $dropLead = $true }
if ($alias -match '\\wmic(\.exe|\.cmd)?$') { $dropLead = $true }
if ($dropLead) {
$sp = $rest.IndexOfAny([char[]]@(' ', "`t"))
if ($sp -lt 0) { return }
$alias = $rest.Substring(0, $sp).Trim().ToLower()
$rest = $rest.Substring($sp).Trim()
if ([string]::IsNullOrEmpty($alias)) { return }
}

# ---------- 输出格式开关(可以出现在任意位置)-----------------
$format = 'table'
if ($raw -match '(?i)/format\s*:\s*list\b') { $format = 'list' }
elseif ($raw -match '(?i)/format\s*:\s*csv\b') { $format = 'csv' }
elseif ($raw -match '(?i)(^|\s)/value(\s|$)') { $format = 'list' }

# ---------- 切出 get 子句 ------------------------------------
$propText = $null
$iGet = Find-WmicKeyword $rest 'get'
if ($iGet -ge 0) {
$propText = $rest.Substring($iGet + 3).Trim()
$rest = $rest.Substring(0, $iGet).Trim()
}

# ---------- 切出 where 子句 ----------------------------------
$whereText = $null
$iWhere = Find-WmicKeyword $rest 'where'
if ($iWhere -ge 0) {
$whereText = $rest.Substring($iWhere + 5).Trim()
$rest = $rest.Substring(0, $iWhere).Trim()
}

# ---------- 清掉开关(可能出现在 get / where 之后)-----------
# process get name /format:list
# process where name='x' /value
$rest = (Remove-WmicSwitches $rest).Trim()
if ($null -ne $propText) { $propText = (Remove-WmicSwitches $propText).Trim() }
if ($null -ne $whereText) { $whereText = (Remove-WmicSwitches $whereText).Trim() }

if ($rest.Length -gt 0) {
# 别名后面还有无法识别的成分 → 不猜,直接返回
return
}

# ---------- 解析属性列表 -------------------------------------
$props = @()
if (-not [string]::IsNullOrWhiteSpace($propText)) {
foreach ($p in ($propText -split ',')) {
$t = $p.Trim()
if ($t.Length -gt 0 -and $t -ne '*') { $props += $t }
}
if ($props.Count -eq 0) { $format = 'list' }
} else {
# 没有 get → 输出全部属性,wmic 此时也用 list 格式
$format = 'list'
}

# ---------- 解析类名 -----------------------------------------
$className = $null
if ($AliasMap.ContainsKey($alias)) {
$className = $AliasMap[$alias]
} elseif ($alias.StartsWith('win32_')) {
$className = $alias.Substring(0, 1).ToUpper() + $alias.Substring(1)
} else {
$className = 'Win32_' + $alias.Substring(0, 1).ToUpper() + $alias.Substring(1)
}

# ---------- 规范化 where 条件 --------------------------------
$filter = $null
if (-not [string]::IsNullOrWhiteSpace($whereText)) {
$filter = $whereText
# 整体被引号包起来的情况:where "name='x'"
if ($filter.Length -ge 2) {
$f0 = $filter.Substring(0, 1)
$fN = $filter.Substring($filter.Length - 1, 1)
if (($f0 -eq '"' -and $fN -eq '"') -or ($f0 -eq "'" -and $fN -eq "'")) {
$filter = $filter.Substring(1, $filter.Length - 2).Trim()
}
}
}

# ---------- 查询 ---------------------------------------------
$instances = @()
if ($null -ne $filter) {
try {
$instances = @(Get-CimInstance -ClassName $className -Filter $filter -ErrorAction Stop)
} catch {
# 首次失败:尝试修正属性名大小写后重试一次
$fixed = Repair-WmicFilter $className $filter
if ($fixed -ne $filter) {
try {
$instances = @(Get-CimInstance -ClassName $className -Filter $fixed -ErrorAction Stop)
} catch { $instances = @() }
} else {
$instances = @()
}
}
} else {
try {
$instances = @(Get-CimInstance -ClassName $className -ErrorAction Stop)
} catch { $instances = @() }
}

if ($instances.Count -eq 0) { return }

# ---------- 属性名统一成类里的规范大小写 ----------------------
# wmic 输出的是规范名(ExecutablePath / ProcessId)。调用方若按表头匹配,
# 大小写必须一致;顺手也把写错大小写的属性名纠正过来。
$canonical = @{}
foreach ($cp in $instances[0].CimInstanceProperties) {
if (-not [string]::IsNullOrEmpty($cp.Name)) { $canonical[$cp.Name.ToLower()] = $cp.Name }
}

# ---------- 没指定属性 → 取类的全部属性 ----------------------
if ($props.Count -eq 0) {
foreach ($cp in $instances[0].CimInstanceProperties) { $props += $cp.Name }
} else {
# 只保留类里真实存在的属性:属性名写错时宁可返回空,
# 也不要吐出一堆空行(真 wmic 会直接报 Invalid property)
$norm = @()
foreach ($p in $props) {
$key = $p.ToLower()
if ($canonical.ContainsKey($key)) { $norm += $canonical[$key] }
}
$props = $norm
if ($props.Count -eq 0) { return }
}

# wmic 默认按属性名字母序排列列,这里保持一致
$props = @($props | Sort-Object)

# ---------- 渲染 ---------------------------------------------
if ($format -eq 'list') {
$first = $true
foreach ($inst in $instances) {
if (-not $first) { Write-Output '' }
$first = $false
foreach ($p in $props) {
Write-Output ($p + '=' + (Format-WmicValue $inst.$p))
}
}
return
}

if ($format -eq 'csv') {
$hostName = $env:COMPUTERNAME
if ([string]::IsNullOrEmpty($hostName)) { $hostName = [System.Environment]::MachineName }
Write-Output ('Node,' + ($props -join ','))
foreach ($inst in $instances) {
$cells = @($hostName)
foreach ($p in $props) { $cells += (Format-CsvCell (Format-WmicValue $inst.$p)) }
Write-Output ($cells -join ',')
}
return
}

# table
$rows = @()
foreach ($inst in $instances) {
$cells = @()
foreach ($p in $props) { $cells += (Format-WmicValue $inst.$p) }
$rows += , $cells
}

$widths = @()
for ($i = 0; $i -lt $props.Count; $i++) {
$w = ([string]$props[$i]).Length
foreach ($r in $rows) {
if (([string]$r[$i]).Length -gt $w) { $w = ([string]$r[$i]).Length }
}
$widths += $w
}

Write-Output (Render-WmicRow $props $widths)
foreach ($r in $rows) { Write-Output (Render-WmicRow $r $widths) }
}


# ==== ENTRY ==== (此标记之后为入口,测试时可只取上方的定义部分)
$rawArgs = (@($args) | ForEach-Object { [string]$_ }) -join ' '
if ([string]::IsNullOrWhiteSpace($rawArgs)) { $rawArgs = $env:WBD_WMIC_ARGS }
Invoke-WmicShim $rawArgs
exit 0

A.3 其余四个脚本

安装-wmic垫片.cmd(管理员)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@echo off
chcp 65001 >nul
title 安装 wmic 兼容垫片
setlocal EnableExtensions

net session >nul 2>&1
if errorlevel 1 (
echo.
echo [!] 需要管理员权限。
echo 请右键本文件,选择「以管理员身份运行」。
echo.
pause
exit /b 1
)

set "SRC=%~dp0"
set "DST=%SystemRoot%\System32\Wbem"

echo.
echo 源目录 : %SRC%
echo 目标 : %DST%
echo.
echo 正在复制...
copy /y "%SRC%wmic.cmd" "%DST%\wmic.cmd" >nul || goto :fail
copy /y "%SRC%wmic-shim.ps1" "%DST%\wmic-shim.ps1" >nul || goto :fail
echo [OK] 复制完成。
echo.

echo ---------- 自检 ----------
echo [1] where wmic
where wmic
echo.
echo [2] wmic process where name='idea64.exe' get processid,executablepath
wmic process where name='idea64.exe' get processid,executablepath
echo.
echo 说明:第 [2] 步应输出 idea64.exe 的完整路径。
echo 若为空,请先启动 IDEA 再重试。
echo.
echo 装好后回到 IDEA,重新打开「代码合并工具」即可。
echo.
pause
exit /b 0

:fail
echo.
echo [X] 复制失败。请确认已用管理员身份运行。
echo.
pause
exit /b 1
安装-wmic垫片-免管理员.cmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@echo off
chcp 65001 >nul
title 安装 wmic 兼容垫片(免管理员 / 用户级 PATH
setlocal EnableExtensions

set "SRC=%~dp0"
set "DST=%USERPROFILE%\wmic-shim"

echo.
echo 源目录 : %SRC%
echo 安装到 : %DST%
echo.

if not exist "%DST%" mkdir "%DST%"
copy /y "%SRC%wmic.cmd" "%DST%\wmic.cmd" >nul || goto :fail
copy /y "%SRC%wmic-shim.ps1" "%DST%\wmic-shim.ps1" >nul || goto :fail
echo [OK] 文件已复制

rem ---- 把安装目录追加到「用户级 PATH」(不影响系统 PATH)----
set "UPATH="
for /f "tokens=2,*" %%a in ('reg query HKCU\Environment /v Path 2^>nul ^| findstr /i "Path"') do set "UPATH=%%b"

echo %UPATH% | find /i "%DST%" >nul
if not errorlevel 1 (
echo [--] 用户 PATH 中已存在,跳过
) else (
if defined UPATH (
setx PATH "%UPATH%;%DST%" >nul
) else (
setx PATH "%DST%" >nul
)
echo [OK] 已加入用户 PATH
)

echo.
echo --- 自检 ---
"%DST%\wmic.cmd" process where name='idea64.exe' get processid,executablepath
echo.
echo 上面若能看到 idea64.exe 的完整路径即为成功。
echo.
echo ! 重要:必须完全退出 IDEA(含 JetBrains Toolbox)再重新启动,
echo 新进程才会带上更新后的 PATH,插件才能找到 wmic。
echo.
pause
exit /b 0

:fail
echo [X] 复制失败,请检查目录权限。
pause
exit /b 1
卸载-wmic垫片.cmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@echo off
chcp 65001 >nul
title 卸载 wmic 兼容垫片
setlocal EnableExtensions

net session >nul 2>&1
if errorlevel 1 (
echo.
echo [!] 需要管理员权限。请右键「以管理员身份运行」。
echo.
pause
exit /b 1
)

set "DST=%SystemRoot%\System32\Wbem"

echo.
echo 正在删除 %DST%\wmic.cmd
echo%DST%\wmic-shim.ps1
echo.
del /f /q "%DST%\wmic.cmd" 2>nul && echo [OK] 已删除 wmic.cmd || echo [--] wmic.cmd 不存在
del /f /q "%DST%\wmic-shim.ps1" 2>nul && echo [OK] 已删除 wmic-shim.ps1 || echo [--] wmic-shim.ps1 不存在
echo.
echo 卸载完成,系统已恢复原状。
echo.
pause
exit /b 0
检查安装状态.cmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@echo off
chcp 65001 >nul
title wmic 垫片 - 安装状态自检
setlocal EnableExtensions

echo.
echo ============================================
echo 1. 文件是否到位
echo ============================================
if exist "%SystemRoot%\System32\Wbem\wmic.cmd" (
echo [OK] %SystemRoot%\System32\Wbem\wmic.cmd
) else (
echo [X ] %SystemRoot%\System32\Wbem\wmic.cmd ^<-- 缺失
)
if exist "%SystemRoot%\System32\Wbem\wmic-shim.ps1" (
echo [OK] %SystemRoot%\System32\Wbem\wmic-shim.ps1
) else (
echo [X ] %SystemRoot%\System32\Wbem\wmic-shim.ps1 ^<-- 缺失
)

echo.
echo ============================================
echo 2. cmd 能否解析到 wmic
echo ============================================
where wmic
if errorlevel 1 echo [X ] PATH 里找不到 wmic

echo.
echo ============================================
echo 3. 实测:查 explorer.exe(一直在运行,必定有结果)
echo ============================================
wmic process where name='explorer.exe' get processid,executablepath

echo.
echo ============================================
echo 4. 实测:查 idea64.exe
echo ============================================
wmic process where name='idea64.exe' get processid,executablepath
echo ^(IDEA 没打开时,这里为空是正常的^)

echo.
echo 判定标准:
echo3 步有路径 = 垫片工作正常
echo4 步有路径 = IDEA 正在运行,插件也能检测到
echo.
pause
exit /b 0

附录 B:排查命令速查

B.1 确认 WMIC 状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 系统版本
(Get-CimInstance Win32_OperatingSystem).Caption
(Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion').DisplayVersion
(Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion').UBR

# wmic 是否还在
Test-Path "$env:SystemRoot\System32\wmic.exe"
Test-Path "$env:SystemRoot\System32\wbem\wmic.exe"

# WMI 服务本身是否正常(正常 ≠ wmic 还在)
Get-Service Winmgmt | Format-List Name, Status, StartType

# 组件存储里是否还有 wmic.exe 的载荷
Get-ChildItem "$env:SystemRoot\WinSxS" -Directory -Filter "*wmic*" |
Select-Object -ExpandProperty Name
# 只剩 wmi-wmiclnt 就说明载荷已被彻底删除

# 是否有待重启
Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending"

B.2 用 CIM 代替 wmic 查询

原来(wmic) 现在(PowerShell)
wmic process where name='idea64.exe' get processid,executablepath Get-CimInstance Win32_Process -Filter "Name='idea64.exe'" | Select ProcessId,ExecutablePath
wmic process get name Get-CimInstance Win32_Process | Select-Object Name
wmic process where processid=1234 get executablepath (Get-CimInstance Win32_Process -Filter "ProcessId=1234").ExecutablePath
wmic os get caption (Get-CimInstance Win32_OperatingSystem).Caption
wmic logicaldisk get name,size Get-CimInstance Win32_LogicalDisk | Select DeviceID,Size
wmic service where state='Running' get name Get-CimInstance Win32_Service -Filter "State='Running'" | Select Name
wmic qfe get hotfixid Get-CimInstance Win32_QuickFixEngineering | Select HotFixID

Get-WmiObject 是老的 PowerShell 5.1 写法,PowerShell 7 里已被移除,
新脚本统一用 Get-CimInstance

B.3 排查某个程序是否在用 wmic

1
2
3
4
5
6
7
8
9
10
# 1. 搜脚本仓库(Git Bash / bash)
# grep -ri wmic .

# 2. 看程序日志
Select-String -Path "$env:LOCALAPPDATA\<程序>\log\*.log" -Pattern "wmic" |
Select-Object -Last 40

# 3. 反编译 Java 程序(JDK 自带 javap)
# unzip -l app.jar | grep '\.class'
# javap -c -p -constants com/example/util/e.class

⚠️ Java 字节码里类名和方法名在常量池中是分开存储的
所以想找「谁调用了 util.e.a()」不能直接搜 util.e.a: 这种拼接串,
要分别匹配类名和方法名 + 描述符。更稳的办法是 javap 输出后按行 grep。

B.4 查看进程创建记录(谁在调 wmic)

1
2
3
4
# 装了 Sysmon 的话
Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" -MaxEvents 500 |
Where-Object { $_.Id -eq 1 -and $_.Message -match 'wmic' } |
Select-Object TimeCreated, Message

没有 Sysmon 就用 Process Monitor:过滤 Operation = Process Start
在 Command Line 列里搜 wmic


附录 C:案例复盘 —— 蓝凌 KMSS IDEA 插件

这一节是具体案例,正文的通用方法在这里落地。只想用垫片的话可以跳过。

环境

系统 Windows 11 家庭版 25H2,Build 26200.9457
IDE IntelliJ IDEA Ultimate 2026.1 / 2026.2(JetBrains Toolbox 安装)
IDE 路径 D:\SoftWare\JetBrains\Apps\IntelliJ IDEA Ultimate\bin\idea64.exe
插件 kmssDevPluginIdea-1.0.16.v20251020.jar
插件位置 %APPDATA%\JetBrains\IntelliJIdea2026.2\plugins\kmssDevPluginIdea\lib\

现象:用「代码合并工具」合并代码时报
「调用idea程序出错,无法弹出比对代码窗口,详情请检查日志」,
插件窗口里「idea程序位置」始终为空。

C.1 第 1 步:它到底在调什么

日志路径:%LOCALAPPDATA%\JetBrains\<IDE 版本>\log\idea.log

landray 关键字,拿到确凿证据:

1
2
3
4
com.landray.kmss.plugin.util.RunExecUtil - bat执行出错,worker.exit=1,
bat=cmd.exe /c wmic process where name='idea64.exe' get processid,executablepath
Error:'wmic' 不是内部或外部命令,也不是可运行的程序
com.landray.kmss.plugin.util.e - ideaPath64 为空,bat=wmic process where name='idea64.exe' ...

结论很直接:插件靠 wmic 找 IDEA 的安装路径,wmic 没了 → 路径为空 → 弹框。

C.2 第 2 步:wmic 确实没了

Test-Path 两个位置都是 Falsewhere wmic 找不到,
Winmgmt 服务是 Running / Automatic —— 典型的「外壳没了、能力还在」。

C.3 第 3 步:有没有绕过的口子(反编译确认)

不看代码就只能瞎猜,所以直接把 jar 拆开:

1
2
ls "%APPDATA%\JetBrains\IntelliJIdea2026.2\plugins\kmssDevPluginIdea\lib\"
javap -c -p -constants com/landray/kmss/plugin/util/e.class

几个关键类:

职责
util.RunExecUtil 执行外部命令,超时 2000ms 后强杀进程
util.e a() 方法做 IDEA 路径自动检测(Windows 走 wmic,macOS 走 ps -ef|grep MacOS/idea
dialog.a 「代码合并工具」对话框
view.c 对话框里的一行「标签 + 控件」
view.a 真正拼 diff 命令、启动 IDEA 对比窗口的地方
service.ComparatorService 持久化组件,存到项目 .idea/kmss_dev_config.xml

三个重要发现:

① 「idea程序位置」是只读的,填不了

界面元素 实际类型 能否输入
开发包位置 TextFieldWithBrowseButton ✅ 可输入(存为 idPath
idea程序位置 JLabel 只读

界面上也能佐证:开发包位置右边有「…」浏览按钮,idea程序位置没有 ——
TextFieldWithBrowseButton 一定带浏览按钮,没有就说明它不是输入框。

② 改 .idea/kmss_dev_config.xml 也没用

调用链是单向的:

1
2
3
4
util.e.a() 自动检测  ──►  dialog.a 显示在 JLabel 上

└─ 点「下一步」时才 setIdeaPath() 写进
ComparatorService ──► kmss_dev_config.xml

全 jar 扫下来,没有任何代码读回 ideaPath(没有 getIdeaPath 调用,也没有字段读取)。
它是个只写不读的缓存,改了不生效,而且 IDEA 退出时还会用内存里的空值覆盖掉你的修改。

③ 真正要用路径的是 view.a,而且它会现场再检测一次

1
2
3
4
5
6
7
8
9
String ideaPath = this.l;                       // 对话框传进来的
if (util.o.a(ideaPath) || !new File(ideaPath).exists()) {
ideaPath = util.e.a(); // ← 再调一次 wmic 检测
}
if (util.o.a(ideaPath) || !new File(ideaPath).exists()) {
// 就是用户看到的那个报错
return "调用idea程序出错,无法弹出比对代码窗口,详情请检查日志";
}
// 拼命令:D: && cd "<IDEA目录>" && idea64.exe diff "文件1" "文件2"

结论:IDEA 路径只能靠自动检测拿到,wmic 不通就一定失败,没有任何手工绕过的口子。
→ 只能走第四章的垫片方案。

C.4 结果

垫片装好后:

1
2
3
wmic process where name='idea64.exe' get processid,executablepath
:: ExecutablePath ProcessId
:: D:\SoftWare\JetBrains\Apps\IntelliJ IDEA Ultimate\bin\idea64.exe 26660

插件在 RunExecUtil 的 2000 ms 超时内拿到路径,比对窗口正常弹出。
插件本身一行都没改


附:一页速查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
问题:Win11 24H2+ 移除了 wmic,依赖它的老程序报错或静默失效

诊断三步:
1. 找出程序执行的命令 → 程序日志 / Process Monitor / 字符串搜索 / 反编译
2. 确认 wmic 真没了 → Test-Path System32\wbem\wmic.exe
3. 有没有绕过的口子 → 看代码!「有配置字段」≠「改了有用」

解决:把 wmic.cmd + wmic-shim.ps1 放进 C:\Windows\System32\Wbem\
(该目录本就在 PATH 上,wmic.exe 已不存在 → cmd 会继续往后找到 .cmd)

验证:cmd /c "wmic process where name='explorer.exe' get processid,executablepath"
有路径 = 垫片正常

别踩:DISM /Add-Capability WMIC 会显示「成功」但什么都没装