☰
WMI与CIM核心区别解析:从Get-WmiObject到Get-CimInstance的迁移指南
2026/10/16 9:58:27 网站建设 项目流程

搞 Windows 运维久了,几乎每个人都会被“WMI 查询失败”这句话折磨过。我前阵子整理一堆历史脚本时,就同时见惯了 WMI 和 CIM 两种写法:老代码里一水儿的 Get-WmiObject,新环境里大家又都在推 Get-CimInstance。WMI 从 Windows 早期就存在,几乎垄断了本地和远程系统信息的读取方式;CIM 则是这些年逐渐普及的规范化协议体系。这篇文章我准备把 WMI 与 CIM 的区别完全摊开,从来历、架构、命令行差异、远程连接、迁移注意事项和常见排错点全部梳理一遍。如果你正在读旧脚本、做新脚本选型,或者排查系统信息采集失败,这份笔记应该能帮你少走不少弯路。

1. 追根溯源:WMI 是什么,CIM 又是从哪来的

1.1 CIM 先是一套“通用语言”

CIM 的全称是通用信息模型(Common Information Model),它本身不是软件,也不是某个命令行工具,而是一套行业标准。这套标准定义了一台计算机里的硬件、软件、网络设备、服务、用户等元素,应该用什么结构来描述。

我习惯把它类比成“体检报告的统一模板”。各家医院的设备和检查手法可以完全不同,但只要都按同一套模板写体检结果,拿到报告的人不用额外解码,就能看懂“血压 120/80”“空腹血糖 5.2”这些字段代表什么。CIM 起的作用也是这样:它规定 CPU 有哪几个属性、网卡有哪几个属性、系统服务有哪几个方法,让不同厂商的软件都能按照同一套模型对话。

所以第一个关键结论要记住:CIM 是“模型”,是“标准”,是一种描述世界观的方式,它不直接绑定某一种操作系统。

1.2 WMI 是 Windows 平台上的“老牌落地实现”

有了标准,自然要有实现。WMI(Windows Management Instrumentation)就是 Windows 平台上历史最悠久、覆盖面最广的那套 CIM 实现之一。它的设计初衷就是让管理员能够用统一接口查询和修改系统信息,你平时见过的 Win32_Processor、Win32_ComputerSystem、Win32_Service 这些类名,都是 WMI 暴露出来的。

WMI 并不是独立存在的。它由几个部件拼在一起:Winmgmt 服务负责管理 WMI 仓库,仓库里存着类和实例的数据;各种 Provider 负责跟硬件、进程、事件打交道;WQL 则是一种类似 SQL 的查询语言,让你可以写类似“找出所有使用超过 2GB 内存的进程”这样的筛选条件。

从 Windows NT 时代开始,WMI 就逐渐成为系统管理和监控工具的必选接口。许多第三方监控软件、备份工具、自动化脚本,底层都在调用 WMI。这也解释了为什么你会在老掉牙的脚本里看到大段大段的 Get-WmiObject。

1.3 新一代 CIM 实现:换了个更现代的“落地方式”

你可能会问:那 CIM 既然是标准,为什么现在又冒出一个“CIM 命令”?其实 PowerShell 从 3.0 开始提供的一整套以 Cim 开头的 cmdlet,背后还是 CIM 标准,只是换了新的实现方式和协议通道。以前 WMI 主要靠 DCOM/RPC 这套老协议走路,现在新一代 CIM 命令默认走 WS-Man(也就是 WinRM 服务),拿到的是一个 CimInstance 对象,而不是老的 ManagementObject。

更重要的是,新一代 CIM 实现不再死守 Windows 平台。在 PowerShell Core 跨平台版本里,Get-CimInstance 也能在 Linux、macOS 上通过标准 CIM 服务访问系统信息,这让原本写在 Windows 上的采集脚本有了更大的迁移空间。所以你可以把 CIM 理解成“同一个标准换了更适合现代环境的新实现”。

