WMI事件子系统允许您订阅WMI事件。WMI事件代表WMI数据中的更改:如果启动记事本,Win32_Process则会创建WMI类的实例,并创建实例创建WMI事件。如果您从磁盘中删除文件,CIM_DataFile则会删除该类的一个实例,并创建一个实例删除WMI事件。实际上,可以使用WMI数据中的任何更改来创建事件,因此很容易了解WMI事件如何在系统管理中起作用。
WMI不会为您创建活动,除非您订阅它们。注册他们对WMI事件感兴趣的应用程序称为事件使用者。
有两种类型的事件消费者:临时和永久。临时事件使用者通常是使用.NET Framework及其System.Management名称空间或WMI脚本库来接收WMI事件的应用程序,这意味着它们仅在用户启动时收到事件。永久事件消费者是不同的 - 他们的目的是随时接收事件。临时和永久事件使用者都使用WMI事件查询来订阅他们感兴趣的事件。
就像其他WMI查询一样,使用WQL(WMI查询语言)发布WMI事件查询。事件查询和其他查询类型之间有几个区别,但最重要的是WMI事件查询使用WMI事件类。如果WMI类是从__Event系统类派生的,则它是一个事件类。因此,要查看使用WMI事件可以完成哪些任务,首先需要检查WMI事件类。但是,你怎么能这样做呢?由于所有事件类都是从__Event系统类派生的,因此可以使用这样的查询:
Select * From Meta_Class Where __This Isa "__Event"
尽管此查询包含对__Event类的引用,但它不是事件查询。实际上,它是一个WMI模式查询 - 它使用Meta_Class一个特殊的类来表示WMI名称空间中的所有类。既然你不想要所有的类,但只__Event需要派生类,你还需要添加这个WHERE子句。发布时,查询返回如下所示的WMI类列表:
. . . MSFT_WMI_GenericNonCOMEvent MSFT_WmiSelfEvent Msft_WmiProvider_OperationEvent Msft_WmiProvider_ComServerLoadOperationEvent Msft_WmiProvider_InitializationOperationFailureEvent Msft_WmiProvider_LoadOperationEvent Msft_WmiProvider_OperationEvent_Pre Msft_WmiProvider_DeleteClassAsyncEvent_Pre Msft_WmiProvider_GetObjectAsyncEvent_Pre Msft_WmiProvider_AccessCheck_Pre Msft_WmiProvider_CreateClassEnumAsyncEvent_Pre Msft_WmiProvider_ExecQueryAsyncEvent_Pre Msft_WmiProvider_CreateInstanceEnumAsyncEvent_Pre Msft_WmiProvider_NewQuery_Pre Msft_WmiProvider_DeleteInstanceAsyncEvent_Pre Msft_WmiProvider_CancelQuery_Pre Msft_WmiProvider_PutInstanceAsyncEvent_Pre . . .
在测试的Windows XP SP2计算机上,查询返回总共136个类。这个数字在您的计算机上可能会有所不同,但如果仔细检查这个列表,您会注意到最常用的WMI类似于Win32_Process或Win32_Service不在其上。
因此,您真正感兴趣的__Event类不是从该类派生的,但仍可以在WMI事件查询中使用它们。您可以在事件查询中使用所有WMI类,但不能直接使用。为了使用不是从__Event事件查询派生的类,您需要使用以下其中一个助手类:
__InstanceCreationEvent __InstanceModificationEvent __InstanceDeletionEvent
以上所有类都是派生自__InstanceOperationEvent并具有一个TargetInstance属性,它是您希望从其接收事件通知的类实例的引用。所以,如果你使用这样的查询:
Select * From __InstanceCreationEvent Where TargetInstance Isa "Win32_Process"
TargetInstance返回的事件的属性将包含对Win32_Process创建的实例的引用。如果您想引用该Win32_Process.ExecutablePath属性,请使用该__InstanceCreationEvent.TargetInstance.ExecutablePath属性。此外,__InstanceModificationEvent该类还具有在PreviousInstance修改WMI类实例副本之前包含对其副本的引用的属性。派生自__InstanceOperationEvent它们的TargetInstance属性及其属性使您可以在事件查询中使用所有WMI类。
WMI事件子系统使用轮询机制进行事件传递。要指定轮询间隔,请使用WITHIN关键字,后跟轮询间隔(以秒为单位):
Select * From Win32_Process Within 10 Where TargetInstance Isa "Win32_Process"
在这个例子中,WMI最初枚举所有Win32_Process实例,并每十秒轮询一次更改。这意味着可能会错过一些事件:如果一个进程在不到十秒的时间内被创建并销毁,它将不会引发事件。
该Group子句导致WMI仅创建一个事件通知来表示一组事件。例如,这个查询:
Select * From __InstanceModificationEvent Within 10 Where TargetInstance Isa "Win32_PerfFormattedData_PerfOS_Processor" Group Within 5 Having NumberOfEvents > 3
将创建一个事件,该事件代表Win32_PerfFormattedData_PerfOS_Processor在5秒内发生的所有修改事件,但仅当事件数量大于3时才会发生。
所以:
临时事件使用者是从WMI请求事件通知的任何应用程序。在大多数情况下,它是使用System.Management名称空间的VBScript或代码。以下是订阅Win32_Process创建事件的示例VBScript :
' VBScript source code strComputer = "." Set objWMIService = GetObject("winmgmts:" _ & "\\" & strComputer & "\root\cimv2") Set colMonitoredProcesses = objWMIService. _ ExecNotificationQuery("select * from __InstanceCreationEvent " _ & " Within 1 Where TargetInstance isa 'Win32_Process'") Do Set objLatestProcess = colMonitoredProcesses.NextEvent Wscript.Echo objLatestProcess.TargetInstance.Name Loop
尽管此代码有效,但它至少有三个缺点:
以下是第三种情况的示例:
' VBScript source code strComputer = "." Set objWMIService = GetObject("winmgmts:" _ & "\\" & strComputer & "\root\cimv2") Set colEvents = objWMIService.ExecNotificationQuery _ ("Select * From __InstanceCreationEvent Within 2" _ & "Where TargetInstance Isa 'Win32_Directory' " _ & "And TargetInstance.Path = '\\Scripts\\'") Do Set objEvent = colEvents.NextEvent() WScript.Echo objEvent.TargetInstance.Name Loop
像这样的脚本通常从命令提示符运行。但是,即使您停止脚本,事件通知也不会被取消 - 您可以轻松观察到,因为软盘驱动器每两秒钟仍在闪烁(我称之为'FDD Light Show')。这不仅适用于文件系统监控脚本,还适用于其他方面。在这种情况下取消事件通知的唯一方法是使用以下命令停止Winmgmt服务本身:
net stop winmgmt
Windows防火墙服务取决于Winmgmt,所以很容易想象这会成为问题。
WMI永久事件订阅可以解决所有这些问题。它不依赖于正在运行的进程(除了承载Winmgmt服务的svchost.exe外)。要中断它,你需要WMI的知识,所以不小心阻止它并不容易,并且可以随时取消它,而无需重新启动Winmgmt服务。在其基础上,永久事件订阅是存储在CIM存储库中的一组静态WMI类。当然,您可以使用VBScript或.NET Framework System.Management类创建这些实例并设置永久事件订阅,但最简单的方法是(至少在我看来)使用MOF。以下是您可以用作创建永久事件订阅模板的示例MOF:
// 1. Change the context to Root\Subscription namespace // All standard consumer classes are // registered there. #pragma namespace("\\\\.\\root\\subscription") // 2. Create an instance of __EventFilter class // and use it's Query property to store // your WQL event query. instance of __EventFilter as $EventFilter { Name = "Event Filter Instance Name"; EventNamespace = "Root\\Cimv2"; Query = "WQL Event query text"; QueryLanguage = "WQL"; }; // 3. Create an instance of __EventConsumer // derived class. (ActiveScriptEventConsumer // SMTPEventConsumer etc...) instance of __EventConsumer derived class as $Consumer { Name = "Event Consumer Instance"; // Specify any other relevant properties. }; // 4. Join the two instances by creating // an instance of __FilterToConsumerBinding // class. instance of __FilterToConsumerBinding { Filter = $EventFilter; Consumer = $Consumer; };
要创建永久WMI事件订阅,您需要执行以下步骤:
创建__EventConsumer派生类的实例。这是表示事件使用者的实例。虽然可以创建自定义__EventConsumer派生类,但这需要创建一个对特定事件有反应的COM对象,这对大多数系统管理员来说可能不是一个可行的解决方案。幸运的是,微软提供了一组标准的事件消费类,像ActiveScriptEventConsumer,LogFileEventConsumer等在这篇文章中,我将仅使用标准事件消费类。
关联的实例__EventFilter,并__EventConsumer通过创建的实例派生类__FilterToConsumerBinding的类。其Filter属性是对以前创建的__EventFilter类实例的Consumer引用,其属性是对标准事件使用者类实例之一的引用。
在本文的其余部分,我将尝试引导您使用标准事件消费者类的几个永久事件订阅样本。
名为WMI事件注册的工具包含在WMI工具中,它在处理永久订阅时非常有用:它允许您探索现有的过滤器,消费者或定时器,并使用用户友好的界面创建新的过滤器。您也可以使用此工具取消活动订阅。
首次打开此工具时,您可以连接到Root\Cimv2名称空间,但可以连接到Root\Subscription- 这是您将创建大多数永久事件订阅的位置。连接后,如果从最左侧的下拉列表中选择“消费者”,您将看到左侧窗格中列出的所有可用标准事件使用者类的列表,因为它们已在此处注册。
如果任何标准事件消费者类的实例已经存在,那么通过选择它,您可以__EventFilter在右侧窗格中查看可用实例。如果任何__EventFilter实例与所选消费者实例连接,则会进行检查,因此,绿色复选标记实际上代表__FilterToConsumerBinding该类的一个实例。
这里介绍的所有永久事件订阅示例都是使用MOF创建的 - 您需要一个名为mofcomp.exe的工具来将包含在MOF文件中的实例定义存储到CIM存储库中。Mofcomp.exe存储在Windows目录中(通常为C:\ Windows \ System32 \ Wbem \),其基本语法是:
mofcomp FileName.mof
该ActiveScriptEventConsumer班是标准的事件消费者的一类:它可以让你无论何时事件被传递到它运行ActiveX脚本代码。要创建ActiveScriptEventConsumer类的实例,您需要为其属性指定值:
作为测试,您可以创建一个事件使用者,只要创建了一个Win32_Process名为' notepad.exe ' 的实例,就会执行一些任意的VBScript代码。要创建使用以下内容的永久事件订阅ActiveScriptEventConsumer:将当前的WMI名称空间更改为Root\Subscription:
#pragma namespace("\\\\.\\root\\subscription")
创建一个EventFilter类的实例来监视Win32_Process创建: <code>instance of EventFilter as $EventFilter {
EventNamespace = "Root\\Cimv2"; Name = "New Process Instance Filter"; Query = "Select * From __InstanceCreationEvent Within 2" "Where TargetInstance Isa \"Win32_Process\" " "And Targetinstance.Name = \"notepad.exe\" "; QueryLanguage = "WQL";
}; </code>
在这种情况下,您将收到一个__InstanceCreationEvent类实例,但是会从该Root\Cimv2名称空间中获取Win32_Process该类所在的位置。
创建ActiveScriptEventConsumer该类的一个实例:
instance of ActiveScriptEventConsumer as $Consumer { Name = "TestConsumer"; ScriptingEngine = "VBScript"; ScriptText = "Set objFSO = CreateObject(\"Scripting.FileSystemObject\")\n" "Set objFile = objFSO.OpenTextFile(\"c:\\log.txt\", 8, True)\n" "objFile.WriteLine Time & \" \" & \" Notepad started\"\n" "objFile.Close\n"; };
分配给其ScriptText属性的VBScript代码只是将创建notepad.exe进程的时间记录到文本文件中。
使用__FilterToConsumerBinding该类绑定以前创建的两个实例:
instance of __FilterToConsumerBinding { Consumer = $Consumer; Filter = $EventFilter; }
当您使用mofcomp.exe编译上述MOF时,每次打开记事本时,创建notepad.exe进程的时间都会记录到c:\ log.txt文件中。如果文件尚不存在,则会在收到第一个事件通知时创建。而不是ScriptText,您也可以使用该ScriptFileName属性:
instance of ActiveScriptEventConsumer as $Consumer { Name = "ExternalScriptConsumer"; ScriptingEngine = "VBScript"; ScriptFileName = "C:\\Consumer.vbs"; };
在这种情况下,您还需要一个外部脚本文件:c:\ Consumer.vbs。
创建VBScript或JScript脚本以供使用时ActiveScriptEventCosumer,您需要了解一些限制:
在设置永久事件订阅时,您可能需要使用字符串,因此以下是一个快速注释:
MOF字符串是用双引号括起来的字符序列。连续的字符串连接在一起,因此:
"Select * From __InstanceCreationEvent " "Within 30 "
变为:
"Select * From __InstanceCreationEvent Within 30 "
您也可以使用以下转义序列:
\b backspace \t horizontal \n linefeed \f form feed \r carriage return \" double quote \' single quote \\ backslash
由ActiveScriptEventConsumer实例执行的脚本可以访问一个名为的环境变量TargetEvent,该环境变量包含对事件类的引用:
instance of ActiveScriptEventConsumer as $Consumer { Name = "TargetEventConsumer"; ScriptingEngine = "VBScript"; ScriptText = "Const ForReading = 1\n" "Const ForWriting = 2\n" "\n" "Set objFso = CreateObject(\"Scripting.FileSystemobject\")\n" "Set objStream = objFso.OpenTextFile( _\n" " TargetEvent.TargetInstance.Name, ForReading, False)\n" "\n" "strContent = objStream.ReadAll()\n" "objStream.Close\n" "\n" "Set objStream = objFso.OpenTextFile( _\n" " TargetEvent.TargetInstance.Name, ForWriting, False)\n" "\n" "objStream.Write( _\n" " Replace(strContent, \"127.0.0.1\", \"Localhost\"))\n" "objStream.Close\n"; };
事件类通常是各种__InstanceOperationEvent派生类中的一种,其TargetInstance属性反过来又是对创建的实际类实例的引用。例如,如果该类是,CIM_DataFile您需要使用以下来访问其Name属性:
TargetEvent.TargetInstance.Name
每次将事件传递给该类时,该类都会发送一封电子邮件。要创建SMTPEventConsumer类的实例,请为其属性指定值:
例如,设置一个永久事件订阅,SMTPEventConsumer每次打印机状态更改时使用该类来发送电子邮件消息。用于SMTPEventConsumer永久事件订阅:
将上下文更改为Root\Subscription名称空间:
#pragma namespace("\\\\.\\root\\subscription")
创建__EventFilter该类的一个实例:
instance of __EventFilter as $EventFilter { EventNamespace = "Root\\Cimv2"; Name = "SMTPEventFilter"; Query = "Select * From __InstanceModificationEvent " "Within 2 " "Where TargetInstance Isa \"Win32_Printer\" " "And (TargetInstance.PrinterStatus = 1 " "Or TargetInstance.PrinterStatus = 2) " "And Not (PreviousInstance.PrinterStatus = 1 " "Or PreviousInstance.PrinterStatus = 2)"; QueryLanguage = "WQL"; };
使用上面的WQL查询,您可以订阅Win32_Printer类实例的修改事件。请注意该__InstanceModificationEvent.PreviousInstance属性的用法,该属性Win32Printer在更改之前包含该实例的副本。它在修改之前和之后比较实例属性非常有用。在这种情况下,我们只关心Win32_Printer.PrinterStatus值从其他值改变为1或2的事件。
创建SMTPEventConsumer该类的一个实例:
instance of SMTPEventConsumer as $Consumer { Name = "Printer Error Event Consumer"; SMTPServer = "SMTPServerName"; ToLine = "[email protected]"; FromLine = "[email protected]"; Subject = "Printer Error!"; Message = "An error is detected in one of the printers!\n" "Printer Name: %TargetInstance.Name%\n" "Server: %TargetInstance.__Server%\n" "Event Date: %TIME_CREATED%"; };
如果您查看SMTPEventConsumer类MOF代码,您会看到其大多数属性都标有Template限定符。这意味着您可以在设置其值时使用WMI标准字符串模板。使用标准字符串模板,您可以访问事件类属性,就像您可以使用TargetEvent环境变量一样ActiveScriptEventConsumer。举例来说,如果TargetInstance是Win32_Printer这样的话:
"Printer Name: %TargetInstance.Name%"
将被转化成以下内容:
"Printer Name: HP LaserJet III PostScript Plus v2010.118"
另外,这个:
"Event Date: %TIME_CREATED%"
会变成:
"Event Date: 128611400690000000"
__InstanceModificationEvent.Time_Created 是1601年1月1日之后的100纳秒间隔的数量,所以如果您想将其转换为可读格式,则可能需要一些工作。
通过创建__FilterToConsumerBinding类的实例来绑定这两个实例:
instance of __FilterToConsumerBinding { Consumer = $Consumer; Filter = $EventFilter; };
LogFileEventConsumer每次将事件传送到文本文件时,该类都将自定义字符串写入文本文件。重要属性:
示例用法LogFileEventConsumer可能是记录Windows服务状态中的更改。用于LogFileEventConsumer永久活动订阅:
将上下文更改为Root\Subscription名称空间:
#pragma namespace("\\\\.\\root\\subscription")
创建__EventFilter监视Win32_Service修改事件的类的实例:
instance of __EventFilter as $EventFilter { EventNamespace = "Root\\Cimv2"; Name = "Service State Event Filter"; Query = "Select * From __InstanceModificationEvent " "Within 2 " "Where TargetInstance Isa \"Win32_Service\" " "And TargetInstance.State <> " "PreviousInstance.State"; QueryLanguage = "WQL"; };
创建一个实例LogFileEventConsumer:
instance of LogFileEventConsumer as $Consumer { Name = "Service State Log Consumer"; Filename = "c:\\scripts\\ServiceStateLog.csv"; IsUnicode = true; Text = "\"%TargetInstance.Name%\"," "\"%PreviousInstance.State%\"," "\"%TargetInstance.State%\"," "\"%TIME_CREATED%\""; };
使用__FilterToConsumerBinding该类绑定这两个实例:
instance of __FilterToConsumerBinding { Consumer = $Consumer; Filter = $EventFilter; };
将值分配给LogFileEventConsumer.Text属性时,请使用WMI标准字符串模板访问与事件相关的数据。
CommandLineEventConsumer当一个事件传递给它时,该类将启动一个任意进程。重要的属性是:
以下是一个CommandLineEventConsumer监视PNP设备更改的MOF 示例。要创建使用以下内容的永久事件订阅CommandLineEventConsumer:
将WMI上下文更改为Root\Subscription名称空间:
#pragma namespace("\\\\.\\root\\subscription")
创建一个EventFilter检测Win32_PNPEntity实例创建的类的实例: <code> instance of EventFilter as $EventFilter {
EventNamespace = "Root\\Cimv2"; Name = "Test Command Line Event Filter"; Query = "Select * From __InstanceCreationEvent " "Within 2 " "Where TargetInstance Isa \"Win32_PNPEntity\" "; QueryLanguage = "WQL";
}; </code>
创建CommandLineEventConsumer该类的一个实例:
instance of CommandLineEventConsumer as $Consumer { Name = "Test CommandLine Event Consumer"; RunInteractively = false; CommandLineTemplate = "cmd /c " "WMIC /Output:" "C:\\HWLogs\\PNPDeviceLog%TIME_CREATED%.html " "Path Win32_PNPEntity " "Get Caption, DeviceId, PNPDeviceId " "/Format:HTable.xsl"; };
此CommandLineEventConsumer实例使用WMI命令行实用程序(WMIC)创建一个简单的HTML文件,其中包含Win32_PNPEntity每次Win32_PNPEntity创建新实例时所有实例的列表。
使用__FilterToConsumerBinding以下方式绑定实例:
instance of __FilterToConsumerBinding { Consumer = $Consumer; Filter = $EventFilter; };
该Win32_LocalTime班是一个例外:它不会从派生__Event类,但你仍然可以使用它在WQL事件查询,这意味着,你也可以用它来建立一个永久的事件订阅。Win32_LocalTime该类的一个有趣用途可以是模仿Windows Scheduler服务。要创建订阅Win32_LocalTime事件的永久事件订阅,请执行以下操作:
将上下文更改为Root\Subscription名称空间:
#pragma namespace("\\\\.\\root\\subscription")
创建__EventFilter该类的一个实例:
instance of __EventFilter as $EventFilter { EventNamespace = "Root\\Cimv2"; Name = "Sample Timer Event Filter"; Query = "Select * From __InstanceModificationEvent " "Where TargetInstance Isa \"Win32_LocalTime\" " "And TargetInstance.Hour = 18 " "And TargetInstance.Minute = 10 " "And TargetInstance.Second = 30"; QueryLanguage = "WQL"; };
使用此过滤器来订阅Win32_LocalTime修改事件。在查询中,你可以使用任意组合Win32_LocalTime属性:Day,DayOfWeek,Hour,Milliseconds,Minute,Month,Quarter,Second,WeekInMonth,和Year。
创建__EventConsumer派生类的实例:
instance of CommandLineEventConsumer as $Consumer { Name = "Test CommandLine Event Consumer"; RunInteractively = false; CommandLineTemplate = "cmd /c " "C:\\Backup\\LocalBackup.bat"; };
在这种情况下,它是CommandLineEventConsumer类的一个实例,但它可以是任何标准的消费者类。
通过创建类的实例来绑定__EventFilter和CommandLineEventConsumer实例__FilterToConsumerBinding:
instance of __FilterToConsumerBinding { Consumer = $Consumer; Filter = $EventFilter; };
永久事件订阅与临时事件订阅相比有几个优点,但它也有一个缺点:临时事件订阅更容易调试。如果使用System.Management命名空间创建预订WMI事件的应用程序,则可以使用所有Visual Studio调试工具。如果您使用的是VBScript,则可以从其他代码中分别测试WQL事件查询,并从WMI收到有意义的(至少有时)错误消息。在测试永久事件订阅时,调试信息的唯一来源是名为Wbemess.log的WMI事件子系统日志文件(它通常位于C:\ Windows \ System32 \ Wbem \ Logs \目录) - 在事件过滤器和事件使用者实例中检测到的所有错误都记录在那里,并且这些消息并不总是易于解密。所以,最好先测试一下你想用于永久事件订阅的WQL查询,System.Management或者首先使用VBScript。
永久事件订阅可能很有用,但如果您不仔细使用它,它可能会消耗太多系统资源并变得效率低下。有两种方法可以解决这个问题:
Select * From __InstanceCreationEvent Where TargetInstance Isa "CIM_DataFile" And TargetInstance.Path = "\\Logs\\"
会比这个效率低:
Select * From __instanceCreationEvent Where TargetInstance Isa "CIM_DataFile" And TargetInstance.Drive = "C:" And TargetInstance.Path = "\\Logs\\" And TargetInstance.Extension = "Log"
查询包括文件系统类像CIM_DataFile或Win32_Directory可以是非常消耗资源,一般来说:监视一对夫妇数百个文件,可以显着降低系统性能的查询。
WQL是SQL的一个版本,对于SQL查询,通常建议不要选择表中的所有字段(使用'*'),除非您确实需要所有字段。我没有用WQL查询测试过这个建议,但我不认为这个建议适用于WQL。