首页 > 解决方案 > 取消使用 ExecNotificationQuery 创建的 WMI 事件通知

问题描述

我可以在脚本中创建非永久性 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文档IWbemServices::ExecNotificationQuery说:

IWbemServices ::ExecNotificationQuery方法执行查询以接收事件。调用立即返回,用户可以在事件到达时轮询返回的枚举器以获取事件。释放返回的枚举数会取消查询。

如何释放返回的枚举数?

EventSource对象似乎不可枚举。尝试For Each在它上面使用失败,并出现"VBScript runtime error: Object doesn't support this property or method",所以我不能在循环Release结束时使用隐式。For Each


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

为此查询设置通知"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 服务可以消除轮询,但显然这不是解决这种情况的好方法。

标签: vbscriptwmi

解决方案


你可以ExecNotificationQueryAsync改用吗?这样,您可以向它传递一个SWbemSink对象,您可以稍后调用Cancel()方法来取消接收器,该接收器还应该删除与该接收器关联的任何事件使用者。

使用该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

输出与此类似的内容:

等待事件...
1
2
3
4
5
已创建测试文件夹。
6
7
__InstanceCreationEvent: C:\Test
8
9
10
接收器已取消。
测试文件夹已删除。

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

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


推荐阅读