与 Windows WMI 事件和 PowerShell 一起工作的指南

你知道你可以监视 Windows 中的几乎每个操作吗?不,你不需要购买一些花哨的软件。基础设施监视诸如服务启动和停止、有人创建文件或文件夹等事件,这些都是通过 Windows 管理基础架构 (WMI) 事件实现的。

WMI 事件不是 PowerShell 特定的功能,但是利用 WMI 事件并创建一些方便的工具是最简单的方法之一,而 PowerShell 则是其中之一。在这个分步教程中,你将学会如何利用 PowerShell 的 WMI 事件,并掌握构建一些方便的监视工具的技能!

让我们开始吧!

先决条件

在这个实践教程中,你将看到许多演示。如果你想跟着演示操作,请确保你具备以下条件:

  • Windows 7+ 或 Windows Server 2012+ – 本教程将使用 Windows Server 2019。
  • 以本地管理员组中的用户身份登录。
  • Windows PowerShell 5.1 或 PowerShell 6+ – 本教程将使用 PowerShell v7.1.2。

了解 WMI 和 CIM

在深入了解 WMI 事件之前,首先重要的是要理解它们所构建的基础架构。虽然本教程不会深入讲解 WMI,但你可以随时参考 微软的 WMI 文档 来了解更多。

WMI及其相关的数据模型《通用信息模型(CIM)》是内置于Windows中的模型,可将几乎所有与Windows内部运行机制及其运行内容相关的信息存储在存储库中。

WMI和CIM是管理员用来管理Windows本地和远程操作的强大工具。使用WMI或CIM,管理员可以查询Windows系统上的信息,如已安装的应用程序、服务状态、文件系统中的文件以及几乎所有其他内容。

许多企业监控解决方案使用WMI和CIM收集操作系统和应用程序的健康信息。但是,您不需要购买昂贵的监控工具来利用WMI;您可以使用PowerShell!

让我们从两个基本元素开始,随着学习的进行,您将了解其他所需元素:

  • 类: 类是应用程序(如PowerShell)可调用的事件和属性,用于读取和更新数据。类位于命名空间内。
  • 命名空间:命名空间是与WMI相关的类的容器。可以将其视为包含与图片相关内容的“我的图片”文件夹。有多个命名空间,其中最常见的是CIMv2,其中包含大多数操作系统类。但是,所有命名空间都位于大型单一命名空间Root下。
WMI Architecture

WMI与CIM

WMI和CIM都是与在Windows系统上包含大量信息的存储库进行交互以及处理WMI事件的方法(稍后详细介绍)。但是,这两种方法有一些区别,主要体现在管理员远程与它们进行交互的方式。

WMI始于Windows NT4,是与存储库进行交互的最初(也是唯一)方式。当您使用WMI管理Windows系统时,Windows使用分布式组件对象模型(DCOM)。DCOM是WMI用于在Windows机器上公开数据存储库中信息的远程协议。

为了通过网络工作,DCOM使用远程过程调用(RPC)。为了在网络上通信,RPC使用动态端口范围,这对防火墙和网络地址转换(NAT)设备有时是一个挑战。

如果您在RPC方面遇到问题,请查看文章使用动态端口测试RPC连接

Microsoft决定利用CIM提供更现代化的与Windows中数据存储库交互的方法。与RPC不同,CIM使用WS-MAN(Web服务管理),这是一种更适合远程管理的HTTP协议。

在本文和其他文章中,WMI和CIM可以互换使用。这两种管理方法交互的数据存储库通常称为WMI存储库。几乎所有术语都是指WMI,而CIM通常在PowerShell cmdlet中使用。

WMI vs. CIM和PowerShell

幸运的是,在处理WMI和CIM与PowerShell的问题时,您有一些选择。PowerShell支持与数据存储库的交互的两种方式。当您在PowerShell中运行Get-Command命令时,您可能会注意到各种Wmi cmdlet,如Get-WmiObjectInvoke-WmiMethodRemove-WmiObjectRegister-WmiEventSet-WmiInstance

如果您运行的是Windows PowerShell 3或更高版本(您最好是这样!),您还会看到一些名称类似的cmdlet,如Get-CimInstanceGet-CimClassRemove-CimInstance

您应该使用哪些PowerShell cmdlet?答案很简单;CIM cmdlet。CIM是微软专注的新标准。WMI cmdlet甚至在PowerShell Core中都不可用!

查询WMI:基础知识

在您开始使用WMI事件之前,您必须了解如何使用PowerShell查询WMI。从WMI存储库中查询信息是WMI数据的最常见用法。

