在 C# 与 PowerShell 中使用 WMI/CIM——硬件信息获取、进程监控与远程查询实务指南

· 更新日期: · · Windows, C#, .NET, PowerShell, WMI, CIM, 业务应用, Windows 开发

更新记录(仅首版,2026年08月01日 发布)
首次发布
引用本文(DOI(已登记存档): 10.5281/zenodo.22175773)

以下 DOI 指向先前登记的存档,内容可能与当前正文不同。引用当前正文时,请使用本页网址。

Go Komura(2026)。《在 C# 与 PowerShell 中使用 WMI/CIM——硬件信息获取、进程监控与远程查询实务指南》。小村软件有限公司。 https://comcomponent.com/zh-CN/blog/wmi-cim-practical-guide/

DOI(已登记存档)
10.5281/zenodo.22175773
DOI(上次登记版本)
10.5281/zenodo.22175774

“想显示电脑的序列号和机型名称”“想查看服务器的磁盘剩余空间”“想检测进程的启动”。在 Windows 的业务应用和管理工具中,能用统一方式处理这类信息的就是 WMI/CIM

CIM 是描述管理信息的标准模型,WMI 是使用该标准的 Windows 管理基础设施。PowerShell 的 CIM cmdlet 和 C# 的 API 就是访问这套基础设施的入口。1

经典需求与 WMI/CIM显示序列号和机型名称、监控磁盘剩余空间、检测进程启动、查询远程电脑这些业务应用的经典需求,经典答案都是 WMI,而管理信息的表示使用 CIM 标准序列号、机型名称WMI(使用 CIM 标准的基础设施)磁盘剩余空间的监控进程启动的检测远程电脑的查询

图1:业务应用最常见的四类需求,经典答案都是 WMI/CIM。

容易犯迷糊的是入口不止一个。搜索结果里旧的 Get-WmiObjectGet-CimInstance 混在一起,C# 一侧也同时有 System.ManagementMicrosoft.Management.Infrastructure。旧的 WMI cmdlet 在 Windows PowerShell 5.1 中还能用,但 PowerShell 7 里没有。2

本文面向要实现硬件信息获取、进程监控、远程电脑查询的 C#/PowerShell 开发者。讲解顺序是:先判断该不该选 WMI,然后用 PowerShell 试,确认连接条件后做进 C#,最后把监控和故障处理也理顺。内容以截至 2026 年 8 月的一手资料为基础,整理了实例与注意事项。

1. 先说结论:按用途和连接目标选择入口

新写的 PowerShell 用 CIM cmdlet,C# 则按用途选 API。不过,用专用机制就能搞定的处理,没必要都硬往 WMI 上靠。

判断、工作 基本方针 讲解章节
决定该不该用 WMI 用于跨领域的信息获取和远程查询;配置修改和高频性能监控选专用机制 第 2 章
决定查询什么 选定命名空间、类、属性,用 WQL 缩小范围。日常的默认值是 root/CIMV23 第 3 章、第 4 章
用 PowerShell 试 Get-CimInstance 读,用 Invoke-CimMethod 操作。面向 5.1 的新代码也写在 CIM 这一侧2 第 4 章
扩展到远程 以 WSMan/WinRM 为基础,对同一台机器的多次操作复用 CIM 会话。必要时选 DCOM34 第 5 章
做进 C# 本地的简易查询用 System.Management,远程和监控的集成考虑 MI API56 第 6 章
监控进程启动 使用事件订阅,并把权限、取消订阅、断开后的重新注册一并设计进去78 第 7 章
排查慢、值不对、找不到类的原因 从查询的数据量与频率、提供程序的位数、存储库的一致性三方面定位 第 8 章

尤其要注意,能枚举列表和能订阅事件是两回事Win32_ProcessStartTrace 的订阅示例要以管理员权限运行。7 另外,不要惯性地使用 SELECT * 和短周期轮询,而要用 -Filter-Property-KeyOnly 只取需要的信息。3

图中实线表示始终成立的关系,虚线表示带条件的关系(成立条件写在详情页各关系的说明中)。关系的完整列表(共 26 条,附依据与确信度)以及主要概念的定义,汇总在知识地图详情页(日文)。数据:JSON-LD / Turtle

2. 什么时候用 WMI/CIM,什么时候选专用机制

WMI 作为“统一的读取入口”很出色,但并不总是最优解。下面是实务中的取舍参考。

想做的事 合适的手段 不选 WMI 的理由
获取硬件信息和操作系统配置、无代理的远程查询 WMI/CIM 这正是 WMI 的看家本领,比逐个调用专用 API 更统一
读写自己应用的配置 直接读注册表(Microsoft.Win32.Registry)、配置文件 经 WMI 操作注册表绕远路,还会连带背上 8.2 节的位数问题
CPU 使用率等高频、连续的性能监控 性能计数器(System.Diagnostics.PerformanceCounter 等) 计数器就是为此设计的机制。WMI 的短周期轮询在负载和精度上都吃亏
单次的操作系统功能调用、需要低延迟的处理 Win32 API(P/Invoke) WMI 要经过 COM 和提供程序,有额外开销
用自身进程权限就够的本地进程枚举与操作 System.Diagnostics.Process 标准库即可完成,减少依赖
防火墙、网络等 Windows 管理功能的配置 Get-NetFirewallRule专用的 CIM 系 cmdlet 比起去找原始的 WMI 类,按用途整理好的 cmdlet 更准确也更安全
检测文件、文件夹的变更 FileSystemWatcher 有专用 API 的领域就别硬塞 WMI

判断标准很简单:“有专用机制的领域用专用机制,跨领域的查询和远程查询用 WMI/CIM”。表中的 Get-NetFirewallRule 等在内部就是构建在 CIM 之上的 cmdlet,可以说是“不直接接触 WMI/CIM,只享受它带来的好处”。

