【问题标题】:Cancel WMI event notification created with ExecNotificationQuery取消使用 ExecNotificationQuery 创建的 WMI 事件通知
【发布时间】:2021-04-11 10:35:16
【问题描述】:

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

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

如何释放返回的枚举数?

EventSource 对象似乎不可枚举。尝试在其上使用 For Each 失败,“VBScript 运行时错误:对象不支持此属性或方法”,所以我不能在 @ 末尾使用隐式 Release 987654333@循环。


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

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

【问题讨论】:

  • Set EventSource = Nothing 呢?
  • @user692942 不,这没有帮助。当脚本退出时,无论如何都会发生这种情况。但是 WMI 事件查询是这样的:您向 WMI 服务发送查询。 WMI 服务在内部设置对请求条件的监视,并返回一种方法来侦听生成的事件。在 VBScript 中将事件侦听器设置为 Nothing 不会破坏机制的 WMI 内部部分。
  • 是的,但这不是一个永久的事件消费者,你正在设置它是一个临时的,它们的工作方式不同。这就是为什么most examples use an infinite do while loop 通常会在脚本延迟的情况下阻止脚本退出。您是否确定在脚本完成后会保持事件侦听器,还是只是一个假设?
  • 查看您最近的编辑,听起来它正在保留该事件,因此您可能想致电ExecNotificationQueryAsync 并传入the sinkcancel that。无论如何,我都不是 WMI 方面的专家,我只是提供建议。
  • @user692942 不,你的想法是对的。取消异步接收器实际上是这样做的。好的。 :) 你能把它变成答案吗?

标签: vbscript wmi


【解决方案1】:

您能改用ExecNotificationQueryAsync 吗?这样,您可以将 SWbemSink object 传递给它,稍后您可以调用 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

输出与此类似的内容:

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

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

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

【讨论】:

  • 很高兴这成功了,很好的例子感谢您将其添加到答案中。
  • 太糟糕了,VBScript 似乎没有弹性的方法来做到这一点。我想知道 PowerShell 是否能够做得更好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-21
  • 1970-01-01
  • 1970-01-01
  • 2022-06-10
  • 2017-06-11
  • 1970-01-01
相关资源
最近更新 更多