[原创]WMI 永久订阅的 Filter 与 Consumer 绑定解析 WMI 永久订阅的 Filter 与 Consumer 绑定解析文章中内容已经在Ksword中实现仓库地址https://github.com/KSwordDEV/KSword本篇要完成的是读取ROOT\subscription中的永久订阅列出每个 Filter 的触发条件再通过 Binding 的对象路径还原它连接的 Filter 和 Consumer。要完成这项读取分为四步初始化 COM 与调用安全状态。连接ROOT\subscription取得该命名空间的服务接口。查询__EventFilter保存名称、WQL、查询语言和事件命名空间。查询__FilterToConsumerBinding解析其中的 Filter 与 Consumer 对象路径。这四步按顺序提供读取条件、目标命名空间、触发条件和关联关系。最后得到的结果说明某个 Filter 在读取时连接到哪个 Consumer同时保留无法解析的原始引用和失败状态。一、对象关系和完整流程永久订阅的运行路径由 Filter、Binding 和 Consumer 组成。Filter 负责匹配事件。Binding 保存对象引用。Consumer 负责事件匹配后的处理方式。三者都位于 WMI 的ROOT\subscription命名空间。系统状态或事件来源变化 - __EventFilter 匹配事件 WQL - __FilterToConsumerBinding 提供对象引用 - Consumer 执行命令、处理脚本或写日志初始化 COM - 连接 ROOT\subscription - 查询 Filter - 查询 Binding - 解析两端对象路径 - 输出关联关系与读取状态读取过程需要分别查询 Filter 与 Binding。它以 Binding 的两个对象路径建立关系不按名称文本猜测归属。Consumer 的类名和名称只在对象路径可解析时读取。WMI 是按命名空间组织的管理对象系统Windows Management InstrumentationWMI为系统、驱动、服务和应用提供统一的管理对象访问方式。WMI 服务接收查询请求。WMI 提供程序把操作系统中的状态映射成类和实例。客户端通过 COM 接口读取这些对象。WMI 的基础对象各有职责。命名空间类似逻辑容器例如ROOT\CIMV2包含大量系统管理类。类描述一组属性和方法例如Win32_Process。实例表示某一时刻的具体对象例如某个进程实例。对象路径是能够唯一定位实例的字符串。客户端指定命名空间和查询后就能通过IWbemServices获取实例。WMI 客户端 COM 调用 / WQL 查询 WMI 服务winmgmt 命名空间 ROOT\CIMV2提供程序与系统管理对象 命名空间 ROOT\subscription永久订阅对象命名空间之间相互独立。某个类在ROOT\CIMV2存在不表示相同类名能在ROOT\subscription查询到。连接成功也只代表拿到所选命名空间的服务接口。WQL 描述对象筛选条件事件 WQL 描述“什么时候”普通查询与事件查询需要分开理解。WQLWMI Query Language是一种面向 WMI 对象的查询语言语法接近 SQL。普通 WQL 例如SELECT Name, ProcessId FROM Win32_Process读取当前实例。事件 WQL 使用__InstanceCreationEvent、__InstanceDeletionEvent、__InstanceModificationEvent等事件类描述对象状态变化时应关注的条件。一个典型的内在事件查询可能是SELECT*FROM__InstanceCreationEventWITHIN5WHERETargetInstance ISAWin32_ProcessWITHIN 5表示 WMI 按一定间隔检查这类变化单位是秒。TargetInstance是事件携带的变化对象。ISA Win32_Process限制关注的对象类别。查询只表达“匹配什么事件”并不包含执行命令、写日志或调用脚本的处理细节。事件还可以由专用提供程序直接报告。无论事件来自轮询还是提供程序Filter 都需要通过EventNamespace指明查询在哪个命名空间解释。相同的 WQL 文本放在不同事件命名空间中可能指向完全不同的类集合。临时订阅存在于连接期间永久订阅保存为 WMI 对象临时订阅与永久订阅的存续范围不同。临时事件订阅由客户端调用事件查询接口建立客户端断开后该订阅通常不再保留。永久事件订阅把 Filter、Consumer 和 Binding 实例保存到ROOT\subscription。WMI 服务可以在随后满足条件时按保存的关联处理事件。永久订阅由三个可独立存在的对象组成。__EventFilter保存事件 WQL、查询语言和事件命名空间。事件处理对象实例保存处理方式。__FilterToConsumerBinding的Filter与Consumer属性保存到两端的对象路径引用。Binding 才是条件与处理端之间的连接边。__EventFilter Name / Query / QueryLanguage / EventNamespace __FilterToConsumerBinding Filter / Consumer / DeliveryQoS / MaintainSecurityContext 事件处理对象实例 __CLASS / Name / 该处理对象类型的特有属性这三个对象可以形成一对多或多对一关系。一个 Filter 能由多个 Binding 指向不同 Consumer一个 Consumer 也能被多个 Binding 引用。因此读取过程应为每个对象保留稳定对象路径并以 Binding 的两个引用建立关联不能仅按Name文本猜测归属。Consumer 类型决定处理属性的含义处理对象的属性要先按 WMI 类区分。NTEventLogEventConsumer用事件日志相关字段描述处理方式。LogFileEventConsumer用日志文件相关字段描述处理方式。CommandLineEventConsumer才会出现命令行模板。ActiveScriptEventConsumer则会涉及脚本引擎和脚本文本等属性。对象路径类似CommandLineEventConsumer.NameExample它是 WMI 实例定位符不是可执行文件路径。正确过程是用GetObject打开该路径读取__CLASS再按这一类处理对象的文档读取适用字段。未知类别应保留类名、对象路径和原始属性状态不能把每个 Consumer 都转换为命令文本。WMI 接口、BSTR 和 VARIANT 有各自的资源责任WMI 的 COM 自动化对象有明确的所有权。IWbemLocator、IWbemServices、IEnumWbemClassObject和IWbemClassObject都是 COM 接口指针。成功得到接口后持有者在使用结束时调用Release。BSTR是自动化字符串SysAllocString成功后由调用方SysFreeString。不能用delete[]或free释放。VARIANT是可保存字符串、整数、布尔值、接口或数组的联合类型。WMI 属性读取会把真实类型写入VARIANT。调用方先检查vt再读取相应成员然后调用VariantClear释放其中可能存在的 BSTR、接口或数组。把任何属性都强制转换为字符串会破坏数值和对象路径的边界。永久订阅配置与一次事件处理结果属于不同层次静态对象与事件执行记录要分开。完整读取 Filter、Binding 和 Consumer只说明在观察时刻这些 WMI 实例与对象路径可以关联。事件是否发生、WQL 是否匹配、处理对象是否成功处理以及处理产生什么结果需要从事件日志、WMI 活动记录或独立运行时证据确认。输出可明确分为三层配置层记录三类对象和引用关系。解析层记录类名和特有属性。运行时层记录事件时间、匹配结果和处理状态。任何层缺失都保留原始失败 HRESULT不用另一层的成功替代。每次读取还应记录命名空间路径、读取时间和调用账户上下文避免不同用户或不同远程连接得到的结果混在同一批记录中。二、第一步初始化 COM 与调用安全状态这一步让当前线程能够使用 COM 接口并为后续 WMI 服务调用准备默认安全状态。COMComponent Object Model组件对象模型是 Windows 中一套对象调用规则。WMI 客户端、定位器和服务接口都通过这套规则协作。初始化失败时不能继续读取。默认安全初始化可能返回RPC_E_TOO_LATE表示进程中的其它组件已经先设置了 COM 安全参数。此时保持已有设置继续连接目标命名空间。完成第一步后当前线程已经具备调用 WMI COM 接口的前提但还没有指向任何命名空间。接下来需要连接ROOT\subscription取得保存永久订阅对象的服务接口。三、第二步连接 ROOT\subscriptionFilter 和 Binding 位于永久订阅命名空间连接 WMI locator 的方法返回命名空间服务接口。ROOT\subscription保存永久事件订阅ROOT\CIMV2通常保存触发事件涉及的系统类。两个命名空间的用途不同。// 意义连接指定 WMI 命名空间。// 返回S_OK 成功。ppNamespace 接收 IWbemServices调用方 Release。HRESULTIWbemLocator::ConnectServer(BSTR strNetworkResource,BSTR strUser,BSTR strPassword,BSTR strLocale,LONG lSecurityFlags,BSTR strAuthority,IWbemContext*pCtx,IWbemServices**ppNamespace);// 意义设置连接后 WMI 服务代理的认证和模拟级别。// 返回S_OK 成功。失败 HRESULT 表示代理安全状态不可用。HRESULTCoSetProxyBlanket(IUnknown*pProxy,DWORD dwAuthnSvc,DWORD dwAuthzSvc,OLECHAR*pServerPrincName,DWORD dwAuthnLevel,DWORD dwImpLevel,RPC_AUTH_IDENTITY_HANDLE pAuthInfo,DWORD dwCapabilities);// 意义从宽字符文本创建 OLE 自动化分配器管理的 BSTR。// 返回成功时为新 BSTR内存不足时为 nullptr。需要下方释放函数。BSTRSysAllocString(constOLECHAR*psz);// 意义释放 SysAllocString 或 COM getter 返回的 BSTR。// 返回无。传入 nullptr 安全。voidSysFreeString(BSTR bstrString);正确做法以当前令牌连接ROOT\subscription再设置RPC_C_IMP_LEVEL_IMPERSONATE。连接返回服务接口后代理安全设置仍是独立步骤。BSTR pathSysAllocString(LROOT\\subscription);IWbemServices*servicesnullptr;if(SUCCEEDED(locator-ConnectServer(path,nullptr,nullptr,nullptr,WBEM_FLAG_CONNECT_USE_MAX_WAIT,nullptr,nullptr,services))){CoSetProxyBlanket(services,RPC_C_AUTHN_WINNT,RPC_C_AUTHZ_NONE,nullptr,RPC_C_AUTHN_LEVEL_CALL,RPC_C_IMP_LEVEL_IMPERSONATE,nullptr,EOAC_NONE);}SysFreeString(path);错误示例从ROOT\CIMV2查询__FilterToConsumerBinding。该类位于永久订阅命名空间错误命名空间会得到“类不存在”或空结果。完成第二步后已经获得只对应ROOT\subscription的IWbemServices服务接口。这个接口能查询永久订阅对象但关联的起点仍然未知。接下来先读取 Filter保留触发条件和稳定对象路径。四、第三步查询 __EventFilterFilter 保存触发条件本身执行 WQL 的方法返回枚举器。__EventFilter的Name是对象名称Query是触发 WQLQueryLanguage和EventNamespace说明查询解释方式与事件来源。// 意义执行只读 WQL 查询并返回对象枚举器。// 返回S_OK 成功。ppEnum 接收 IEnumWbemClassObject调用方 Release。HRESULTIWbemServices::ExecQuery(constBSTR strQueryLanguage,constBSTR strQuery,LONG lFlags,IWbemContext*pCtx,IEnumWbemClassObject**ppEnum);正确做法保留名称、相对路径、WQL、查询语言和事件命名空间。相对路径如__EventFilter.NameSCM Event Log Filter可与 Binding 的Filter引用直接对照。constwchar_t*queryLSELECT Name, Query, QueryLanguage, EventNamespace, __RELPATH LFROM __EventFilter;// 对每个结果分别保存 Name、Query、QueryLanguage、EventNamespace、__RELPATH。错误示例只保存 WQL。相同 WQL 在不同事件命名空间中的含义可能不同。缺少EventNamespace时无法确定查询的事件类来源。完成第三步后每个 Filter 都有名称、触发 WQL、事件命名空间和相对路径。此时只知道“什么事件会匹配”还不知道匹配后连接到哪个处理对象。接下来读取 Binding并逐条保留它的两个引用。五、第四步查询 Binding 并解析两端对象路径Binding 引用连接 Filter 与 ConsumerBinding 查询要保存两个原始引用。Filter指向一个__EventFilter对象。Consumer指向CommandLineEventConsumer、ActiveScriptEventConsumer、日志处理对象或其它处理对象。DeliveryQoS与MaintainSecurityContext是 Binding 自身属性。constwchar_t*queryLSELECT Filter, Consumer, DeliveryQoS, MaintainSecurityContext LFROM __FilterToConsumerBinding;// 对每个 Binding 保留 Filter 与 Consumer 的原始对象路径不用名称文本替代引用。正确做法通过服务接口读取引用对象。GetObject接受 WMI 对象路径。返回对象后读取__CLASS与Name。解析失败时保留原始引用。引用缺失本身也是读取结果。// 意义按 WMI 对象路径取得一个对象实例。// 返回S_OK 成功。ppObject 接收 IWbemClassObject调用方 Release。HRESULTIWbemServices::GetObject(constBSTR strObjectPath,LONG lFlags,IWbemContext*pCtx,IWbemClassObject**ppObject,IWbemCallResult**ppCallResult);IWbemClassObject*endpointnullptr;if(SUCCEEDED(services-GetObject(bindingConsumerPath,0,nullptr,endpoint,nullptr))){// 读取 endpoint 的 __CLASS 与 Name随后 Release。endpoint-Release();}错误示例把Consumer引用当成可执行文件路径。它通常是NTEventLogEventConsumer.Name...这类 WMI 对象路径需要先解析类和对象名。是否存在命令文本取决于处理对象类型。完成第四步后Filter、Binding 和 Consumer 已能通过对象路径对应起来。读取结果还需要处理超时、属性类型和资源释放才能区分空结果、等待超时和属性不可读。六、处理等待与属性资源枚举器需要有超时边界读取枚举器的方法每次最多等待指定毫秒数。WBEM_S_TIMEDOUT且没有对象时表示本次等待结束不等于类没有实例。连续超时达到上限后记录状态并继续其它类。// 意义从 WMI 枚举器读取对象。// 返回WBEM_S_NO_ERROR 成功。WBEM_S_TIMEDOUT 等待超时。HRESULTIEnumWbemClassObject::Next(LONG lTimeout,ULONG uCount,IWbemClassObject**apObjects,ULONG*puReturned);HRESULT statusenumerator-Next(5000,1,object,returned);if(statusWBEM_S_TIMEDOUTreturned0){// 增加超时次数。达到上限后结束这一类查询。}属性读取后要清理VARIANT。Filter 的 Query、Binding 的引用通常是 BSTR数值和布尔字段使用其它联合成员。读取后需要使用下方清理 API不能把所有属性强制转换成字符串指针。// 意义把 VARIANT 初始化为空值以便 WMI 属性方法写入。// 返回无。每个初始化后的 VARIANT 都需要清理内部资源。voidVariantInit(VARIANTARG*pvarg);// 意义释放 VARIANT 内部的 BSTR、接口、数组等资源。// 返回S_OK 成功。HRESULTVariantClear(VARIANTARG*pvarg);完成等待和属性处理后输出中的每个字段都有明确来源也能保留读取失败的边界。最后要保持三类对象各自的身份避免把配置关系误读为一次实际执行结果。七、保持三类对象的独立身份Filter、Binding 与 Consumer 不能合成一条记录对象建模要分别保存 Filter、Binding 和 Consumer。Filter 表示条件Consumer 表示动作Binding 表示关联。一个 Filter 可以连接多个 Consumer一个 Consumer 也可被多个 Binding 引用。配置与实际触发需要分别判断。读取到完整 Binding说明三类对象在读取时可以关联。实际事件是否发生、WQL 是否匹配和处理对象是否成功处理需要事件日志或其它独立证据确认。完整可运行程序在附件https://wangweicm.lanzouu.com/i5pgX3xfbzxg

本月热点