2. 架构设计:表面都在“查信息”,骨子里是两套玩法

2.1 WMI 的经典架构与“祖传短板”

WMI 的经典架构是围绕 DCOM 和 RPC 建起来的。本地查询时,客户端调用 WMI 服务,服务再指挥各个 Provider 去收集数据;远程查询时,客户端需要通过 RPC 和 DCOM 连接到目标机器,握手、鉴权、建立连接这些环节一个都不能少。

这种架构有一个非常现实的痛点:远程端口很难收敛。DCOM 默认走 TCP 135 端口,但后续真正传输数据的端口往往是动态协商出来的,范围大概在 49152 到 65535 之间。也就是说,你想放开防火墙让 WMI 远程通行,只放行 135 往往不够,还得开放一大段动态端口范围,这在企业网络里是非常让人头大的事。

另外,WMI 的 Provider 运行在 Wmiprvse 进程里,如果某个 Provider 写得有问题,整个进程可能持续占用 CPU,甚至会拖累同一台机器上的其他管理操作。我见过不止一次因为某个第三方软件的 WMI Provider 异常,导致系统日志里一小时刷几百条事件记录的情况。

2.2 CIM 的新实现:协议更规整,连接可复用

新一代 CIM 命令在设计上刻意绕开了 DCOM 的雷区。它默认通过 WS-Man 协议访问远程机器,实际承载者是 WinRM 服务,传输端口默认是 5985(HTTP)和 5986(HTTPS)。端口规则简单,防火墙策略很容易写,也不容易出现动态端口打不开的尴尬。

另一个体验上的巨大差异是会话复用。WMI 的 -ComputerName 参数本质上每次调用都会重新建立一条远程连接;而 CIM 命令支持 New-CimSession 创建一个持久会话,之后反复查询都是复用同一条通道。这一点在批量巡检场景里尤其重要,后面我会给对比。

2.3 一张表看懂架构差异

对比维度WMICIM(PowerShell 视角)
标准定位对 CIM 标准的经典实现同样是 CIM 标准的实现,但采用新一代协议体系
远程协议DCOM / RPCWS-Man(WinRM),可选 HTTPS 加密
默认远程端口135 + 动态协商端口5985 / 5986,可通过端口转发或自定义监听器调整
核心服务WinmgmtWinRM
跨平台能力仅限 WindowsPowerShell Core 可跨 Linux/macOS
返回对象ManagementObjectCimInstance
发展趋势基本冻结,遗留为主新脚本、新功能的推荐方向

如果你以前只用过 WMI,可以把这页表格当作快速记忆点:CIM 解决了 WMI 最让人头疼的网络层问题,也把返回对象做得更适合自动化处理。

3. 实际操作对比:从 Get-WmiObject 到 Get-CimInstance

3.1 命令对应关系先记一遍

PowerShell 里 WMI 系列 cmdlet 和 CIM 系列 cmdlet 基本一一对应,先把这张表存下来,后面用的时候就省得查:

功能WMI 写法CIM 写法
查询实例Get-WmiObjectGet-CimInstance
调用方法Invoke-WmiMethodInvoke-CimMethod
创建实例Set-WmiInstanceNew-CimInstance / Set-CimInstance
删除实例Remove-WmiObjectRemove-CimInstance
注册事件Register-WmiEventRegister-CimIndicationEvent

这里要坦白提醒:在 PowerShell 7 和 PowerShell Core 环境里,老一代 WMI cmdlet 已经不再被推荐,甚至在很多新版本中没有原生提供。这不是社区说说的,是实实在在的兼容性趋势。所以新项目尽量别开倒车。

3.2 类名相同,参数有细微差别

拿查询 Win32_ComputerSystem 举例,老写法是:

$old = Get-WmiObject -Class Win32_ComputerSystem -ComputerName server01

新写法是:

