vbscript - 取消使用 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 服务可以消除轮询,但显然这不是解决这种情况的好方法。
解决方案
你可以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 服务是摆脱轮询循环的唯一方法。
推荐阅读
- powershell - 如何创建适用于格式表的 PowerShell 数据结构
- javascript - 如何使用 javascript 过滤各种 mime 文件类型
- recursion - 递归地将项目添加到 SML 中的 2 元组列表中
- reporting-services - 按 SSRS 中的多值参数过滤
- python - 如何在python中提取txt文件中的数字
- leaflet - 传单地图同时显示英语和当地语言
- css - 在 Typescript 中定义 CSS Position 接口的正确方法是什么?
- laravel - Laravel - 在一个查询中搜索两个表
- sql - 优化实体框架中的 SQL 查询以提高性能
- scala - 使用 Scala 在 Hive 中为多个单记录文件加载数据的最佳方法