取消使用 ExecNotificationQuery 创建的 WMI 事件通知

Cancel WMI event notification created with ExecNotificationQuery

我可以在脚本中创建非永久性 WMI 事件查询,例如这个,它记录了接下来 5 个新 Notepad.exe 进程的 PID:

Set WMI = GetObject("winmgmts:\.\ROOT\cimv2")

wql = "SELECT * FROM __InstanceCreationEvent WITHIN 2 " & _ 
      "WHERE TargetInstance ISA 'Win32_Process' AND TargetInstance.Name = 'Notepad.exe'"
Set EventSource = WMI.ExecNotificationQuery(wql)

For i = 1 To 5
    With EventSource.NextEvent(-1)
        Wscript.Echo .TargetInstance.ProcessId
    End With
Next

但我缺少一种明确取消 EventSource 的方法。否则,事件通知将在 WMI 内无限期地继续 运行,即使侦听生成的事件的脚本因任何原因终止 (*)。当脚本多次 运行 时,这将导致开销增加。

MSDN documentation of IWbemServices::ExecNotificationQuery 说:

The IWbemServices::ExecNotificationQuery method executes a query to receive events. The call returns immediately, and the user can poll the returned enumerator for events as they arrive. Releasing the returned enumerator cancels the query.

如何释放返回的枚举器?

EventSource 对象似乎不可枚举。尝试在其上使用 For Each 失败并显示 "VBScript 运行 时间错误:对象不支持此 属性 或方法",所以我可以不要在 For Each 循环的末尾使用隐式 Release


(*) 这是根据文档说明 “释放返回的枚举器取消查询。”,这意味着 not 释放枚举器会导致查询持续存在 - 但它也可以明确确认:

为此查询设置通知 "SELECT * FROM __InstanceCreationEvent WITHIN 2 WHERE TargetInstance ISA 'Win32_Directory' And TargetInstance.Name = 'C:\Test'" 并使用 Sysinternals Process Monitor 观察来自 WMI 服务 (WmiPrvSE.exe) 的文件系统访问。 WMI 服务将开始每 2 秒轮询一次名为“C:\Test”的文件夹,并在设置监视的脚本结束后继续这样做。

重新启动 WMI 服务可以摆脱轮询,但显然这不是解决这种情况的好办法。

您可以使用 ExecNotificationQueryAsync instead? That way you can pass it a SWbemSink object that you can later call Cancel() method 来取消接收器,它还应该删除与该接收器关联的任何事件消费者。

使用 ExecNotificationQuery() 方法的问题是它只允许您访问 SWbemEventSource 对象,该对象允许调用枚举器中的下一个事件。似乎不可能在事件消费者注册后使用该方法删除它。

运行这个:

Set FSO = CreateObject("Scripting.FileSystemObject")
Set WMI = GetObject("winmgmts:\.\ROOT\cimv2")
Set Sink = WScript.CreateObject("WbemScripting.SWbemSink", "Sink_")

wql = "SELECT * FROM __InstanceCreationEvent WITHIN 2 " & _
      "WHERE TargetInstance ISA 'Win32_Directory' And TargetInstance.Name = 'C:\Test'"

WScript.Echo "Waiting for events..."
WMI.ExecNotificationQueryAsync Sink, wql

For i = 1 To 10
    WScript.Echo i
    Wscript.Sleep 1000
    If i = 5 Then
        FSO.CreateFolder "C:\Test"
        WScript.Echo "Test folder created."
    End If
Next

Sink.Cancel
WScript.Echo "Sink canceled."

FSO.DeleteFolder "C:\Test"
WScript.Echo "Test folder deleted."

Sub Sink_OnObjectReady(eventObject, asyncContext)
    Set folder = eventObject.TargetInstance
    WScript.Echo "__InstanceCreationEvent: " & folder.Name
End Sub

输出类似这样的东西:


Waiting for events...
1
2
3
4
5
Test folder created.
6
7
__InstanceCreationEvent: C:\Test
8
9
10
Sink canceled.
Test folder deleted.

之后,Process Monitor 将确认 WMI 已停止对文件夹创建的后台轮询。

警告:调用 Sink.Cancel 是绝对必要的。如果脚本意外终止,WMI 在后台继续轮询,重新启动 WMI 服务是摆脱轮询循环的唯一方法。