在PowerShell世界中查询WMI数据,Get-CimInstance cmdlet是您的好朋友。这个cmdlet有几种不同的方法来查询WMI数据。但是,本教程将专注于Query参数。Query参数允许您提供一个Windows查询语言(WQL)查询以查询WMI。

例如,也许您想要找到Win32_Service类中的所有WMI实例。类似于SQL,您将使用查询Select * from Win32_Service,如下所示。星号(*)告诉WMI返回找到的每个实例的所有属性。

Get-CimInstance -Query 'Select * from Win32_Service'
Getting Windows Services using WMI Query

在上面的例子中,您找到了所有Win32_Service类中的服务实例,但是如果您只想找到一些怎么办?在这种情况下,您将使用WHERE子句。WHERE子句创建一个过滤器,只返回符合特定条件的实例。

WHERE子句告诉Get-CimInstance仅返回属性与特定值匹配的实例。例如,也许您只想查找State属性为Running的服务实例。如果是这样,您会定义WHERE子句Where State='Running',如下所示。

Get-CimInstance -Query "Select * from Win32_Service Where State='Running'"

您可以在下面看到,Get-CimInstance仅返回了State属性等于Running的服务实例。

Returning only service that are in Running state

WMI事件:WMI的操作

WMI包含有关Windows中数千个项目的大量信息的存储库。您可以通过像上面那样查询它来获取该信息,但它还具有另一个较少知名的功能;WMI事件。

在Windows中,任何时候都可能发生数百个事件。当您使用Windows的各种功能,如创建文件、停止和启动服务、安装软件或其他任何操作时,可能会触发WMI事件。

几乎在Windows上执行的每个操作都可以通过WMI事件暴露出来。当在Windows上执行操作时,Windows通过其内部基础架构触发事件。默认情况下,您看不到这些事件;它们是在后台发生的。要查看这些事件,您必须订阅它们。

使用PowerShell构建服务监控脚本

为了演示WMI事件的工作原理,而不是用大量信息来使您厌烦,让我们来构建一个有用的工具。由于WMI事件在Windows中发生事件时会触发,您可以使用它们创建一些方便的监控工具。

也许你想在关键服务器上的Windows服务状态发生变化时,将消息写入日志文件。然后,你可以订阅WMI事件,这些操作在发生时触发事件。当你订阅事件并触发事件时,你可以执行一些操作,比如记录到文件、发送电子邮件,或者使用PowerShell执行的任何其他操作。

与购买一些昂贵的监控解决方案不同,一个简单的PowerShell脚本可以成为一个非常好的贫人监控工具!如果你准备好了,打开你的PowerShell控制台,让我们开始吧!

查找CIM类

在WMI中,与静态实例一样,事件包含在中。这些类包含你上面查询的所有静态数据以及对这些实例进行更改的触发器。你可以在Microsoft文档中找到所有CIM类的列表

要找到所有CIM类,请运行Get-CimClass cmdlet,不带任何参数。Get-CimClass cmdlet默认返回ROOT/cimv2命名空间中的所有类。ROOT/cimv2命名空间是存储几乎所有有趣的Windows类的“主”命名空间。

Get-CimClass

但你可以看到下面返回了很多的类。

Finding the CIM Class

也许你已经进行了一些调查,最终意识到Windows服务都存储在Win32_Service中。因此,当你知道类名时,使用ClassName参数来指定名称,如下所示。

Get-CimClass -ClassName Win32_Service
Get CimClass Parameter

查找CIM类属性

一旦你知道要查找的类,接下来你必须弄清楚要查看的属性。当实例属性的值发生变化(或整个实例被创建或删除)时,会触发一个事件。你必须捕获那个状态变化。为此,你必须知道你想要监视的是哪个属性。

要找到该属性,检查前一节中查询的CIM类实例上的CimClassPropertiesPowerShell对象属性。

(Get-CimClass -ClassName win32_Service).CimClassProperties

请注意下面其中一个属性是State属性。

Some of the Win32_Service Properties

现在你知道了你想要监视的CIM类和属性,是时候订阅WMI事件了!

构建WMI事件订阅:高层次概述

如果你以前从未创建过WMI事件订阅,构建WMI事件订阅可能是一项令人困惑的任务。为了帮助你保持行动的清晰,让我们先了解整体情况,然后再详细介绍基本步骤。

