WMIC 这东西,放到今天看确实有点“老古董”的意思,但你要是翻过运维脚本、看过 PE 维护工具、或者处理过几台老服务器,绝对绕不开它。一条wmic cpu get name敲下去,CPU 型号直接出来;一条wmic process where "name='xxx.exe'" call terminate,进程说杀就杀。不用装额外软件,不用打开图形界面,更不用一台台去点鼠标,这对于批量查机器信息、做资产盘点、远程排查故障来说,实在是太顺手了。
但这几年有个尴尬情况:新一代 Windows 系统默认已经不装 WMIC 了,很多教程和脚本还在用,结果不少人一执行就提示“wmic 不是内部或外部命令”。于是有人困惑,有人干脆放弃,还有人硬着头皮在老环境里继续用。这篇文章我想把 WMIC 命令这件事讲透——它是什么、怎么用、有哪些常见坑、以及和 PowerShell 的 CIM 命令到底该怎么选。适合刚接触命令行的新手,也适合从老脚本 migration 过来的运维老手。不管你最后用不用 WMIC,理解它背后的思路,对排查系统信息和写批处理都会很有帮助。
1. WMIC 是什么,为什么老脚本里全是它
1.1 命名空间、类和实例:WMIC 背后的逻辑
想要真正理解 WMIC,不能光记命令。它本质上是 WMI(Windows Management Instrumentation)的命令行外壳,而 WMI 本身是一套管理规范,把系统里的 CPU、内存、进程、服务、磁盘、网卡等信息都抽象成了“类”。比如说,CPU 信息对应Win32_Processor类,操作系统信息对应Win32_OperatingSystem类,进程对应Win32_Process类。每一台机器上的具体 CPU、具体进程,就是这些类的“实例”。
WMIC 做的事情,就是让你在命令行里对这些类和实例做查询、做修改。它把复杂的 WMI 查询语句转换成了更像自然语言的命令格式。举个例子,底层 WMI 查询可能是SELECT * FROM Win32_Processor,而用 WMIC 只需要敲wmic cpu list brief。这种封装让不熟悉 WQL 查询语法的人也能快速上手。
用生活里的类比:WMI 像一个巨大的档案室,Win32_Processor是其中一个文件柜,“CPU 型号”是抽屉里的档案。WMIC 则是档案室门口的前台,你告诉前台“我要看 CPU 信息”,前台去档案室帮你把文件抽出来递给你。所以本质上,用 WMIC 查信息不是 WMIC 自己知道答案,而是它在背后向 WMI 发起了查询请求。
1.2 为什么十年前大家都在用 WMIC
现在 PowerShell 功能强大,大家可能不太理解为什么老脚本里全是 WMIC。原因主要有这么几个:
第一,出现得早。在 PowerShell 还没大规模普及的年代,WMIC 已经是 Windows 自带的管理工具,装好系统就能用,不需要额外配置。第二,语法简洁。对于只查一两个字段,比如查个 CPU 型号、查个序列号,wmic cpu get name比当时其他方法要短得多。第三,批量友好。配合批处理 for 循环,能够一台接一台采集信息,输出成文本,方便汇总。当时做资产盘点,很多人就是靠 WMIC 批量导出的。第四,支持远程。通过/node、/user、/password参数,可以在命令行里直接连远程机器查信息,这在当时是非常强大的能力。
所以,你翻到的很多老教程、老脚本里,WMIC 几乎是无处不在的标配。即便现在微软已经在新的系统里移除了它,那些老的运维流程、自动化脚本,仍然大量建立在 WMIC 之上。这也是为什么直到今天,你依然需要知道它、能读得懂它。
2. 先把 WMIC 的语法骨架看懂
2.1 全局格式:别名、动词和参数
WMIC 的命令格式其实非常固定,理解了骨架之后,任何命令都能拆解。基本结构是:
wmic [全局开关] 别名 [动词] [参数]- 全局开关:作用于整条命令的选项,比如
/node指定目标机器、/output指定输出位置、/user和/password指定远程连接身份。 - 别名:你要操作的对象,对应某个 WMI 类。常见的有
cpu、os、diskdrive、logicaldisk、process、service、startup、nicconfig、useraccount、share、product等等。 - 动词:对对象执行的操作,通常是
get(获取属性)、list(列表)、call(调用方法)、create(创建)、delete(删除)、set(修改)。默认情况下,只输入别名会自动执行list brief。 - 参数:给动词补充条件。
get后面跟字段名,where后面跟筛选条件,call后面跟方法名。
举个例子:
wmic cpu get name这条命令里,cpu是别名,get是动词,name是参数,表示只需获取 CPU 的name属性。
再比如:
wmic process where "name='notepad.exe'" get processid,executablepath这里就是先where筛选出进程名等于notepad.exe的进程,再get它的processid和executablepath。理解了这个骨架,所有命令都只是在换别名、换动词、换参数而已。
2.2 三大动词:get、where 和 call
get 动词用来获取对象的属性字段。关键词是“字段”,不是整段信息。CPU 有几十个属性,你用get name就只拿名称,用get name,maxclockspeed就多拿最大时钟频率。多个字段之间用英文逗号分隔,中间不需要空格。如果不加字段直接get,则会显示该类的所有属性,输出会非常长,而且很多时候会折行混乱。所以我建议:尽量把需要的字段写全,不要偷懒用裸get。
where 子句用来筛选特定实例。它相当于 SQL 里的WHERE,写法是where "条件",条件用英文双引号包起来。比如:
wmic service where "name='wuauserv'" get state,startmode这条命令查询名为wuauserv(Windows Update 服务)的状态和启动模式。注意,服务名两侧是英文单引号,整个条件外面是英文双引号。如果少了一个引号,命令经常报错。这个细节坑过很多人。
call 动词是用来调用 WMI 类的方法的。比如终止进程,可以用:
wmic process where "name='notepad.exe'" call terminate这里call后面跟着方法名terminate,意思是把筛选出来的进程终止掉。类似的方法还有service的startservice、stopservice,process的create等。call 属于写操作,会影响系统状态,使用前一定要确认筛选条件准确无误。
2.3 关于输出格式的几个细节
WMIC 默认把结果输出到控制台,但控制台宽度有限,经常会出现折行、错位,非常难看。这时候可以用/output全局开关:
wmic /output:"D:\cpu_info.txt" cpu get name,numberofcores这个命令把结果写到指定文件。如果不想要文件,只想去掉表头,可以用format相关的参数:/format:csv、/format:list、/format:table。例如:
wmic cpu get name,numberofcores /format:csv会输出 CSV 格式的文本,方便后续处理。还有/format:list会一行一个字段地输出,适合解析。我自己常用的是list格式,因为字段对应关系清楚,脚本解析不容易出错。
输出还有个经典问题:中文乱码。控制台代码页和输出文件的编码如果不匹配,中文系统信息会变成乱码。通常可以用chcp 65001切到 UTF-8,或者用cmd /u启动 Unicode 模式。后面我会在问题排查部分细说。
3. 日常运维最常用的 WMIC 实操场景
3.1 一条命令摸清一台机器的配置
拿到一台“新”机器,不管是入职配发的电脑,还是刚上架的服务器,我习惯先用 WMIC 把核心配置拉一遍。下面这几条命令基本可以覆盖 80% 的信息需求。
CPU 型号与核心数:
wmic cpu get name,numberofcores,numberoflogicalprocessors,maxclockspeed输出大概是这样的:
MaxClockSpeed Name NumberOfCores NumberOfLogicalProcessors 2808 Intel(R) Xeon(R) CPU E5-2680 v4 @ 2.40GHz 14 28注意MaxClockSpeed有时候显示的是标称频率,有时候显示当前睿频,具体看系统版本。别太纠结这个值。
内存容量与内存条型号:
wmic memorychip get capacity,speed,manufacturer,partnumber这条命令输出的是每条物理内存的信息,而不是“总内存”。如果要看操作系统能识别的总内存,用:
wmic computersystem get totalphysicalmemory注意单位是 KB,不是 GB。我之前就犯过迷糊,把几千万 KB 直接当字节看,以为机器内存爆炸了。
主板序列号与整机序列号:
wmic bios get serialnumber wmic baseboard get serialnumber,product,manufacturer整机序列号有时候在bios里,有时候在baseboard里,不同品牌型号位置不一样,两条都跑一下。这个在资产盘点时特别重要。
操作系统版本与安装时间:
wmic os get caption,version,installdate,lastbootuptimeinstalldate和lastbootuptime是 CIM 日期格式,比如20240115093000.000000+480,表示 2024 年 1 月 15 日 9 点 30 分。需要转换一下,但信息本身很有价值,特别是排查“这台机器多久没重启了”的时候。
硬盘型号与序列号:
wmic diskdrive get model,serialnumber,size,interfacetype这里要说明一下,size是逻辑大小,单位字节,要除以 1,000,000,000 才是常见的“多少 GB”。而serialnumber是硬盘固件序列号,不是你在资源管理器里看到的盘符。
3.2 进程与服务管理三板斧
查配置是只读操作,相对安全。但在日常运维里,更常用的是进程和服务的排查功能。
列出所有正在运行的进程名称和 PID:
wmic process get name,processid,executablepath如果只想找某个进程,加where:
wmic process where "name='java.exe'" get processid,executablepath,commandline这里commandline属性特别有用,它能显示进程启动时带了什么参数,可以用来判断这个 Java 进程到底属于哪个应用。很多老运维排查端口占用,第一步就是用这条命令先定位进程。
终止进程:
一个进程卡死了、杀不掉,可以用:
wmic process where "processid=1234" call terminate也可以按进程名批量终止:
wmic process where "name='notepad.exe'" call terminate注意,按进程名操作时要格外小心,因为可能同时有多实例运行。比如用户开了三个记事本,你这么一敲,全给关掉了。所以优先用 PID,或者加commandline条件精确匹配。
查询服务状态与启动类型:
wmic service where "name='wuauserv'" get state,startmode,pathnamestate表示当前状态,startmode表示开机启动类型。排查某个服务为什么起不来,先看startmode是不是Auto,再看state是不是Running,然后再查依赖。
查看开机启动项:
wmic startup list full这条命令会列出所有开机启动项,包括注册表启动项和启动文件夹里的项目。输出包含Caption、Command、Location等字段,排查开机慢、可疑自启动时非常有用。我见过很多恶意软件会往启动项里写东西,用这条命令能很快看到异常。
3.3 资产盘点:把信息导出成文件
资产盘点的时候,不可能一台台抄写。用/output加get,可以一次性把一台机器的信息导出来。
我最常用的组合是:
wmic /output:"C:\pc_info.csv" cpu get name,numberofcores /format:csv wmic /output:"C:\pc_info.txt" bios get serialnumber两台机器可以写成一个批处理,然后用批处理变量拼接文件名,批量执行后统一汇总。比如:
@echo off set pcname=%COMPUTERNAME% wmic /output:"C:\info_%pcname%.txt" os get caption,version wmic /output:"C:\info_%pcname%.txt" cpu get name,numberofcores wmic /output:"C:\info_%pcname%.txt" bios get serialnumber注意这里有个细节:/output指定的同一文件,多个 WMIC 命令会覆盖而不是追加。所以在批处理里要每个命令一个文件名,或者先输出到临时文件再合并。我之前吃过这个亏,三条命令指向同一个文件,最后只剩最后一条的内容。
产品列表导出用得也很多,比如列出机器上安装的所有软件名称和版本:
wmic product get name,version不过提醒一句,product别名执行起来非常慢,比从注册表卸载项读数据要慢得多,因为它在后台会触发 WMI 的产品审计,逐项核对已安装产品的信息。机器上软件一多,可能要跑好几分钟。如果你只是想知道装了什么软件,我更推荐用注册表路径查询,或者直接用 PowerShell 的Get-ItemProperty。但product的好处是它能获取到部分软件完整的version和installsource,有时候排查软件冲突会用得上。,执行前要做好心理准备,别以为它卡死了。
4. 远程查询与踩坑实录
4.1 远程机器的三种连接方式
WMIC 能远程查询,这是它曾经的招牌功能。基本格式是在命令最前面加/node、/user、/password。
wmic /node:"192.168.10.20" /user:"administrator" /password:"yourpassword" cpu get name这条命令会远程连到 IP 为 192.168.10.20 的机器,使用 administrator 身份,查询 CPU 名称。
连接远程机器的过程依赖几个关键条件:
- 目标机器开启 WMI 服务:也就是 Windows Management Instrumentation 服务(Winmgmt),默认是启动的。
- 目标机器防火墙允许 WMI 访问:入站规则需要放行“Windows Management Instrumentation(WMI)”或者 TCP 135 端口,以及后续 WMI 动态分配的高位端口。如果防火墙配置不当,经常出现“节点错误”或“拒绝访问”。
- 本地账号有足够权限:目标机器上用于连接的账号必须在
Administrators组或WinRMRemoteWMIUsers组中。如果是在域环境里,域管理员账号一般都可以;如果是工作组环境,可能还需要配置本地账号的“通过网络访问此计算机”权限。 - UAC 远程限制:对于内置本地管理员账号,Windows 默认会通过 UAC 过滤掉大部分远程管理权限。如果直接使用内置 administrator 还提示拒绝访问,往往需要修改
LocalAccountTokenFilterPolicy注册表值,或者使用域账号。
这里我多说一句:远程操作属于高风险行为,在真实生产环境里,密码写到命令行里本身就是不安全的行为。/password是明文出现在命令行里的,可能被其他进程看到,也会留在命令历史中。所以我才建议:能本地操作的就本地操作,远程操作尽量使用已授权的远程管理平台,或者用 PowerShell 的Enter-PSSession配合受控会话。
4.2 我踩过的几个典型坑
这些年用 WMIC 遇到过不少问题,挑几个典型的说说,大家遇到的时候能少走弯路。
报错“节点错误”或者“RPC 服务器不可用”
这种基本都是网络不通或者防火墙拦截。排查步骤:先ping目标机器确认能通;再用telnet 目标IP 135测试 135 端口是否可达;最后检查目标机器的 Windows 防火墙里 WMI 入站规则是否启用。如果目标在域环境,可能还要检查组策略是否禁用了 WMI 相关规则。
报错“拒绝访问”
账号权限问题。优先级从高到低排查:账号是否在管理员组;账号密码是否正确;内置 Administrator 账号是否被 UAC 过滤。一个很隐蔽的坑:如果目标机器启用了“仅来宾”本地账户模型,则任何非域账号都无法远程 WMI。检查注册表LocalAccountTokenFilterPolicy,设为 1 可以取消内置管理员远程 UAC 限制,但这个修改需要重启服务或重启系统才生效。
中文信息乱码
比如查操作系统版本,或查软件名称,结果显示一堆????,这是因为控制台代码页和输出编码不匹配。解决办法一是执行chcp 65001切到 UTF-8 后再跑 WMIC;二是用/format:csv输出到文件再打开,通常也是 UTF-8 编码;三是用cmd /u启动 Unicode 模式,这样输出到文件的就是 UTF-16,记事本打开不会乱。
64 位与 32 位路径问题
新版 Windows 里 WMIC 已不再默认存在,老系统则存在 64 位和 32 位两套权限环境。当你用一个 32 位进程去调用 WMIC 时,系统可能会重定向到SysWOW64目录下的 WMI 工具,这会导致某些底层系统信息读取不到,比如读取Win32_LogicalDisk时出现 64 位/32 位路径不一致的问题。解决办法是在 64 位环境下用%SystemRoot%\System32\wbem\WMIC.exe的完整路径调用。
杀毒软件拦截
WMIC 这类命令行工具经常被安全软件视为高风险操作,尤其是在执行process call terminate、service call stopservice这类敏感操作时。如果脚本在一台机器上正常、在另一台机器上报错“拒绝访问”或者“找不到文件”,先看看是不是安全软件拦截了命令行调用。
4.3 权限与安全边界
WMIC 是一把双刃剑,查询信息很方便,写操作也非常直接。比如:
wmic service where "name='某服务'" call stopservice这一条命令就能把服务停掉。如果你远程执行这个命令且没有精确控制目标范围,影响范围可能远超预期。
所以我对使用 WMIC 有几个原则:
- 只读查询优先:能用
get、list解决的就不要用call、delete。 - 写操作必须有审计:在批处理脚本里执行
call terminate或delete时,至少先输出确认信息,或者通过时机和参数限制避免误操作。 - 不要把明文密码写进脚本:如果非要远程使用 WMIC,用交互式输入密码,或者用更安全的连接方式,比如先用
net use建立受控连接,再执行 WMIC 命令。 - 生产环境操作前先备份或演练:特别是对服务器批量执行命令之前,先在测试机上验证筛选条件和影响范围。
5. 从 WMIC 平滑迁移到 PowerShell
5.1 命令对照表:CIM 替代方案
现在新装系统默认没有 WMIC,我们总不能在后续脚本里依赖一个不存在的命令。PowerShell 里对应的命令是 CIM 相关 cmdlet,核心是Get-CimInstance。它和 WMIC 使用的 WMI 类是一模一样的,只是调用方式不同。
我把常用的 WMIC 命令和对应的 PowerShell 命令整理成了一张对照表:
| 场景 | WMIC 命令 | PowerShell 命令 |
|---|---|---|
| 查询 CPU 信息 | wmic cpu get name,numberofcores | Get-CimInstance Win32_Processor | Select-Object Name,NumberOfCores |
| 查询操作系统信息 | wmic os get caption,version | Get-CimInstance Win32_OperatingSystem | Select-Object Caption,Version |
| 查询物理内存 | wmic memorychip get capacity,speed | Get-CimInstance Win32_PhysicalMemory | Select-Object Capacity,Speed |
| 查询磁盘信息 | wmic diskdrive get model,serialnumber,size | Get-CimInstance Win32_DiskDrive | Select-Object Model,SerialNumber,Size |
| 查询进程列表 | wmic process get name,processid,executablepath | Get-CimInstance Win32_Process | Select-Object Name,ProcessId,ExecutablePath |
| 按条件杀进程 | wmic process where "name='x.exe'" call terminate | Get-CimInstance Win32_Process -Filter "Name='x.exe'" | Invoke-CimMethod -MethodName Terminate |
| 查询服务状态 | wmic service where "name='x' " get state | Get-CimInstance Win32_Service -Filter "Name='x'" | Select-Object State |
| 查询启动项 | wmic startup list full | Get-CimInstance Win32_StartupCommand | Select-Object * |
| 查询已安装软件 | wmic product get name,version | Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName,DisplayVersion |
注意,Get-CimInstance的-Filter参数先于-Query执行,效率比Get-WmiObject -Query更高。而且Get-CimInstance输出的是对象,不是格式化文本,在管道里做筛选、排序、导出非常方便。
5.2 为什么新环境建议直接用 CIM
新系统默认不带 WMIC 只是表面原因。更深层的原因在于,WMIC 是十几年前的设计,它在新时代有两个明显短板:
第一,输出是文本流。没有结构化的对象,想在批处理里提取某个值,要么用for /f切分,要么用 findstr 匹配,非常原始。PowerShell 直接输出对象,取属性就是.PropertyName,做条件判断、循环都方便得多。
第二,兼容性考虑。WMIC 在一些新版本的系统上已经没有了,即便手动从旧系统拷贝 wmic.exe 过去,也可能因为缺少配套的 MOF 文件或WMIC.exe依赖项而无法正常工作。与其维护这种“补丁式”方案,不如直接在脚本里用Get-CimInstance,一步到位。
不过话说回来,PowerShell 也有自己的坑。比如早期版本的 PowerShell 中Get-WmiObject与Get-CimInstance的输出格式和可用方法并不完全一致。Get-WmiObject直接绑定 WMI 对象,而Get-CimInstance返回的是 CIM 实例,方法调用需要通过Invoke-CimMethod,一开始会有点不习惯。但熟练之后,CIM 的体验更好,因为它可以通过-CimSession轻松复用远程连接,省去反复验证身份的过程。
5.3 迁移小技巧和兼容脚本写法
如果你和我一样,手里有一批老脚本还在用 WMIC,又想让它们在新系统上也能跑,可以加一个兼容层。思路很简单:判断当前系统有没有 wmic.exe,有就用 WMIC 执行,没有就调用 PowerShell 的 CIM 命令。
批处理里可以这样写:
@echo off where wmic >nul 2>nul if %errorlevel%==0 ( wmic cpu get name,numberofcores ) else ( powershell -Command "Get-CimInstance Win32_Processor | Select-Object Name,NumberOfCores" )这样新老环境都能跑,不会因为缺了 WMIC 直接报“不是内部或外部命令”。缺点是两套命令都要维护,适合过渡阶段。
更推荐的做法是:把老的 WMIC 查询统一改成 PowerShell 脚本,至少新写的脚本不再依赖 WMIC。下面是一个采集系统信息的 PowerShell 示例,可以作为参考:
$cpu = Get-CimInstance Win32_Processor $os = Get-CimInstance Win32_OperatingSystem $bios = Get-CimInstance Win32_BIOS $info = [PSCustomObject]@{ ComputerName = $env:COMPUTERNAME CPUName = $cpu.Name CoreCount = $cpu.NumberOfCores OSVersion = $os.Caption LastBoot = $os.LastBootUpTime BIOSSerial = $bios.SerialNumber TotalMemoryGB = [math]::Round($os.TotalVisibleMemorySize / 1MB, 2) } $info | Export-Csv -Path "C:\info.csv" -NoTypeInformation -Encoding UTF8这个脚本读配置、算内存、导出 CSV,一次完成。TotalVisibleMemorySize单位是 KB,所以要除两次 1024 转成 GB。Export-Csv默认会带类型信息,加上-NoTypeInformation去掉,导出的 CSV 更干净。
迁移过程中有个小技巧:先在命令行里用Get-CimInstance Win32_Processor直接敲一遍,看输出,再用Select-Object慢慢裁剪字段,最后才写进脚本。不要一步到位试图背命令,CIM 类的属性名和 WMIC 的别名映射不是一一对应的,实际看一下最稳。
最后再说一点经验
我自己现在的习惯是:新机器、新脚本默认用 PowerShell CIM,老系统上应急排查才用 WMIC。反正两条路子本质都是访问同一套 WMI 数据,掌握其中一个再迁移到另一个并不难。关键在于理解背后的“类”和“属性”概念,而不是死记命令拼写。
如果有人问我有没有必要专门去学 WMIC,我的回答是:值得了解,但不必在新项目里死守它。碰到了老脚本能读、能改,遇到没有 PowerShell 的环境(比如某些 PE 维护系统)能应急,就足够了。而如果你正在维护一批依赖 WMIC 的自动化脚本,我的建议很直接:别拖,尽早把核心功能迁到Get-CimInstance,然后给脚本加一层兼容判断,让它在新旧系统上都能跑。这样以后系统升级了,你也不用半夜被喊起来改命令。
最后分享一个保存命令输出的小技巧:用wmic ... /format:list输出到文件时,每行是一个字段,后续用来做 diff 对比特别方便。比如你怀疑两台机器配置有差异,把两台机器的list输出存成文件,再用fc命令对比,哪里不一样一眼就看出来了。这是纯文本时代留下来的笨办法,但很多时候比图形界面对比还直观。