$new = Get-CimInstance -ClassName Win32_ComputerSystem -ComputerName server01

光看代码,几乎只是把类名从 -Class 换成了 -ClassName。实际上两个命令拿到的对象类型并不一样,一个是 ManagementObject,一个是 CimInstance。你日常取 Name、Manufacturer、Model 这些属性时,二者输出基本看不出差别;但一旦涉及集合、嵌套对象或者序列化,差异就很明显了。

举个例子,如果你要把采集结果转成 JSON 或写进日志,CimInstance 转出来的结构通常更干净,因为它是专门为脚本间传递和工具链复用设计的数据模型。而老式 WMI 对象带了一堆元信息,序列化时经常要额外处理,否则会多出一堆没用的属性。

3.3 筛选条件的写法差异

筛选进程时,老代码常用 -Filter:

Get-WmiObject -Class Win32_Process -Filter "Name LIKE '%powershell%'"

新写法也支持 -Filter:

Get-CimInstance -ClassName Win32_Process -Filter "Name LIKE '%powershell%'"

稍微复杂一点的例子,以前有人习惯用 -Query 直接写 WQL:

Get-WmiObject -Query "SELECT * FROM Win32_Process WHERE Name LIKE '%powershell%'"

Get-CimInstance 也有 -Query 参数可以接收同样的 WQL,不过在写新脚本时,我建议优先用 -Filter,少用完整查询串。原因很简单:-Filter 参数更直观,筛选条件的转义规则也更一致,出问题时容易定位。如果你要把老脚本迁移过来,可以先保留老写法一次性验证,再逐条换成参数化写法,不要图省事全部一步到位。

3.4 批量远程场景,会话复用的差距是数量级的

这是我在使用中感受最深的一点。批量采集几十台机器的系统信息时,用 WMI 的 -ComputerName 参数,每查一台机器都要重新握手、鉴权、建立 DCOM 连接,速度慢不说,还容易触发目标机器的并发连接限制。而 CIM 的 New-CimSession 能把一堆机器一次性纳入会话,之后连续查询复用同一通道,开销明显低很多。

一个典型做法是这样的:

$sessions = New-CimSession -ComputerName server01, server02, server03 foreach ($s in $sessions) { Get-CimInstance -CimSession $s -ClassName Win32_OperatingSystem | Select-Object CSName, Caption, Version } Remove-CimSession -CimSession $sessions

对比一下没有会话概念的 WMI 写法:

foreach ($node in "server01", "server02", "server03") { Get-WmiObject -Class Win32_OperatingSystem -ComputerName $node }

我在某个内网虚拟化环境里实测过,机器数量在 10 台左右、查询同一组系统信息时,CIM 会话方式比逐台 WMI 方式快数倍到十几倍,具体数字取决于网络延迟和中间防火墙策略。必须说明这只是个人环境经验,不能当官方基准,但方向已经很明显:批量任务优先把多个目标放进 CimSession。

4. 场景选型与迁移心态:什么时候用哪个

4.1 暂时继续用 WMI 的场景

听到“Get-WmiObject 已过时”并不意味着你今晚就要把所有脚本推倒重来。有些场景里,老接口仍然发挥着不可替代的作用。

最典型的情况是存量脚本和存量工具。如果你的线上有一大批写着 Get-WmiObject 的调度脚本,并且它们跑得很稳定,贸然全量替换反而可能引入新问题。还有不少第三方监控软件、备份代理,底层依旧直接调用 WMI 接口,这些不是你改几个 PowerShell 命令就能改变的。

另外,如果你是维护 Windows 事件订阅那一类老功能,可能还是需要 WMI 事件相关的语法。新一代 CIM 命令虽然也提供事件注册能力,但把老订阅迁移过去并不总是简单的一对一替换,涉及触发条件、永久订阅和 Provider 支持问题,必须单独评估。