创建WMI事件订阅大致需要四个步骤:

  1. 制作 WQL 查询 – 就像查询静态数据时一样,您必须创建一个与您想查看的 WMI 事件类型相匹配的 WQL 查询。但与查询数据存储不同,您必须在查询中使用一些更复杂的组件,如系统类和检查周期(稍后详细介绍)。
  2. 创建事件过滤器 – 创建了 WQL 查询后,必须创建事件过滤器。事件过滤器在 CIM 中注册 WQL 查询。
  3. 创建使用者 – 使用者定义当事件过滤器查询返回类中的更改时要执行的操作。例如,每当服务状态启动、停止、创建或删除时,使用者会触发一个动作。
  4. 将事件过滤器绑定到使用者 – 将 Windows WMI 查询与使用者关联的粘合剂。绑定是在事件过滤器收到匹配时通知使用者的方法。

将这些项目组合在一起时,您就创建了一个订阅

Basic example of a WMI event subscription

制作 WQL 查询

A WQL query for a WMI event looks a bit different than performing a simple query with Get-CimInstance. Below you’ll find a typical WMI event query.

Select * from <system class> within <checking cycle> where TargetInstance ISA '<class name>'

由于一开始 WQL 查询可能看起来很复杂,让我们拆分它并了解每个组件的工作原理。

系统类

Get-CimInstance的示例中,您已经发现希望在Win32_Service类的实例发生更改时收到通知。您仍然需要此类,但不是使用这样的 WQL 查询:

Select * from Win32_Service

代替,查询将从以下开始。您要查询的主类不是包含您希望收到通知的实例的类。相反,该类是一个系统类

Select * from <system class>

系统类是表示事件引发的类型的内部类。WMI事件有四种类型的系统类:

  • InstanceModificationEvent 检查类中实例的任何属性值更改。这是您将使用的类,因为您希望监视Status属性值在Win32_Service类的实例(服务)上的更改。
  • InstanceCreationEvent – 检查任何新实例。例如,如果您想监视创建的任何新服务,您将使用此系统类。
  • InstanceDeletionEvent – 检查任何已删除的实例。例如,如果您想监视已删除的服务,您将使用此系统类。
  • InstanceOperationEvent – 此系统类检查所有事件类型,包括修改、创建和删除。

对于我们的监视脚本,WQL查询的开头将如下所示:

Select * from __InstanceModificationEvent

WMI系统类名总是以两个下划线(__)开头,后面是类的名称。

检查周期

接下来是检查周期。检查周期包括关键字within和表示以秒为单位的轮询间隔的值。

within <checking cycle>

WMI事件不是实时的,因此必须为订阅定义一个特定的间隔,以检查变化。例如,如果将检查周期设置为10,订阅将每10秒检查一次是否有变化。如果发现变化,它将触发消费者。

如果在轮询间隔内更改、创建或删除实例,将无法检测到变化!考虑您所需的频率,但确保它对CPU和内存友好!

对于教程中的服务监视示例,让我们将检查周期设置为10,以每10秒轮询一次WMI以查看Windows服务的变化。WQL查询正在增长!

Select * from __InstanceModificationEvent within 10

过滤器

最后,为了完成WQL查询,您必须定义一个过滤器以限制从系统类返回的实例。您必须以以下形式定义该过滤器。在这种情况下,您要监视的CIM类被称为TargetInstance

where Targetinstance ISA '<class name>'

ISA是一个将查询应用于指定类的子类的运算符。

由于教程正在构建一个用于监视Windows服务的订阅,您可以创建如下的过滤器:

where Targetinstance ISA 'Win32_Service'

目前,过滤器会查找所有的 Win32_Service 实例。如果您只想监视单个属性,例如特定服务,您可以使用 AND 运算符。

where Targetinstance ISA 'win32_Service' AND Targetinstance.name='bits'

ANDOR 运算符可添加其他条件,以获得更准确的结果。

教程过滤器(以及整个查询)现已完成,如下所示的代码片段。

Select * from __InstanceModificationEvent within 10 where Targetinstance ISA 'win32_Service' AND Targetinstance.name='bits'

创建事件过滤器

现在,您已经拥有查询过滤器,其余流程就容易理解多了!您现在必须创建事件过滤器来使用该查询。事件过滤器实际上是另一个 CIM 实例,它是 Root/subscription 命名空间内 __EventFilter 类的一部分。

以下是包含所需一切的代码片段。下面的脚本将WQL查询分配给$FilterQuery变量。然后,它创建一个散列表,其中包含事件过滤器所需的每个属性和值。然后运行New-CimInstance cmdlet来创建事件过滤器。最后,生成的CIM实例对象存储在变量$CIMFilterInstance中,以备将来使用。

