與 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相關類的容器。可以將其視為包含與圖片相關內容的My Picture文件夾。有多個命名空間,其中最常見的是CIMv2,它包含大多數OS類。但是,所有命名空間都位於大的單一命名空間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中的數據存儲庫進行更現代交互的方法。CIM使用WS-MAN(Web-Service for Management)而不是RPC,這是一種更適合遠程管理的HTTP協議。

在本文和其他文章中,WMI和CIM可能可互換使用。這兩種管理方法互動的數據存儲庫通常被稱為WMI存儲庫。幾乎所有術語都指的是WMI,而CIM通常在PowerShell cmdlets中提到。

WMI與CIM和PowerShell

幸運的是,對於WMI和CIM在PowerShell中的交互,你有一些選擇。PowerShell支持與數據存儲庫互動的兩種方式。當你在PowerShell中運行Get-Command命令時,你可能會注意到各種Wmi cmdlets,如Get-WmiObjectInvoke-WmiMethodRemove-WmiObjectRegister-WmiEventSet-WmiInstance

如果你正在運行Windows PowerShell 3或更高版本(你最好是!),你還會看到一些名稱類似的cmdlets,如Get-CimInstanceGet-CimClassRemove-CimInstance

你應該使用哪個PowerShell cmdlets?答案很簡單;使用CIM cmdlets。CIM是微軟專注的新標準。WMI cmdlets甚至在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 中,像静态实例一样,事件包含在中。这些类包含了你上面查询的所有静态数据,以及触发对这些实例的更改。你可以在微软文档中找到所有 CIM 类的列表

要查找所有 CIM 类,请运行Get-CimClass 命令而不带任何参数。Get-CimClass 命令默认返回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 類實例上的 CimClassProperties PowerShell 物件屬性。

(Get-CimClass -ClassName win32_Service).CimClassProperties

請注意下面其中一個屬性是 State 屬性。

Some of the Win32_Service Properties

現在你知道你想要監視的 CIM 類和屬性是什麼,是時候訂閱 WMI 事件了!

構建 WMI 事件訂閱:高級概述

如果你以前從未創建過 WMI 事件訂閱,那麼創建一個可能是一個令人困惑的任務。為了幫助你保持行動一致,讓我們首先在開始討論具體步驟之前先從整體上概述基本步驟。

創建 WMI 事件訂閱需要四個大致步驟:

  1. 製作 WQL 查詢 – 就像查詢靜態數據時一樣,您必須創建一個符合您想查看的 WMI 事件類型的 WQL 查詢。但是,與查詢數據存儲不同,您必須在查詢中使用一些更複雜的組件,如系統類和檢查週期(稍後會談到更多內容)。
  2. 創建事件篩選器 – 一旦您創建了 WQL 查詢,您必須創建事件篩選器。事件篩選器將 WQL 查詢註冊在 CIM 中。
  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 检查类中实例的任何属性值更改。您将使用此类,因为您希望监视Win32_Service类的一个实例(服务)上的属性值Status
  • 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'

創建事件過濾器

既然您有了查詢過濾器,餘下的過程就容易理解多了!您現在必須創建事件過濾器以使用該查詢。事件過濾器實際上是 __EventFilter 中的另一個 CIM 實例,位於 Root/subscription 命名空間中。

以下是包含所需内容的代码片段。下面的脚本将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% 添加。在此示例中使用**Caption**和**State
	##**。
	Text = "The Service %TargetInstance.Caption% has been Changed: %TargetInstance.State%"
}
$CIMEventConsumer=New-CimInstance -ClassName LogFileEventConsumer -Namespace 'ROOT/subscription' -Property $CIMCOnsumerProperties

## 其他使用者的示例
######################
## NTEventLogEventConsumer
######################
## $Template = @(
##	'The Service %TargetInstance.Caption% has been Changed: %TargetInstance.State%'
##)
##$CIMCOnsumerProperties=@{
## ## 用戶的名稱
##	Name="MyEventLogConsumer"
## ## 要使用的事件 ID
##	EventID =[UInt32] 7040
##  EventType 可以是以下值之一
##    ## - **0**:成功的事件
##    ## - **1**:錯誤的事件
##    ## - **2**:警告的事件
##    ## - **4**:資訊的事件
##    ## - **8**:成功的審核事件
##    ## - **16**:失敗的審核事件
##  EventType=[UInt32] 1 #資訊
## ## 事件來源的名稱
##  SourceName="Service Control Manager"
##  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= 'The File Server changed %Targetinstance.Name% , %TargetInstance.Status%'

## $CIMCOnsumerProperties=@{
##	Name="MyService-EmailConsumer"
##	## 寄件者的電子郵件地址。
##	FromLine ='[email protected]'
##	## 收件者的電子郵件地址。
##	ToLine = '[email protected]'
##	## 用於中繼訊息的 SMTP 伺服器。
##	SMTPServer = 'MySMTPServer.MyDomain.Com'
##	## 訊息主旨
##	Subject = 'File Server Changed…'
##	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命令重新启动它。

Get-Service -Name BITS | Restart-Service

等待大約10秒,然後檢查C:\MyCIMMonitoring.txt。現在你應該在創建消費者時定義的日誌文件中看到文字。

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/