手段选择的判断标准有专用机制的领域用专用机制,没有专用机制的领域里跨领域查询和远程查询用 WMI 和 CIM,这就是判断标准,而专用的 CIM 系 cmdlet 是不直接接触 WMI 和 CIM 只享受好处的形式存在不存在专用的机制存在吗?使用专用机制使用 WMI / CIM跨领域查询、远程查询专用的 CIM 系 cmdlet只享受好处的形式

图2:有专用机制的领域用专用机制,跨领域查询和远程查询用 WMI/CIM。

3. 掌握机制:标准、命名空间、类、WQL

3.1. CIM 是标准,WMI 是 Windows 上的实现

先把术语之间的关系一次理清。

术语 实质
CIM(Common Information Model) 描述系统、应用、网络、设备等管理对象的行业标准模型。由 DMTF(Distributed Management Task Force)制定并维护1
WBEM(Web-Based Enterprise Management) 制定企业环境管理信息访问标准技术的行业倡议1
WMI WBEM 的 Microsoft 实现。使用 CIM 标准描述管理对象,内置于 Windows 中1
MI(Windows Management Infrastructure) WMI 的下一代版本。与传统 WMI 完全兼容,许多新的提供程序都是用 MI 编写的1
CIM 标准与 WMI 实现的关系DMTF 制定并维护的 CIM 标准在 WBEM 倡议的框架下被使用,其 Microsoft 实现就是 WMI,下一代版本 MI 与传统 WMI 完全兼容,而 CIM 系 API 连接的目标是同一套 WMI 基础设施DMTF 制定并维护CIM(行业标准模型)WBEM(行业倡议)WMI(Microsoft 实现)MI(下一代、完全兼容)CIM 系 API(PowerShell / C#)

图3:CIM 是规范,WMI 是 Windows 上的实现。CIM 系 API 连接的是同一套 WMI 基础设施。

3.2. 把命名空间、类、提供程序、WQL 串起来理解

开发者需要掌握的结构有下面四项。

  • 命名空间(namespace):汇集类的层级。日常查询几乎都用 root/CIMV2,它也是 CIM cmdlet 的默认值。3 此外还有 root\default(注册表提供程序等)等。
  • :像 Win32_ComputerSystem(计算机本体)、Win32_LogicalDisk(逻辑驱动器)、Win32_Process(进程)这样的管理对象类型。继承自 CIM 标准类(如 CIM_LogicalDisk)的 Windows 专有类带有 Win32_ 前缀。9
  • 提供程序:提供类实体的组件。收到查询后,提供程序会当场向操作系统取值。
  • WQL:类似 SQL 的查询语言。像 SELECT Name, State FROM Win32_Service WHERE StartMode = 'Auto' 这样,把类当作表来筛选。WQL 也是 CIM cmdlet 的默认查询语言。3

“能用统一的类和查询语言读取操作系统与硬件的信息”——这就是 WMI 的价值所在。不过,并不是所有类都能写入或执行操作。有方法的类用 Invoke-CimMethod 操作,可写属性的修改用 Set-CimInstance。正如 4.1 节的对照表所示,要按类提供的功能来选入口。

WMI 查询的结构WQL 的查询指向命名空间 root/CIMV2 中的 Win32_ 开头的类,提供类实体的提供程序当场向操作系统取值并返回结果用 WQL 查询命名空间 root/CIMV2Win32_* 类提供程序当场向操作系统取值返回结果

图4:查询依次经过命名空间、类、提供程序,值是当场生成的。

4. 用 PowerShell 试:读取、方法调用、资产信息

下面在 Windows 的 PowerShell 上做实验。即使是从旧代码迁移,也请先确认 cmdlet 的对应关系和返回值处理上的差异。

4.1. 新代码用 CIM cmdlet 来写

在 PowerShell 6 及之后的版本(当前的 PowerShell 7)中,下列 WMI v1 cmdlet 已被删除。相同的功能由 CimCmdlets 模块(WMI v2)提供。2

旧版(直到 Windows PowerShell 5.1) 当前(CIM cmdlet) 补充
Get-WmiObject Get-CimInstance -Filter / -Query 的思路相同
Get-WmiObject -List Get-CimClass 查找类、确认类定义
Invoke-WmiMethod Invoke-CimMethod 参数用 -Arguments @{ } 的哈希表传入
Register-WmiEvent Register-CimIndicationEvent 事件订阅(第 7 章)
Set-WmiInstance Set-CimInstance 修改可写属性
Remove-WmiObject Remove-CimInstance 删除实例

Windows PowerShell 5.1 也能用 CIM cmdlet,所以新写的东西即使要在 5.1 上运行,也写在 CIM 一侧,这样才不会留下迁移成本。5.1 与 7 共存及迁移的整体情况,总结在“Windows PowerShell 5.1 与 PowerShell 7 的区别”一文中。

新脚本用 CIM 编写的理由用 WMI cmdlet 写的脚本虽然在 5.1 上能运行但在 PowerShell 6 之后已被删除因此迁移时需要重写,而 CIM cmdlet 在 5.1 上也能用所以新写的东西写在 CIM 一侧就不会留下迁移成本WMI cmdletCIM cmdlet新写的脚本用哪一侧写?在 5.1 上能运行在 5.1 上也能用在 PowerShell 7 中已删除迁移时要重写不留下迁移成本

图5:新代码用 CIM cmdlet 写好,迁移到 PowerShell 7 时就不必重写。

4.2. 用 Get-CimInstance 限定行和列

# 指定类名(默认命名空间 root/CIMV2)
Get-CimInstance -ClassName Win32_OperatingSystem

# -Filter 里只写 WHERE 子句(不写 WHERE 关键字)
Get-CimInstance -ClassName Win32_Service -Filter "StartMode = 'Auto' AND State <> 'Running'"

# 只取需要的属性,减少传输量
Get-CimInstance -ClassName Win32_Process -Property Name, ProcessId, CreationDate

# 想直接写 WQL 就用 -Query
Get-CimInstance -Query "SELECT * FROM Win32_Process WHERE Name LIKE 'p%'"

-Filter 就是 WQL 的 WHERE 子句本身,-Property 是对取回列的限定。3 返回值是 CimInstance 对象,日期属性(如 CreationDateLastBootUpTime)会以转换好的 DateTime 返回。

4.3. 方法用 Invoke-CimMethod 调用

与旧的 Get-WmiObject 不同,不是从取回的对象上直接调用 WMI 方法,因此方法调用要交给 Invoke-CimMethod

CimInstance 的方法调用Get-CimInstance 返回的 CimInstance 一方面日期属性已转换为 DateTime,另一方面不是直接调用 WMI 方法的形式,因此方法调用要把实例传给 Invoke-CimMethod 来完成Get-CimInstanceCimInstance 对象日期已转换为 DateTimeWMI 方法走另一个入口传给 Invoke-CimMethod方法调用

图6:CimInstance 的 WMI 方法不直接调用,方法调用要传给 Invoke-CimMethod 完成。

# 调用实例的方法:取得各进程的所有者
Get-CimInstance -ClassName Win32_Process -Filter "Name = 'notepad.exe'" |
    Invoke-CimMethod -MethodName GetOwner

# 调用类的静态方法:启动进程
Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{ CommandLine = 'notepad.exe' }

# 查看类定义(属性与方法列表)
Get-CimClass -ClassName Win32_Process

4.4. 按要取的信息挑选类

想取的信息 主要属性
厂商、机型名称 Win32_ComputerSystem Manufacturer, Model
主机序列号 Win32_BIOS SerialNumber
操作系统版本、启动时间 Win32_OperatingSystem Caption, Version, LastBootUpTime
磁盘剩余空间 Win32_LogicalDisk DeviceID, FreeSpace, Size, DriveType9
服务状态 Win32_Service Name, State, StartMode
进程列表 Win32_Process Name, ProcessId, CommandLine

4.5. 资产管理示例:机型、序列号、磁盘剩余空间

# 机型信息与序列号(用于与电脑资产台账核对)
$cs   = Get-CimInstance -ClassName Win32_ComputerSystem -Property Manufacturer, Model
$bios = Get-CimInstance -ClassName Win32_BIOS -Property SerialNumber
[pscustomobject]@{
    Manufacturer = $cs.Manufacturer
    Model        = $cs.Model
    Serial       = $bios.SerialNumber
}

# 本地磁盘(DriveType = 3)的剩余空间
Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType = 3" |
    Select-Object DeviceID,
        @{ Name = 'FreeGB'; Expression = { [math]::Round($_.FreeSpace / 1GB, 1) } },
        @{ Name = 'SizeGB'; Expression = { [math]::Round($_.Size / 1GB, 1) } }

DriveType = 3 表示“本地磁盘”,它会排除可移动设备(2)、网络驱动器(4)和 CD(5)。9 在满足第 5 章的连接条件之后,把这段脚本经由 CIM 会话发到各台服务器上跑,就搭起了无代理磁盘监控的底座。

无代理磁盘监控的底座用 DriveType 等于 3 的筛选排除可移动设备、网络驱动器和 CD,只针对本地磁盘,再把同一段脚本经由 CIM 会话发到各台服务器上跑,就搭起了无代理磁盘监控的底座取剩余空间的脚本按 DriveType = 3 筛选排除可移动设备等经由 CIM 会话发到各台服务器无代理监控

图7:把限定到本地磁盘的脚本用 CIM 会话发到各台服务器,是监控的底座。

5. 扩展到远程:连接方式与前提条件

5.1. 本地与远程的连接方式不同

不传 -CimSession 的 CIM cmdlet,若也不指定 -ComputerName,就用 COM 连接本地的 WMI;指定 -ComputerName 时则会用 WSMan(WinRM)协议创建临时会话再连接。对同一台计算机执行多次操作时,创建 CIM 会话并复用在性能上更有利。3

CIM 连接方式的选择不传 CimSession 时,不指定 ComputerName 就用 COM 连接本地 WMI,指定 ComputerName 则每次查询都会创建 WSMan 的临时会话,对同一台机器的多次操作复用 New-CimSession 在性能上更有利,对未配置 WinRM 的机器则有 DCOM 协议选项没有单次多次不传 CimSession 执行指定了 ComputerName 吗?用 COM 连接本地 WMI对同一台机器多次操作?WSMan 的临时会话复用 New-CimSession每次查询都会创建未配置 WinRM 的机器DCOM 协议选项

图8:远程查询默认走 WSMan,对同一台机器的多次操作复用 CIM 会话是惯例做法。

5.2. 准备好 WinRM、防火墙、身份验证与权限

用 WSMan/WinRM 查询时的前提如下。在动手写连接处理之前,先把服务、通信链路、身份验证、权限分开逐项确认。

  • 目标机器上已配置 WinRM。winrm quickconfig 会一次性完成服务的自动启动、HTTP 侦听器(默认端口 5985)的创建、防火墙例外的登记。10 如果想用 HTTPS(默认端口 5986)连接,仅靠它还不够,需要先准备好服务器证书,再用 winrm quickconfig -transport:https 等命令另行配置 HTTPS 侦听器。10
  • 链路上的防火墙已放行相应端口。入站规则的设计与登记的实务做法,在“Windows 防火墙与业务应用”一文中已经讲过。
  • 身份验证。域环境下由 Kerberos 完成相互身份验证。工作组中无法使用 Kerberos,因此有时需要在客户端的 TrustedHosts 中登记。登记范围要压到最小。10
  • 权限。在默认配置下,远程的 WMI 查询与操作基本上要用属于目标机器管理员组的账户来执行。如果要向普通用户开放,需要同时在 WinRM 和 WMI 命名空间两侧配置访问权限。10
远程查询前提条件的确认在目标机器上 winrm quickconfig 会一次性完成服务自动启动、HTTP 侦听器创建和防火墙例外登记,HTTPS 侦听器要准备证书后另行配置,工作组环境下有时需要在 TrustedHosts 中登记winrm quickconfig服务的自动启动创建 HTTP 侦听器(5985)防火墙例外HTTPS 侦听器(5986)准备证书并另行配置工作组环境仅在必要时登记

图9:winrm quickconfig 一次性完成默认配置,HTTPS 侦听器与工作组的身份验证另行处理。

5.3. 对同一台机器的多次操作要复用 CIM 会话

# 单次操作就用 -ComputerName(每次都会创建临时会话)
Get-CimInstance -ClassName Win32_ComputerSystem -ComputerName Server01, Server02

# 要反复查询就复用 CIM 会话
$session = New-CimSession -ComputerName Server01
Get-CimInstance -ClassName Win32_OperatingSystem -CimSession $session
Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType = 3" -CimSession $session
Remove-CimSession $session

5.4. 无法配置 WinRM 的机器可以考虑 DCOM

对于无法配置 WinRM 的老旧机器等用 WSMan 连不上的对象,可以选择 DCOM 协议4

$dcom = New-CimSessionOption -Protocol Dcom
$session = New-CimSession -ComputerName OldServer -SessionOption $dcom

DCOM 还会使用 RPC 的动态端口,因此跨防火墙的设计会变得困难。今后新做的机制,还是把 WSMan 当作默认更稳妥。

上面的会话示例只是演示连接方法。实际集成时,请用 try / finally 等方式保证收尾,让查询中途失败也一定能走到 Remove-CimSession。DCOM 示例中创建的会话同样要释放。

6. 做进 C#:两套 API 以及类型与日期的处理

6.1. 按用途在两套 API 中选择

C# 中使用 WMI 的 API 有两套,两者都只支持 Windows

  System.Management Microsoft.Management.Infrastructure (MI API)
引入方式 .NET Framework 中是标准组件。当前的 .NET 中是 NuGet 包 System.Management5 NuGet 包 Microsoft.Management.Infrastructure6
入口类 ManagementObjectSearcher(传入 WQL 进行查询)5 CimSession(Create → QueryInstances / InvokeMethod / Subscribe)6
类型体系 ManagementObject / ManagementEventWatcher11 CimInstance / CimSession——与 CIM cmdlet 相同的类型3
远程 基于 DCOM 基于 WSMan(CIM 会话)。有异步版本(*Async6
适用场景 本地的信息获取。维持既有代码资产 远程查询与监控的集成。与 PowerShell 配合的设计

6.2. System.Management:传入 WQL,按 CIM 类型读取

把 WQL 以字符串传入,用 Get() 接收结果集合。5

// NuGet: System.Management(仅支持 Windows)
using System.Management;

using var searcher = new ManagementObjectSearcher(
    @"root\cimv2",
    "SELECT DeviceID, FreeSpace, Size FROM Win32_LogicalDisk WHERE DriveType = 3");

foreach (ManagementObject disk in searcher.Get())
{
    var freeGb = (ulong)disk["FreeSpace"] / 1024.0 / 1024.0 / 1024.0;
    var sizeGb = (ulong)disk["Size"] / 1024.0 / 1024.0 / 1024.0;
    Console.WriteLine($"{disk["DeviceID"]} 剩余 {freeGb:F1} GB / 总计 {sizeGb:F1} GB");
}

属性通过索引器以 object 返回,因此要查阅类的文档确认 CIM 类型(本例中 FreeSpace / Size 是 uint649)后再强制转换。不确认类型、想当然按 int 转换就会抛出 InvalidCastException,这是最开始最常见的绊脚石。

属性获取与强制转换的陷阱System.Management 的属性通过索引器以 object 返回,因此必须查阅类的文档确认 CIM 类型后再强制转换,想当然按 int 转换就会抛出 InvalidCastException通过索引器获取以 object 返回查文档确认 CIM 类型转换为正确的类型想当然按 int 转换InvalidCastException

图10:属性以 object 返回,因此要先确认 CIM 类型再强制转换。

6.3. MI API:用与 PowerShell 相同的类型查询

CimSession 能以同样的形式处理本地和远程。枚举、查询、方法调用、事件订阅以及异步版本一应俱全。6

// NuGet: Microsoft.Management.Infrastructure(仅支持 Windows)
using Microsoft.Management.Infrastructure;

// 本地用 CimSession.Create(null),远程则传入计算机名
using CimSession session = CimSession.Create(null);

IEnumerable<CimInstance> disks = session.QueryInstances(
    @"root\cimv2", "WQL",
    "SELECT DeviceID, FreeSpace, Size FROM Win32_LogicalDisk WHERE DriveType = 3");

foreach (CimInstance disk in disks)
{
    var deviceId = (string)disk.CimInstanceProperties["DeviceID"].Value;
    var free = (ulong)disk.CimInstanceProperties["FreeSpace"].Value;
    Console.WriteLine($"{deviceId} 剩余 {free / 1024.0 / 1024 / 1024:F1} GB");
}

处理的 CimInstance 与 PowerShell 的 CIM cmdlet 返回的是同一种类型,所以“先用 PowerShell 试,再抄写到 C#”这条开发路径能顺畅衔接。如果要设计 C# 与 PowerShell 的联动本身,还可以参考“从 C# 执行 PowerShell 并以对象形式接收结果的方法”。

先用 PowerShell 试再抄写到 C# 的流程PowerShell 的 CIM cmdlet 与 C# 的 MI API 处理的是同一种 CimInstance 类型,因此先用 PowerShell 做原型再抄写到 C# 的开发流程能顺畅衔接用 PowerShell 做原型CIM cmdlet用 C# 正式实现MI API同一种 CimInstance 类型抄写能顺畅衔接

图11:CIM cmdlet 与 MI API 处理同一种 CimInstance 类型,因此从原型到正式实现能衔接起来。

6.4. DMTF 日期不要手工拼接,交给转换 API

WMI 的日期是以 CIM 规范中的 DMTF 格式 yyyymmddHHMMSS.mmmmmm±UUU(末尾是相对 UTC 的偏移分钟数。例:20260801100000.000000+540)的字符串存储的。不要用字符串处理去拆分拼接原始值,而要使用转换 API。

  • C#(System.Management)ManagementDateTimeConverter 提供 DMTF 格式与 DateTime / TimeSpan 的相互转换。11
  • CIM 系 API(Get-CimInstance / MI API):日期属性会以转换好的 DateTime 返回,因此根本不会遇到这个问题。(Get-CimInstance Win32_OperatingSystem).LastBootUpTime 可以直接当作 DateTime 参与计算。
DMTF 日期格式的处理WMI 的日期以 DMTF 格式的字符串存储,用 System.Management 读取原始值时要用 ManagementDateTimeConverter 转换,用 CIM 系 API 则会以转换好的 DateTime 返回,因此不要自己拼接字符串System.ManagementCIM 系 APIDMTF 格式的字符串用哪种 API 获取?ManagementDateTimeConverter以转换好的 DateTime 返回转换为 DateTime / TimeSpan自行拼接字符串不要使用

图12:DMTF 字符串的转换交给转换 API,用 CIM 系 API 时直接使用已转换的 DateTime。

这里的代码只是 API 的基本形态。在实际应用中,除了处理获取失败和未设置的值之外,还要按所用 API 实现结果集合、取回对象和会话的释放。

7. 监控进程启动:权限、订阅、取消与重新注册

不要“用轮询定期获取 Win32_Process 再比对差异”,而要改用事件订阅。检测进程启动,最简单的做法是订阅 Win32_ProcessStartTrace(内核跟踪提供程序的事件类,带有 ProcessName / ProcessID / ParentProcessID 等属性8)。

7.1. 先确定执行权限和监控代码的落脚点

Register-CimIndicationEvent 用类名或 WQL 事件查询来注册订阅,每次有事件到达就执行 -Action 的脚本块。7 订阅这个类需要管理员权限7 谁能接收事件由事件类的安全描述符控制,普通用户身份会被拒绝访问。8

Win32_ProcessStartTrace 一类的订阅以管理员权限为前提。7“在开发机上(以管理员运行)能跑通,到客户现场的普通用户环境里监控就不工作”这种坑,和防火墙的提示对话框一样常见。如果要在以普通用户运行的业务应用里嵌入监控,请考虑把监控部分拆到 Windows 服务(LocalSystem 等)中,再通过进程间通信与应用本体连接。

普通用户环境下的监控结构以管理员权限为前提的订阅要从以普通用户运行的应用本体中拆出来,把监控部分分离到以 LocalSystem 等身份运行的 Windows 服务中,再通过进程间通信与应用本体连接进程间通信用于监控的 Windows 服务订阅启动跟踪以 LocalSystem 等身份运行应用本体(普通用户)

图13:需要管理员权限的订阅放到服务一侧,再通过进程间通信与应用本体连接。

7.2. 用 PowerShell 订阅,监控结束时取消

# 要在管理员权限的 PowerShell 中运行
$action = {
    $name = $Event.SourceEventArgs.NewEvent.ProcessName
    $id   = $Event.SourceEventArgs.NewEvent.ProcessID
    Write-Host "进程启动: $name (PID=$id)"
}
Register-CimIndicationEvent -ClassName Win32_ProcessStartTrace `
    -SourceIdentifier ProcessStarted -Action $action

订阅与注册它的 PowerShell 会话绑定,只要正常持续,每次有进程启动就会执行 -Action。请注意,如果把取消订阅的命令也一口气接着执行,那只会在监控开始前就把订阅删掉。

取消要在结束监控时执行。

# 结束监控时:取消订阅
Unregister-Event -SourceIdentifier ProcessStarted
进程启动事件订阅的流程在管理员权限的 PowerShell 会话中用 Register-CimIndicationEvent 注册订阅,每次有进程启动就收到事件并执行 Action,结束监控时用 Unregister-Event 取消WMIPowerShell 会话WMIPowerShell 会话以管理员权限运行用 Register-CimIndicationEvent 注册订阅每次进程启动都收到事件执行 -Action用 Unregister-Event 取消(监控结束时)

图14:订阅与注册它的会话绑定,取消要在结束监控时进行。

7.3. 使用 WITHIN 的通用事件本质上是轮询

另一种方法是监视类的实例创建的通用实例创建事件__InstanceCreationEvent)。它的机制是 WMI 按 WITHIN 指定的间隔轮询并把差异变成事件,因此检测间隔与负载之间的取舍要由你自己决定。

检测进程启动的两种订阅方式Win32_ProcessStartTrace 是订阅内核跟踪提供程序事件类的方式,而通用的 __InstanceCreationEvent 是 WMI 按 WITHIN 指定的间隔轮询并把差异变成事件因此检测间隔与负载的取舍要自己决定进程启动的检测Win32_ProcessStartTrace__InstanceCreationEvent订阅内核跟踪按 WITHIN 间隔轮询间隔与负载的取舍

图15:是订阅专用的事件类,还是使用带轮询间隔的通用实例创建事件。

# 以 5 秒间隔轮询,监视 Win32_Process 的新实例
$query = "SELECT * FROM __InstanceCreationEvent WITHIN 5 WHERE TargetInstance ISA 'Win32_Process'"
Register-CimIndicationEvent -Query $query -SourceIdentifier ProcPoll -Action {
    Write-Host "启动: $($Event.SourceEventArgs.NewEvent.TargetInstance.Name)"
}

7.4. C# 中用 ManagementEventWatcher 接收

在 C#(System.Management)中,承担同样角色的是 ManagementEventWatcher11

using System.Management;

// 在以管理员身份运行的进程中
var watcher = new ManagementEventWatcher(
    new WqlEventQuery("SELECT * FROM Win32_ProcessStartTrace"));
watcher.EventArrived += (_, e) =>
{
    var name = (string)e.NewEvent["ProcessName"];
    var pid  = (uint)e.NewEvent["ProcessID"];
    Console.WriteLine($"进程启动: {name} (PID={pid})");
};
watcher.Start();
// 结束监控时别忘了 watcher.Stop() 和 Dispose

7.5. 常驻监控要把订阅断掉之后的重新注册也设计进去

如果要做进常驻监控,请把订阅断掉时(服务重启时、出错时)的重新注册也纳入设计。包含设备监控在内的“状态确认与显示”的设计思路,在“外部设备状态确认与显示的最佳实践”中有讲解。

常驻监控的订阅生命周期常驻监控中订阅中的状态可能因服务重启或出错而断掉,因此要把检测到断开后重新注册并回到订阅中的设计也包含进去服务重启、出错订阅中订阅已断开的状态重新注册

图16:常驻监控要把订阅断掉后重新注册、回到订阅中的设计也包含进去。

8. 排查故障:性能、位数、存储库

连不上的情况回到 5.2 节,只有订阅被拒绝的情况回到 7.1 节,强制转换和日期处理回到 6.2、6.4 节确认。这里区分其余常见的“慢”“值不对”“找不到类”。

8.1. 慢:重新审视获取的数据量、频率和会话

WMI 的查询是“提供程序当场生成值”的处理,并不是免费的。经典的反模式有两个。

  • 惯性使用 SELECT *如果把 Win32_Process 的全部属性、全部条目都取回来,提供程序的工作量和网络传输量(远程时)就会随之膨胀。用 -Filter 限定行、用 -Property 限定列,如果只需要键以便后续操作,就用 -KeyOnly。这些都是官方为“减小对象体积和网络流量”而准备的手段。3
  • 短周期轮询。“每秒执行一次 Get-CimInstance Win32_Process”这类设计,要换成第 7 章的事件订阅。即使实在要用轮询型(WITHIN),也要把间隔放宽到刚好满足需求为止。

另外,对远程逐台重复指定 -ComputerName 也是很浪费的做法。因为每次查询都会创建临时会话,所以多次操作要改成复用 CIM 会话3

性能反模式与替代做法惯性使用 SELECT 星号要改成用 Filter 和 Property 限定行与列只要键时用 KeyOnly,短周期轮询改成事件订阅,逐台指定 ComputerName 的重复调用改成复用 CIM 会话惯性使用 SELECT *用 Filter 和 Property 限定只要键时用 KeyOnly短周期轮询改成事件订阅逐台指定 ComputerName复用 CIM 会话

图17:限定行、列、键,选择合适的监控方式,复用会话,就能压住不必要的负载。

8.2. 值不对:确认 32 位/64 位的提供程序

在 64 位 Windows 上,有些提供程序同时存在 32 位版和 64 位版,默认由与调用方应用位数一致的一侧响应。12 典型例子是 root\default 的注册表提供程序(StdRegProv):从 32 位应用读取时返回的是 Wow6432Node 一侧(32 位视图)的值。12 当出现“用 WMI 读到的注册表值和 regedit 里看到的不一样”时,请先怀疑这一点。

如果需要另一侧的视图,可以在连接时的上下文中指定 __ProviderArchitecture(若要强制,再加上 __RequiredArchitecture)来显式请求。12 位数问题的全貌,在“从 C# 安全调用 Win32 API——P/Invoke 实务指南”中也有讲解。

64 位环境下的提供程序选择默认由与调用方应用位数一致的提供程序响应,32 位应用的注册表查询拿到的是 Wow6432Node 一侧的值,但可以通过指定 __ProviderArchitecture 显式请求另一侧的视图32 位64 位调用方是多少位?由 32 位提供程序响应由 64 位提供程序响应注册表是 Wow6432Node 一侧的值指定 __ProviderArchitecture显式请求另一侧的视图

图18:默认由与调用方位数一致的一侧响应,因此 32 位应用读到的是 Wow6432Node 一侧。

8.3. 找不到类:存储库要先确认再修复

WMI 的类定义保存在存储库中(它不是单个文件,而是 Repository 文件夹内的一组文件作为数据库运作13)。一旦它出现不一致,即便应用侧什么都没改,也会开始出现“本该存在的类找不到”“命名空间无效”之类的错误。

定位和修复要使用 winmgmt.exe13

rem 一致性检查(结果为 inconsistent 就说明有不一致)
winmgmt /verifyrepository

rem 一致性检查 + 若有不一致则重建(能读出的内容会被合并)
winmgmt /salvagerepository

要注意的是,不要把删除或初始化存储库当成第一手。经由 WMI 报出的错误也可能源自操作系统的其他部分,Microsoft 明确指出,把删除存储库作为最先采取的处理“可能导致系统和已安装应用受损”。13 请遵守先用 /verifyrepository 确认、再用 /salvagerepository 修复的顺序。

WMI 存储库不一致的排查步骤出现找不到类等错误时先用 winmgmt 的 verifyrepository 确认一致性,若不一致就用 salvagerepository 重建,不要把删除或初始化存储库当成第一手找不到类等错误winmgmt /verifyrepository结果是 inconsistent 吗?winmgmt /salvagerepository怀疑操作系统其他部分的原因能读出的内容会被合并删除、初始化存储库不要当成第一手

图19:遵守先 verify 确认再 salvage 修复的顺序,不要把删除当成第一手。

9. 小结:把查询、监控与运维串起来

WMI/CIM 是跨领域查询 Windows 管理信息的统一入口。它不是把所有处理都收拢进来的工具,专用 API 能搞定的事就交给专用 API。

集成阶段 要确认的事
选工具 配置修改、高频性能监控、单次的操作系统操作优先用专用机制,信息获取和远程查询用 WMI/CIM
用 PowerShell 试 面向 5.1 也用 CIM cmdlet 写。选好 root/CIMV2 的类,限定行、列、键
扩展到远程 备齐 WSMan/WinRM 的连接条件,多次操作复用会话。对必要的机器考虑 DCOM 选项
迁到 C# 在适合本地查询的 System.Management 与适合远程、监控的 MI API 之间选择,并确认类型与日期的处理
做成常驻监控 确认 Win32_ProcessStartTrace 的权限,把订阅、取消、断开后的重新注册连成一套设计
排查故障 从短周期轮询、提供程序位数、存储库一致性三方面定位。不要把删除当成最先的处理

把 CIM 这个标准与 WMI 这个实现、查询与事件订阅、连接与访问权限分开来考虑,选哪个工具、该确认哪里就清楚了。请先用 PowerShell 取到需要的信息,再把结果接到 C# 的实现与运维设计上。

相关文章

相关咨询领域

小村软件有限公司承接的工作包括:把使用 WMI/CIM 的硬件信息获取、进程监控、远程电脑查询集成到业务应用中,把基于 Get-WmiObject 的公司内部脚本迁移到 CIM cmdlet,以及排查“开发机上能跑、到客户现场就报权限错误”这类问题的原因。从 PowerShell 上的原型到 C# 的正式实现,都可以一并咨询。

参考链接

  1. Microsoft Learn, About WMI. 关于 WMI 是 WBEM(开发企业环境管理信息访问标准技术的行业倡议)的 Microsoft 实现,管理对象的表示使用 CIM(Common Information Model)行业标准,而 CIM 由 DMTF(Distributed Management Task Force)开发并维护,下一代版本 MI(Windows Management Infrastructure)与传统 WMI 完全兼容,以及远程 WMI 连接通过 DCOM 进行、并有基于 WS-Management 的 WinRM 作为替代方案。  2 3 4 5

  2. Microsoft Learn, Differences between Windows PowerShell 5.1 and PowerShell 7.x. 关于 WMI v1 cmdlet(Register-WmiEvent / Set-WmiInstance / Invoke-WmiMethod / Get-WmiObject / Remove-WmiObject)已从 PowerShell 中删除,以及 CimCmdlets 模块(WMI v2)的 cmdlet 提供相同功能并具备新特性和重新设计的语法。  2 3

  3. Microsoft Learn, Get-CimInstance (CimCmdlets). 关于既不指定 ComputerName 也不指定 CimSession 时用 COM 会话连接本地 WMI、指定 -ComputerName 时创建 WsMan 协议的临时会话,对同一台计算机的多次操作从性能上推荐用 CIM 会话连接,-Filter 是不含 WHERE 关键字的 WQL/CQL where 子句,用 -Property 和 -KeyOnly 可以减小对象体积和网络流量,默认命名空间是 root/CIMV2 且默认查询语言(-QueryDialect)是 WQL,输出是 Microsoft.Management.Infrastructure.CimInstance,与 Invoke-CimMethod 组合调用 GetOwner 的示例,以及该 cmdlet 仅支持 Windows。  2 3 4 5 6 7 8 9 10

  4. Microsoft Learn, New-CimSessionOption (CimCmdlets). 关于 CIM 会话选项分为 WsMan 用和 DCOM 用两个参数集,-Protocol 可指定 Dcom / Default / Wsman,把 New-CimSessionOption -Protocol Dcom 创建的选项传给 New-CimSession 的 -SessionOption 来创建 DCOM 的 CIM 会话的示例,以及 DCOM 会话的默认模拟级别是 Impersonate。  2

  5. Microsoft Learn, ManagementObjectSearcher Class (System.Management). 关于它根据指定的 WQL 查询获取管理对象集合,是获取管理信息最常用的入口类,接收 ObjectQuery 和 ManagementScope(WMI 命名空间)并通过 Get() 返回 ManagementObjectCollection,以及 System.Management.dll 以 NuGet 包 System.Management 的形式提供。  2 3 4

  6. Microsoft Learn, CimSession Class (Microsoft.Management.Infrastructure). 关于 Microsoft.Management.Infrastructure.dll 以 NuGet 包 Microsoft.Management.Infrastructure 的形式提供,用 Create(computerName) 创建会话,用 QueryInstances(namespace, queryDialect, query) 执行查询,具备 EnumerateInstances / GetInstance / InvokeMethod / Subscribe 及各自的异步版本(*Async)并实现了 IDisposable。  2 3 4 5

  7. Microsoft Learn, Register-CimIndicationEvent (CimCmdlets). 关于用类名或查询表达式订阅指示(事件)并用 -SourceIdentifier 给订阅命名,Win32_ProcessStartTrace 的订阅示例及其执行需要以管理员身份运行 PowerShell 的注记,在 -Action 脚本块中通过 $Event.SourceEventArgs.NewEvent 引用 ProcessName / ProcessId 的示例,指定 -ComputerName 时使用 WsMan 的临时会话、未指定时用 COM 连接本地,以及取消订阅使用 Unregister-Event。  2 3 4 5

  8. Microsoft Learn, Win32_ProcessStartTrace class. 关于它是表示新进程启动的事件类,具有 ProcessName / ProcessID / ParentProcessID / SessionID / Sid 等属性,SECURITY_DESCRIPTOR 属性是事件提供程序用来决定哪些用户可以接收事件的描述符,以及其命名空间是 Root\CIMV2 并由内核跟踪提供程序(Krnlprov.dll)提供。  2 3

  9. Microsoft Learn, Win32_LogicalDisk class. 关于 Win32_LogicalDisk 是派生自 CIM_LogicalDisk、表示本地存储设备的类,DriveType 的取值(2=可移动设备、3=本地磁盘、4=网络驱动器、5=CD 等),FreeSpace / Size 是 uint64 的字节值,DeviceID 是键,以及用 DriveType = 3 筛选的 VBScript / C# 查询示例。  2 3 4

  10. Microsoft Learn, Installation and configuration for Windows Remote Management. 关于默认情况下未配置 WinRM 侦听器因而无法收发 WS-Management 消息,winrm quickconfig 会把服务设为自动启动、配置 HTTP/HTTPS 侦听器并登记防火墙例外,WinRM 2.0 的默认端口是 HTTP 5985 / HTTPS 5986,在工作组等无法建立相互身份验证(Kerberos)的场合要把 TrustedHosts 尽可能限定后再设置,以及控制侦听器远程访问的默认安全描述符(RootSDDL)和允许非管理员用户使用 WMI 插件时的额外配置。  2 3 4

  11. Microsoft Learn, System.Management Namespace. 关于该命名空间用 ManagementObjectSearcher 系列类向 WMI 基础设施发起查询、用 ManagementEventWatcher 进行事件订阅,WqlEventQuery 表示 WQL 格式的事件查询,以及 ManagementDateTimeConverter 提供 DMTF 日期时间、时间间隔与 CLR 的 DateTime / TimeSpan 相互转换的方法。  2 3

  12. Microsoft Learn, Requesting WMI Data on a 64-bit Platform. 关于提供程序同时存在 32 位版和 64 位版时,默认由 32 位提供程序响应 32 位应用(含脚本)、由 64 位提供程序响应 64 位应用,可以用上下文的 __ProviderArchitecture(32 或 64)和 __RequiredArchitecture 请求并强制使用非默认一侧的提供程序(强制时若没有对应版本会返回 WBEM_E_PROVIDER_LOAD_FAILURE),以及注册表提供程序的例子中 32 位客户端收到的是 HKLM\SOFTWARE\Wow6432Node 一侧的数据。  2 3

  13. Microsoft Learn, winmgmt. 关于 winmgmt.exe 的 /verifyrepository 对 WMI 存储库进行一致性检查,/salvagerepository 在一致性检查后于检出不一致时重建存储库并合并能读出的内容,/resetrepository 恢复到操作系统初次安装时的状态,存储库由 Repository 文件夹内的一组文件作为数据库运作,以及经由 WMI 报出的错误有时源自操作系统的其他部分,因而不应把删除存储库作为最先采取的处理,那可能导致系统和已安装应用受损。  2 3

共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。

与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。

本文与以下服务页面相关联,欢迎从最接近的入口查看。

常见问题

汇总了咨询这一主题时常见的问题。

WMI 和 CIM 有什么区别?
CIM 是由 DMTF(Distributed Management Task Force)制定并维护的“用于描述系统、设备等管理对象的行业标准模型”。WMI 则是使用该标准的 WBEM 倡议的 Microsoft 实现,内置于 Windows 中。也就是说,CIM 是规范,WMI 是 Windows 上的实现。PowerShell 的 Get-CimInstance 和 C# 的 Microsoft.Management.Infrastructure 之所以名为“CIM”,是因为它们是遵循该标准的 API,而连接的目标仍是同一套 WMI 基础设施。日常开发中只要理解成“用 CIM 系 API 查询 WMI 的类(Win32_* 等)”,实务上就够用了。
Get-WmiObject 已经不能用了吗?
在 Windows PowerShell 5.1 中它现在仍然可用,但在 PowerShell 6 及之后的版本(当前的 PowerShell 7)中,Get-WmiObject、Invoke-WmiMethod、Register-WmiEvent、Set-WmiInstance、Remove-WmiObject 这些 WMI v1 cmdlet 已被删除,无法执行。相同的功能由 CimCmdlets 模块(Get-CimInstance / Invoke-CimMethod / Register-CimIndicationEvent 等)提供。新写的脚本即使只在 5.1 上运行,也应该用 CIM cmdlet 来写,这样更稳妥。这样一来,迁移到 PowerShell 7 时就不必重写 WMI 相关部分。
在 C# 中使用 WMI,应该选 System.Management 还是 Microsoft.Management.Infrastructure?
两者都只支持 Windows,在当前的 .NET 中都以 NuGet 包的形式引入。System.Management 是只要把 WQL 传给 ManagementObjectSearcher 就能使用的经典 API,如果主要做本地信息获取,用它已经足够。用于转换 DMTF 日期的 ManagementDateTimeConverter 也包含在其中。而 Microsoft.Management.Infrastructure(MI API)拥有与 PowerShell 的 CIM cmdlet 相同的类型体系(CimSession / CimInstance),可以统一处理经由 WSMan 的远程查询、异步版本的方法以及事件订阅(Subscribe)。如果要正式地把远程电脑的查询和监控做进产品,选 MI API 更合理。
Get-CimInstance 连不上远程电脑,应该检查什么?
首先请确认目标机器上是否配置了 WinRM。指定 -ComputerName 的 CIM 操作会用 WSMan(WinRM)协议创建临时会话,前提是目标机器上的 WinRM 服务和侦听器正在运行。用 winrm quickconfig 可以一次完成默认配置(服务启动、创建侦听器、登记防火墙例外)。默认端口 HTTP 是 5985、HTTPS 是 5986,因此还要检查链路上的防火墙。工作组环境下无法使用 Kerberos 的相互身份验证,因此有时需要把对方登记到客户端的 TrustedHosts 中。对于实在无法配置 WinRM 的机器,还可以用 New-CimSessionOption -Protocol Dcom 创建的选项,通过 DCOM 连接。
WMI 的日期为什么会以“20260801100000.000000+540”这样的格式返回?
因为 WMI 的日期是按 DMTF 在 CIM 规范中规定的字符串格式(yyyymmddHHMMSS.mmmmmm±UUU,末尾是相对 UTC 的偏移分钟数)存储的。用旧版 Get-WmiObject 或 System.Management 读取原始值时,返回的就是这个字符串。在 C#(System.Management)中,ManagementDateTimeConverter 提供了 DMTF 格式与 DateTime / TimeSpan 相互转换的方法,请使用它,而不要自己拼接字符串。另外,如果是用 Get-CimInstance 等 CIM 系 API 获取的,日期属性会以转换好的 DateTime 返回,因此根本不会遇到这个问题。

作者简介

本文作者的个人简介页面。

Go Komura

小村软件有限公司 代表

以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。

返回博客列表