$FilterQuery="Select * from __InstanceModificationEvent within 10 where TargetInstance ISA 'Win32_Service'"
$CIMEventFilterProperties = @{
	## 事件过滤器的名称。这可以是任何相关的内容。
	Name="MyServiceFilter"
	## 目标类的命名空间,例如,目标类为
	## **Win32_Service** 为 Root/CIMv2
	EventNameSpace="Root/CIMV2"
	## 查询语言,通常为 **WQL**。
	QueryLanguage="WQL"
	## 要使用的查询。
	Query=$FilterQuery
}

$CIMFilterInstance=New-CimInstance -ClassName __EventFilter -Namespace "Root/SubScription" -Property $CIMEventFilterProperties

现在,运行Get-CimInstance来验证已创建__EventFilter的新CIM实例。

Get-CimInstance -Namespace root/subscription -ClassName __EventFilter
Event Filter Created Successfully and registered the Windows WMI Query

创建消费者

接下来,是创建消费者或在Windows触发WMI事件时将发生的操作。在创建消费者时,根据您想要触发的操作类型,您有几个选项。

确保可执行文件的ACL正确定义,以防止有人替换EXE为恶意二进制文件。

在本教程中,让我们使用LogFileEventConsumer消费者类型,当与WQL查询匹配的服务发生变化时,将其写入日志文件。

$CIMCOnsumerProperties = @{
	## 脚本在**根/订阅**命名空间中注册的名称
	Name="MyServiceConsumer"
	## 当事件触发时日志写入的文件路径和名称。
	FileName="C:\\MyCIMMonitoring.txt"
	## 要写入日志中的文本。您可以使用
	## %TargetInstance.WMIProperty% 添加变量。在此示例中,使用了**标题**和**状态
	##**。
	Text = "The Service %TargetInstance.Caption% has been Changed: %TargetInstance.State%"
}
$CIMEventConsumer=New-CimInstance -ClassName LogFileEventConsumer -Namespace 'ROOT/subscription' -Property $CIMCOnsumerProperties

## 其他消费者示例
######################
## NTEventLogEventConsumer
######################
## $Template = @(
##	'Service %TargetInstance.Caption% 已更改:%TargetInstance.State%'
##)
##$CIMCOnsumerProperties=@{
## ## 消费者的名称
##	Name="MyEventLogConsumer"
## ## 要使用的事件 ID
##	EventID =[UInt32] 7040
##  EventType 可包含以下值之一
##    ## - **0**: 成功事件
##    ## - **1**: 错误事件
##    ## - **2**: 警告事件
##    ## - **4**: 信息事件
##    ## - **8**: 成功审核事件
##    ## - **16**: 失败审核事件
##  EventType=[UInt32] 1 #信息
## ## 事件源的名称。
##  SourceName="服务控制管理器"
##  Category=[UInt16] 0
##  ## **InsertionStringTemplates** 的行数。
##  NumberOfInsertionStrings =[UInt32] $Template.Length
##  ## 显示在 Windows EventLog 记录中的消息文本。
##  InsertionStringTemplates = $Template
##}
## $CIMEventConsumer=New-CimInstance -ClassName NTEventLogEventConsumer -Namespace 'ROOT/subscription' -Property $CIMCOnsumerProperties

######################
## CommandLineEventConsumer
######################
## $CIMCOnsumerProperties=@{
##  ## 消费者的唯一名称。
##	Name="MyStartAppConsumer"
##  ## 触发事件时要启动的应用程序的路径和参数。
##	CommandLineTemplate ='pwsh.exe c:\\myscript.ps1 -ServiceName %TargetInstance.name% -NewState %TargetInstance.State%'
##  ## (可选) 在一定秒数后终止应用程序。这有助于保护您的服务器资源。
##  ## KillTimeout = 5 
##}
##$CIMEventConsumer=New-CimInstance -ClassName CommandLineEventConsumer  -Namespace 'ROOT/subscription' -Property $CIMCOnsumerProperties

######################
## SMTPEventConsumer
######################
## 电子邮件消息正文
## $Message= '文件服务器更改了%Targetinstance.Name%,%TargetInstance.Status%'