所以我的建议是:老环境里不急于清理,但要在心里竖起一根标记——这些代码总有一天要动。

4.2 新脚本和跨平台需求,应该直接选 CIM

新项目,尤其是新写的自动化脚本、巡检脚本、CMDB 数据采集脚本,我会毫无保留地推荐 CIM 家族。理由不只是“新东西更好看”,而是协议层带来的实际收益:端口好管理、会话可复用、对象更适合序列化和推送、跨平台支持。

如果你的环境里有 Linux 节点,或者你在用 PowerShell Core 运维异构环境,CIM 几乎是唯一明智的选择。老式 WMI 在非 Windows 环境里根本没戏,而 CIM 可以借助标准 CIM 服务访问操作系统信息,至少让采集脚本有机会统一。

还有一点容易被忽略:CIM 返回的 CimInstance 对象在处理外键、关联、实例引用时更规整。比如你要从 Win32_Service 关联到 Win32_Process,直接用对象的关联属性再查询,整体代码会清爽很多。

4.3 一份简单的迁移检查清单

如果你决定迁移某个核心脚本,可以参考下面这个清单,而不是上来就改。

检查项WMI 写法CIM 写法
类名Win32_ComputerSystem同样使用 Win32_ComputerSystem,绝大多数类名兼容
命名空间-Namespace root\cimv2默认也是 root/cimv2,必要时写 -Namespace
远程目标-ComputerName 每次重连推荐 New-CimSession 创建会话后复用
筛选条件-Filter 或 -Query 均可同样支持,新代码建议 -Filter
方法调用Invoke-WmiMethodInvoke-CimMethod
事件注册Register-WmiEventRegister-CimIndicationEvent

迁移过程中最容易翻车的其实不是命令本身,而是输出属性的兼容性。有些老脚本会直接读取对象里的特定属性,甚至依赖属性名的字母大小写和数据类型,CIM 对象在这些细节上偶尔会有区别。稳妥的做法是先在测试机上并行跑两种写法,导出结果做 diff,确认输出一致后再替换正式代码。

5. 常见问题与排查实录

5.1 本地“拒绝访问”不一定是权限问题

在本地跑 Get-WmiObject 遇到“拒绝访问”时,老手的第一反应是:你是管理员吗?很多 WMI 类只有管理员权限才能读取,比如 Win32_OperatingSystem 的一些安全属性、系统日志类、磁盘卷信息等。先拿管理员身份重新开 PowerShell 再跑一遍,能排除一大半问题。

如果你确认是管理员身份还是报 0x80070005,那就得看 WMI 命名空间本身的权限配置。打开 WMI 管理控制台(命令是 wmimgmt.msc),找到对应命名空间的“安全设置”,看看当前账户所在用户组有没有被授予“启用账户”“远程启用”等权限。这一步很容易被忽略,因为默认配置在你的域策略被改动过以后,会和文档描述完全不同。

5.2 远程 WMI 连不上,先查 RPC 端口和 UAC 限制

远程 WMI 连不上的经典现象是 RPC 服务不可用,报错里常带着 0x800706BA。原因通常有两类:一类是防火墙没放行 TCP 135 和动态 RPC 端口;另一类是目标机器上“文件和打印机共享”相关规则被策略禁用了,导致 RPC 通信被挡。

还有一个非常典型的 Windows 坑:远程 UAC 限制。当你用本地管理员账户去远程调用 WMI 时,系统默认会把令牌降级成普通管理员,很多 WMI 查询就会拒绝访问。解决办法是修改注册表的 LocalAccountTokenFilterPolicy,但这会降低系统安全性,必须在可控环境里评估后再动,不能随手乱改。

5.3 CIM 远程连接超时,多半是 WinRM 没准备好

新版 CIM 命令远程访问失败时,报错一般会提到 WinRM。先跑一句测试命令判断基础连通性:

Test-WSMan -ComputerName server01

