
搞 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 平台上的“老牌落地实现”有了标准自然要有实现。WMIWindows 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 服务传输端口默认是 5985HTTP和 5986HTTPS。端口规则简单防火墙策略很容易写也不容易出现动态端口打不开的尴尬。另一个体验上的巨大差异是会话复用。WMI 的 -ComputerName 参数本质上每次调用都会重新建立一条远程连接而 CIM 命令支持 New-CimSession 创建一个持久会话之后反复查询都是复用同一条通道。这一点在批量巡检场景里尤其重要后面我会给对比。2.3 一张表看懂架构差异对比维度WMICIMPowerShell 视角标准定位对 CIM 标准的经典实现同样是 CIM 标准的实现但采用新一代协议体系远程协议DCOM / RPCWS-ManWinRM可选 HTTPS 加密默认远程端口135 动态协商端口5985 / 5986可通过端口转发或自定义监听器调整核心服务WinmgmtWinRM跨平台能力仅限 WindowsPowerShell Core 可跨 Linux/macOS返回对象ManagementObjectCimInstance发展趋势基本冻结遗留为主新脚本、新功能的推荐方向如果你以前只用过 WMI可以把这页表格当作快速记忆点CIM 解决了 WMI 最让人头疼的网络层问题也把返回对象做得更适合自动化处理。3. 实际操作对比从 Get-WmiObject 到 Get-CimInstance3.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 筛选条件的写法差异筛选进程时老代码常用 -FilterGet-WmiObject -Class Win32_Process -Filter Name LIKE %powershell%新写法也支持 -FilterGet-CimInstance -ClassName Win32_Process -Filter Name LIKE %powershell%稍微复杂一点的例子以前有人习惯用 -Query 直接写 WQLGet-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 限制改用管理员身份检查命名空间权限和 LocalAccountTokenFilterPolicy0x800706BA 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拿自己的机器做一次双边查询你会发现它们的区别其实远没有想象中那么玄。