## $CIMCOnsumerProperties=@{
##	Name="MyService-EmailConsumer"
##	## 发件人的电子邮件地址。
##	FromLine ='[email protected]'
##	## 收件人的电子邮件地址。
##	ToLine = '[email protected]'
##	## 转发消息的 SMTP 服务器。
##	SMTPServer = 'MySMTPServer.MyDomain.Com'
##	## 消息主题
##	Subject = '文件服务器更改…'
##	Message= $Message
##}
##$CIMEventConsumer=New-CimInstance -ClassName SMTPEventConsumer   -Namespace 'ROOT/subscription' -Property $CIMCOnsumerProperties

每个消费者类都有其自己的参数,因此请检查 CimClassProperties 以获取有关每个类的更多详细信息,例如 (Get-CimClass -ClassName __NTEventLogEventConsumer).CimClassProperties

创建消费者后,再次使用 Get-Ciminstance 检查其是否存在。

Get-CimInstance -Namespace Root/Subscription -ClassName LogFileEventConsumer
Consumer Registered with the required parameters

将事件过滤器和消费者绑定在一起

最后,现在是完成此订阅并将事件过滤器和消费者绑定在一起的时候了!正如您可能已经猜到的那样,创建绑定意味着创建一个新的 CIM 实例。这次,您必须在 __FilterToConsumerBinding 类中创建一个新实例。

下面的代码片段使用先前创建的两个实例(过滤器和消费者)作为哈希表,定义了创建新实例所需的属性。然后,像之前一样将其传递给 New-CimInstance 以创建绑定。

$CIMBindingProperties=@{
	Filter = [Ref]$CIMFilterInstance
	Consumer = [Ref]$CIMEventConsumer
}

$CIMBinding = New-CimInstance -ClassName __FilterToConsumerBinding -Namespace "root/subscription" -Property $CIMBindingProperties

像往常一样,通过再次运行 Get-CimInstance 来确认已创建绑定。

Get-CimInstance -Namespace Root/Subscription -ClassName __FilterToConsumerBinding

正如您所见,绑定包含有关 FilterConsumer 的信息。

Binding Details

测试订阅

您终于完成了!现在是时候测试您劳动的成果了!您现在唯一需要做的是更改 BITS 服务的状态,以查看 PowerShell 是否将条目写入位于 C:\MyCIMMonitoring.txt 的日志文件中。

根据 BITS 服务的状态,停止或启动它,或者只需使用 Restart-Service cmdlet 重新启动它。

Get-Service -Name BITS | Restart-Service

等待大约10秒钟,然后检查C:\MyCIMMonitoring.txt。您现在应该在创建消费者时定义的日志文件中看到Text

Get-Content -Path C:\MyCIMMonitoring.txt
Service Changes are detected

要监视所有的 WMI 事件活动,请查看路径为应用程序和服务日志\Microsoft\Windows\WMI-Activity\Operational 的 Windows 事件日志。

停止和清理订阅

完成订阅后,是时候清理它了。要停止并删除 WMI 事件订阅,必须删除事件过滤器、消费者和绑定实例。

通过首先使用 Get-CimInstance 查找实例来删除事件过滤器。

Get-CimInstance -Namespace Root/Subscription -ClassName __EventFilter | Remove-CimInstance

## 对于多个实例
Get-CimInstance -Namespace Root/Subscription -ClassName __EventFilter | where {$.name -like "MyServiceFilter"} | Remove-CimInstance
Registered EventFilter

接下来,以同样的方式删除消费者。

Get-CimInstance -Namespace Root/Subscription -ClassName LogFileEventConsumer | Remove-CimInstance

## 对于多个实例
Get-CimInstance -Namespace Root/Subscription -ClassName LogFileEventConsumer | where {$_.Name -like "MyServiceConsumer"} | Remove-CimInstance
Getting Consumer from the LogFileEventConsumer Class

最后,删除绑定。查找绑定的属性略有不同。绑定实例没有 Name 属性,而是有一个 Filter 属性,实际上是一个带有 Name 属性的对象。

Get-CimInstance -Namespace Root/Subscription -ClassName __FilterToConsumerBinding | Where-Object {$_.Filter.Name -like "MyServiceFilter"} | Remove-CimInstance
Getting the Binding information.

结论

WMI/CIM 是一个方便且强大的系统,可用于查找有关 Windows 的信息,并使用 WMI 事件对其进行监视。使用 WMI 事件监视更改可以为您提供更好的可见性,并更快地对可能的问题做出反应,从而更容易为每个事件自动化响应。

对于一个很好的现实世界的例子,请查看如何使用WMI事件跟踪Active Directory更改

Source:
https://adamtheautomator.com/your-goto-guide-for-working-with-windows-wmi-events-and-powershell/