如果能返回服务信息,说明 WinRM 监听器基本正常;如果报错,再检查 WinRM 服务是否启动、防火墙是否放行了 5985/5986。快速启用 WinRM 可以在目标机器上执行:

winrm quickconfig

在工作组环境里,CIM 远程还会要求客户端把目标机器加入 TrustedHosts,不然即使网络通、服务正常,也仍然会因为“不受信任的主机”而失败。配置方式如下:

Set-Item WSMan:\localhost\Client\TrustedHosts -Value "server01" -Force

在域环境里通常不需要这步操作,因为 Kerberos 认证已经帮我们把信任问题处理了。

5.4 快速排查表

报错或现象可能原因快速排查思路
0x80070005 拒绝访问权限不足或远程 UAC 限制改用管理员身份,检查命名空间权限和 LocalAccountTokenFilterPolicy
0x800706BA RPC 服务不可用防火墙没放行 135/动态端口,目标 WMI 服务异常检查 Winmgmt 服务,检查防火墙规则,用 wbemtest 本机验证
Test-WSMan 失败WinRM 服务未启动或未配置监听器在目标机器运行 winrm quickconfig,确认服务状态
连接超时网络隔离、防火墙、端口未开用 Test-NetConnection 验证 5985/5986,检查 TrustedHosts
属性为空或查询无结果查询的类在目标系统不存在确认命名空间和类名是否一致,必要时用 Get-CimClass 列出可用类

5.5 一个我踩过的真实坑

有次我把某个巡检脚本从 Get-WmiObject 批量改成 Get-CimInstance,跑了两天都正常,换到另一个机房后发现大量超时。排查半天才发现,目标机器的 WinRM 服务被安全基线策略停用了,但为了满足安全扫描要求,网络策略又没有放行 5985。类似这种“本地能查、远程不能查”的情况,靠的就是上面那几条命令逐步缩小范围,没有别的捷径。

6. 经验总结与选择建议

如果让我今天搭一个新的运维脚本库,我会毫不犹豫把 CIM 家族当成默认选项。理由前面已经写过一堆:协议规整、会话复用、跨平台、对象适合自动化流转,这些都是实打实的好处。但我也绝不会因为 CIM 成了主流,就放弃看懂老脚本里的 WMI。毕竟生产环境里还躺着大量老代码,第三方工具也没有全部切换到新接口,突然一刀切只会把自己陷入被动。

我个人的迁移节奏是先做摸底,把一个脚本文件里所有 Get-WmiObject、Invoke-WmiMethod、Register-WmiEvent 都列出来,再逐个对照改成 CIM 版本,每改一个就用同样的查询结果做一次输出对比。确认属性、类型、筛选逻辑都一致后再换上去。别追求一次全部改完,改得越猛,出问题时越难定位。

最后分享一个我一直在用的兼容写法:可以在脚本里先判断环境里有没有 Get-CimInstance 命令,有就优先用 CIM,没有就退回 WMI。比如下面的简化函数:

function Get-ComputerSystemInfo { param([string]$ComputerName = $env:COMPUTERNAME) if (Get-Command Get-CimInstance -ErrorAction SilentlyContinue) { Get-CimInstance -ClassName Win32_ComputerSystem -ComputerName $ComputerName } else { Get-WmiObject -Class Win32_ComputerSystem -ComputerName $ComputerName } }

这段函数不复杂,但能让同一份脚本在老 PowerShell 5.1 和新 PowerShell 7 之间都能跑,也避免在 CIM 不可用或 WMI 不可用时直接报错。另外提醒一句:不管是 WMI 还是 CIM,先在测试机器上跑通,再铺到生产脚本里。系统管理的脚本,稳比快更重要——这句话是我改脚本踩了多次坑之后才认同的。接下来真正要做的事情很简单:打开 PowerShell,拿自己的机器做一次双边查询,你会发现它们的区别其实远没有想象中那么玄。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询