在《WMI技术介绍和应用——WMI概述》中,我们使用了下图介绍WMI构架(转载请指明出于breaksoftware的csdn博客)
我们之前介绍的使用WMI查询系统、硬件等信息的功能,是通过查询WMI静态数据的空间实现的。这个功能的核心是在上图中2,即WMI Infrastructure层实现的。本文将让我们对WMI的认识深入到1,即WMI Providers and Managed Objects层。具体的功能是使用WMI检测到WMI数据和服务的变化。再具体一点就是,我们可以使用WMI检测进程创建、服务状态变化、电脑状态变化,磁盘可用空间变化等信息。这些都是非常让人激动的技术,我想做过安全的朋友应该清楚,如果想全局监控系统中进程的创建,除了下驱动就是使用Hook技术。如果你只是想知道哪些进程创建这样轻量级的需求,也要使用驱动、Hook这种重量级的技术,是否感觉有点得不偿失?而WMI就给我们提供了这样的一种轻量级的解决方案。
上图的WMI Providers and Managed Objects(托管对象和WMI提供者)层中,我们将使用Provider帮我们检测WMI变化。需要注意的一点是,并不是所有的Provider都可以为我们提供事件通知——只有WMI Event Class的托管对象才会在事件发生时给我们提供通知。我们收到的事件,可能来源于两种事件,一种是intrinsic event,即内在事件;还有一种是extrinsic event,即外来事件。内在事件是在标准的WMI数据模型发生改变而产生的事件,这将是我们介绍的重点。外来事件,和内在事件相对,即非标准WMI数据数据模型发生改变而产生的事件。
介绍了这么多基础知识了,那如何查询事件通知呢?在《WMI技术介绍和应用——使用VC编写一个半同步查询WMI服务的类》中,我们讲解WMI查询静态数据时,我们可以使用同步查询和半同步查询两种查询方式。像静态数据,正如其名,它是静态的,即它存在就存在,不存在即不存在,所以我们可以使用同步方式查询。半同步其实就是一个伪装的异步操作,我们在那篇文章中已经做了介绍,本文不再赘述。而本文主要讲解的查询事件通知,它是动态发生的。即可能我查询的即刻,那个事件还未发生,我们需要等待一段时间,才会在事件发生后接收到通知。这就意味着查询事件通知,是不可能使用同步查询方式,我们可以选择异步查询或者半同步查询方式。
作为查询的载体——事件使用者(Event Consumers),也是分为两种:临时事件使用者和永久事件使用者。
临时事件使用者是我们未来最早接触到的一个使用者,顾名思义,它是指WMI接收事件通知的生命周期和发起查询的应用程序一致。WMI包含一个统一的接口用来向客户端应用程序提供WMI事件。
永久事件使用者是一种更复杂的使用者——它是一个COM对象,用于持续接收WMI事件通知。它使用一些现有的对象和过滤器去获取WMI事件。我们可以设置一些WMI对象和过滤器去获取WMI事件。当一个事件发生,并命中过滤器,WMI将加载永久事件使用者并通知它某事件发生了。或许你会有点好奇,永久事件使用者是保存在什么地方?WMI又是如何找到它的?永久事件使用者是保存在WMI仓库中(上图2层中WMI repository),并且是一个在WMI中注册的可执行文件,这样WMI便可以方便的寻找和加载它了。
这些事件都是由事件提供者(An event provider)发送给WMI的。它也是个COM组件。我们可以使用C++或者C#编写事件提供者程序。大部分事件提供者管理着一个WMI对象。对于如何编写WMI事件提供者,我们会在之后介绍。
我们再回到查询事件通知,首先我们要编写一个异步事件查询类。因为连接空间等操作和之前的都相同,所以我们的查询类也是继承于《WMI技术介绍和应用——VC开发WMI应用的基本步骤》介绍的CWMI类
template<typename T>
class CAsynNotifyQuery : public CWMI
{
public:
CAsynNotifyQuery(const wstring& wszNamespace, const wstring& wszWQLQuery, HANDLE hExitEvent);
~CAsynNotifyQuery(void);
private:
HRESULT Excute(CComPtr<IWbemServices> pSvc);
private:
wstring m_wszWQLQuery;
HANDLE m_hExitHandle;
};
CAsynNotifyQuery是一个模板类,这是区别于我们之前的其他类。我们再来关注下主要方法——Excute方法的实现
template<typename T>
HRESULT CAsynNotifyQuery<T>::Excute( CComPtr<IWbemServices> pSvc )
{
HRESULT hr = WBEM_S_FALSE;
do {
CComPtr<IUnsecuredApartment> pUnsecApp = NULL;
hr = CoCreateInstance( CLSID_UnsecuredApartment, NULL,
CLSCTX_LOCAL_SERVER, IID_IUnsecuredApartment, (void**) &pUnsecApp);
CHECKWMIHR(hr);
CComPtr<IWbemObjectSink> pSink = new T;
CComPtr<IUnknown> pStubUnk = NULL;
pUnsecApp->CreateObjectStub(pSink, &pStubUnk);
CComPtr<IWbemObjectSink> pStubSink = NULL;
pStubUnk->QueryInterface(IID_IWbemObjectSink, (void**)&pStubSink);
hr = pSvc->ExecNotificationQueryAsync(
CComBSTR("WQL"),
CComBSTR(m_wszWQLQuery.c_str()),
WBEM_FLAG_SEND_STATUS,
NULL,
pStubSink );
CHECKWMIHR(hr);
if ( NULL != m_hExitHandle ) {
WaitForSingleObject(m_hExitHandle, INFINITE );
}
hr = pSvc->CancelAsyncCall(pStubSink);
if ( NULL != pSink ) {
delete pSink;
pSink = NULL;
}
} while (0);
return hr;
}
首先我们需要创建一个IUnsecuredApartment接口实例。该接口是客户进程发起异步调用的,它提供了一个CreateObjectStub方法创建一个桩,WMI将在异步执行过程中对该桩进行操作。我们再看下传入的模板类的定义
class CSink : public IWbemObjectSink
{
public:
CSink();
~CSink();
virtual ULONG STDMETHODCALLTYPE AddRef();
virtual ULONG STDMETHODCALLTYPE Release();
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppv);
virtual HRESULT STDMETHODCALLTYPE Indicate(LONG lObjectCount, IWbemClassObject __RPC_FAR* __RPC_FAR* apObjArray);
virtual HRESULT STDMETHODCALLTYPE SetStatus(LONG lFlags, HRESULT hResult, BSTR strParam, IWbemClassObject __RPC_FAR* pObjParam);
HRESULT DealIWbemClassObject(CComPtr<IWbemClassObject> pObj);
private:
// 返回值为WBEM_S_NO_ERROR则继续枚举,否则中断枚举
virtual HRESULT DealWithSingleItem(CComBSTR bstrName, CComVariant Value, CIMTYPE type, LONG lFlavor);
private:
LONG m_lRef;
bool m_bDone;
};
IWbemObjectSink接口用于接收WMI事件。我们主要需要实现Indicate方法,WMI框架将调用这个方法把消息实例传递给我们。从这个函数的最后一个参数可以看出,它传递过来的是一个事件数组
HRESULT STDMETHODCALLTYPE CSink::Indicate( LONG lObjectCount, IWbemClassObject __RPC_FAR* __RPC_FAR* apObjArray )
{
for (long i = 0; i < lObjectCount; i++) {
CComPtr<IWbemClassObject> pObj = apObjArray[i];
DealIWbemClassObject(pObj);
}
return WBEM_NO_ERROR;
}
DealWbemClassObject是我自己定义和实现的方法,其主要功能就是输出事件的内容。 我们可以使用下例去调用查询操作
HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
CAsynNotifyQuery<CInstanceEvent> recvnotify(L"root\\CIMV2", L"SELECT * FROM __InstanceModificationEvent WITHIN 1 WHERE TargetInstance ISA 'Win32_Process'", hEvent);
recvnotify.ExcuteFun();
更多事件查询的例子,可以查看《WMI技术介绍和应用——接收事件》一文。
工程源码见《WMI技术介绍和应用——WMI概述》结尾。