编程使用WMI

Birty

发布日期: 2018-11-30 21:25:55 浏览量: 865
评分:
star star star star star star star star star_border star_border
*转载请注明来自write-bug.com

背景

WMI出现至今已经二十多年了,但很多人对它并不熟悉。知道它很好很强大,但不知道它从哪里来,怎么工作,使用范围是什么?

  WMI有一组API。我们不管使用VBScript、PowerShell脚本还是利用C#的来访问WMI的类库,都是因为WMI向外暴露的一组API。这些API是在系统安装WMI模块的时候安装的,通过他们我们能够能拿到我们想要的类。

  WMI有一个存储库。尽管WMI的多数实例数据都不存储在WMI中,但是WMI确实有一个存储库,用来存放提供程序提供的类信息,或者称为类的蓝图或者Schema。

  WMI有一个Service。WMI总是能够响应用户的访问,那是因为它有一个一直运行的Windows服务,名字叫Winmgmt。停止这个服务,所有对WMI的操作都将没有反应。

  WMI是可扩展的。人人都知道WMI能干很多事情,读取本机硬盘信息、读取远程计算机的用户信息、读取域用户信息等等。基本上,你能想到的获取或者更改资源的操作,它都能干。可谓吃得少,干得多。它为什么这么能干呢?这基于WMI的可扩展性。WMI对资源的操作,不是它自己实现了什么方法,而完全取决于向它注册的提供程序。

  WMI是管理员日常必备的强大工具之一,是脚本伴侣。当然也可以把一个大型系统建立在WMI以及WMI的提供程序之上。

WMI的全称是Windows Management Instrumentation,即Windows管理工具。它是Windows操作系统中管理数据和操作的基础模块。我们可以通过WMI脚本或者应用程序去管理本地或者远程计算机上的资源。对于VC和汇编程序员,想获取诸如CPU序列号和硬盘序列号等信息是非常容易的。但是对于VB以及其他一些脚本语言,想尝试获取系统中一些硬件信息可能就没那么容易了。微软为了能达到一种通用性目的(遵守某些行业标准),设计了WMI。它提供了一个通过操作系统、网络和企业环境去管理本地或远程计算机的统一接口集。应用程序和脚本语言使用这套接口集去完成任务,而不是直接通过Windows API。可能有人要问,为什么不让设计的脚本直接在底层使用Windows API,而非要弄个新的技术呢?原因是在目前Windows API中,有些是不支持远程调用或者脚本调用的。这样通过统一模型的WMI,像VB和脚本语言就可以去访问部分系统信息了。但是并不是所有脚本语言都可以使用WMI技术:它要支持ActiveX技术。

WMI通常是被脚本所调用的,不过也对。对于WMI能做的操作,我们也完全可以通过VS去调用Win32 API去实现。但是,本文要讲的是使用VS调用WMI提供的接口去获取系统的信息。那么,VS中怎么使用WMI呢?接下来,我就把我所了解的知识分享给大家。

函数介绍

CoInitializeEx 函数

为当前线程初始化COM库并设置并发模式 。

函数声明

  1. HRESULT CoInitializeEx(
  2. void * pvReserved,
  3. DWORD dwCoInit
  4. );

参数

  • pvReserved
    系统 保留的参数,必须传入 NULL。
  • dwCoInit
    该标示指明基于当前线程的并发模式和初始化选项。该参数是 COINIT 枚举类型,传入参数时候,除了COINIT_APARTMENTTHREADED 和COINIT_MULTITHREAD ED标记外,其余的标记可以组合使用。

返回值

  • S_OK :COM库初始化成功。
  • S_FALSE :当前线程上,COM库已经被初始化。
  • RPC_E_CHANGED_MODE :COM库已经被初始化且传入参数设置的并发模式和本次不同。

CoInitializeSecurity 函数

注册安全性并设置进程的默认安全性值。

函数声明

  1. HRESULT CoInitializeSecurity(
  2. _In_opt_ PSECURITY_DESCRIPTOR pSecDesc,
  3. _In_ LONG cAuthSvc,
  4. _In_opt_ SOLE_AUTHENTICATION_SERVICE *asAuthSvc,
  5. _In_opt_ void *pReserved1,
  6. _In_ DWORD dwAuthnLevel,
  7. _In_ DWORD dwImpLevel,
  8. _In_opt_ void *pAuthList,
  9. _In_ DWORD dwCapabilities,
  10. _In_opt_ void *pReserved3
  11. );

参数

  • pSecDesc [in]
    服务器将用于接收呼叫的访问权限。
  • cAuthSvc [in]
    asAuthSvc参数中的条目计数。只有当服务器调用CoInitializeSecurity时,此参数才被COM使用。如果此参数为0,则不会注册认证服务,并且服务器无法接收安全呼叫。值为-1表示COM选择要注册的身份验证服务,如果是这种情况,则asAuthSvc参数必须为NULL。但是,如果参数为-1,则服务器将不会选择Schannel作为身份验证服务。
  • asAuthSvc [in]
    一组服务器愿意用来接收呼叫的认证服务。
  • pReserved1 [in]
    此参数是保留的,必须为NULL。
  • dwAuthnLevel [in]
    进程的默认身份验证级别。
  • dwImpLevel [in]
    代理的默认模拟级别。此参数的值仅在进程为客户端时使用。
  • pAuthList [in]
    指向SOLE_AUTHENTICATION_LIST的指针,它是一个SOLE_AUTHENTICATION_INFO结构的数组。
  • dwCapabilities [in]
    通过设置一个或多个EOLE_AUTHENTICATION_CAPABILITIES值指定的客户端或服务器的附加功能。
  • pReserved3 [in]
    此参数是保留的,必须为NULL。

返回值

  • S_OK :COM库初始化成功。
  • RPC_E_TOO_LATE:CoInitializeSecurity 已经被调用。
  • E_OUT_OF_MEMORY:内存不足。

CoCreateInstance 函数

用指定的类标识符创建一个COM对象,用指定的类标识符创建一个未初始化的对象。

函数声明

  1. STDAPI CoCreateInstance(
  2. REFCLSID rclsid, //创建的Com对象的类标识符(CLSID)
  3. LPUNKNOWN pUnkOuter, //指向接口IUnknown的指针
  4. DWORD dwClsContext, //运行可执行代码的上下文
  5. REFIID riid, //创建的Com对象的接口标识符
  6. LPVOID * ppv //用来接收指向Com对象接口地址的指针变量
  7. );

参数

  • rclsid
    [in] 用来唯一标识一个对象的CLSID(128位),需要用它来创建指定对象。
  • pUnkOuter
    [in] 如果为NULL, 表明此对象不是聚合式对象一部分。如果不是NULL, 则指针指向一个聚合式对象的IUnknown接口。
  • dwClsContext
    [in] 组件类别. 可使用CLSCTX枚举器中预定义的值。
  • riid
    [in] 引用接口标识符,用来与对象通信。
  • ppv
    [out] 用来接收指向接口地址的指针变量。如果函数调用成功,*ppv包括请求的接口指针。

返回值

  • S_OK:指定的Com对象实例被成功创建。
  • REGDB_E_CLASSNOTREG:指定的类没有在注册表中注册. 也可能是指定的dwClsContext没有注册或注册表中的服务器类型损坏。
  • CLASS_E_NOAGGREGATION:这个类不能创建为聚合型。
  • E_NOINTERFACE:指定的类没有实现请求的接口, 或者是IUnknown接口没有暴露请求的接口。

CoSetProxyBlanket 函数

设置将用于在指定代理上进行呼叫的认证信息。

函数声明

  1. HRESULT CoSetProxyBlanket(
  2. _In_ IUnknown *pProxy,
  3. _In_ DWORD dwAuthnSvc,
  4. _In_ DWORD dwAuthzSvc,
  5. _In_opt_ OLECHAR *pServerPrincName,
  6. _In_ DWORD dwAuthnLevel,
  7. _In_ DWORD dwImpLevel,
  8. _In_opt_ RPC_AUTH_IDENTITY_HANDLE pAuthInfo,
  9. _In_ DWORD dwCapabilities
  10. );

参数

  • pProxy [in]
    要设置的代理。
  • dwAuthnSvc [in]
    要使用的身份验证服务。
  • dwAuthzSvc [in]
    要使用的授权服务。
  • pServerPrincName [in]
    要与身份验证服务一起使用的服务器主体名称。
  • dwAuthnLevel [in]
    要使用的认证级别。
  • dwImpLevel [in]
    要使用的模拟级别。
  • pAuthInfo [in]
    指向建立客户端身份的RPC_AUTH_IDENTITY_HANDLE值的指针。由句柄引用的结构的格式取决于认证服务的提供者。
  • dwCapabilities [in]
    这个代理的功能。

返回值

  • S_OK:执行成功。
  • E_INVALIDARG:一个或者多个参数无效。

实现原理

现在,我们来解析下在VS2013中使用WMI的原理过程:

  • 首先,我们要使用CoInitializeEx初始化COM组件环境。因为WMI是基于COM组件实现的,使用COM组件前,必须要进行初始化操作。

  • 然后,使用CoInitializeSecurity注册安全性并设置进程的默认安全性值。

  • 接着,使用CoCreateInstance创建一个IWbemLocator的COM对象。

  • 跟着,通过IWbemLocator::ConnectServer函数连接到WMI,并获取IWbemServices指针。

  • 然后,使用CoSetProxyBlanket设置连接的安全级别。

  • 接着,使用IWbemServices指针发出WMI请求,执行WQL语句。

  • 然后,从查询返回集中获取获取返回数据。

  • 进行清理工作。

其中,WQL其实非常简单,它有如下特点:

  1. SELECT 属性名 FROM 类名

每个WQL语句必须以SELECT开始,SELECT后跟你需要查询的属性名,也可以像SQL一样,以*表示返回所有属性值。然后,FROM关键字,即你要查询的类的名字。

本文使用的WQL语句是:SELECT * FROM Win32_Process。

编码实现

加载WMI所需的库文件

  1. #include <comdef.h>
  2. #include <WbemIdl.h>
  3. #pragma comment(lib, "wbemuuid.lib")

调用WIM获取进程信息

  1. int WMI_EnumProcess()
  2. {
  3. /*******************************************/
  4. // 初始化操作 //
  5. /*******************************************/
  6. // 初始化COM组件
  7. HRESULT hRes = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
  8. if (FAILED(hRes))
  9. {
  10. printf("CoInitializeEx Error[%d]\n", hRes);
  11. return 1;
  12. }
  13. // 设置进程的安全级别
  14. hRes = ::CoInitializeSecurity(NULL,
  15. -1,
  16. NULL,
  17. NULL,
  18. RPC_C_AUTHN_LEVEL_DEFAULT,
  19. RPC_C_IMP_LEVEL_IMPERSONATE,
  20. NULL,
  21. EOAC_NONE,
  22. NULL);
  23. if (FAILED(hRes))
  24. {
  25. printf("CoInitializeSecurity Error[%d]\n", hRes);
  26. return 2;
  27. }
  28. /*******************************************/
  29. // 创建一个WMI命名空间连接 //
  30. /*******************************************/
  31. // 创建一个CLSID_WbemLocation对象
  32. IWbemLocator *pIWbemLocator = NULL;
  33. hRes = ::CoCreateInstance(CLSID_WbemLocator,
  34. NULL,
  35. CLSCTX_INPROC_SERVER,
  36. IID_IWbemLocator,
  37. (LPVOID *)(&pIWbemLocator));
  38. if (FAILED(hRes))
  39. {
  40. printf("CoCreateInstance Error[%d]\n", hRes);
  41. return 3;
  42. }
  43. // 使用 pIWbemLocator 连接到 "root\cimv2", 并获取 pIWbemServices
  44. IWbemServices *pIWbemServices = NULL;
  45. hRes = pIWbemLocator->ConnectServer(_bstr_t(L"ROOT\\CIMV2"),
  46. NULL,
  47. NULL,
  48. NULL,
  49. 0,
  50. NULL,
  51. NULL,
  52. &pIWbemServices);
  53. if (FAILED(hRes))
  54. {
  55. printf("pIWbemLocator->ConnectServer Error[%d]\n", hRes);
  56. return 4;
  57. }
  58. /*******************************************/
  59. // 设置连接的安全级别 //
  60. /*******************************************/
  61. // 设置连接的安全级别
  62. hRes = ::CoSetProxyBlanket(pIWbemServices,
  63. RPC_C_AUTHN_WINNT,
  64. RPC_C_AUTHN_NONE,
  65. NULL,
  66. RPC_C_AUTHN_LEVEL_CALL,
  67. RPC_C_IMP_LEVEL_IMPERSONATE,
  68. NULL,
  69. EOAC_NONE);
  70. if (FAILED(hRes))
  71. {
  72. printf("CoSetProxyBlanket Error[%d]\n", hRes);
  73. return 5;
  74. }
  75. /*******************************************/
  76. // 执行WQL查询代码 //
  77. /*******************************************/
  78. // 这里是列出正在运行进程的例子
  79. // 为了接收结果, 你必须定义一个枚举对象
  80. IEnumWbemClassObject *pIEnumWbemClassObject = NULL;
  81. hRes = pIWbemServices->ExecQuery(bstr_t("WQL"),
  82. bstr_t("SELECT * FROM Win32_Process"), // 类似数据库中的 SQL 语句
  83. WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
  84. NULL,
  85. &pIEnumWbemClassObject);
  86. if (FAILED(hRes))
  87. {
  88. printf("pIWbemServices->ExecQuery Error[%d]\n", hRes);
  89. return 6;
  90. }
  91. // 显示查询结果
  92. IWbemClassObject *pIWbemClassObject = NULL;
  93. ULONG ulRet = 0;
  94. VARIANT varProp = { 0 };
  95. while (pIEnumWbemClassObject)
  96. {
  97. // 获取下一个对象
  98. hRes = pIEnumWbemClassObject->Next(WBEM_INFINITE,
  99. 1,
  100. &pIWbemClassObject,
  101. &ulRet);
  102. // 判断获取完毕结束
  103. if (0 == ulRet)
  104. {
  105. break;
  106. }
  107. // 获取 ProcessId 字段的值 并 显示
  108. ::VariantClear(&varProp);
  109. pIWbemClassObject->Get(L"ProcessId", 0, &varProp, NULL, NULL);
  110. printf("[%d]\t", varProp.intVal);
  111. // 获取 Name 字段的值 并 显示
  112. ::VariantClear(&varProp);
  113. pIWbemClassObject->Get(L"Name", 0, &varProp, NULL, NULL);
  114. printf("[%ws]\n", varProp.bstrVal);
  115. }
  116. /*******************************************/
  117. // 清理释放 //
  118. /*******************************************/
  119. // 释放
  120. ::VariantClear(&varProp);
  121. pIWbemServices->Release();
  122. pIWbemLocator->Release();
  123. ::CoUninitialize();
  124. return 0;
  125. }

程序测试

在 main 函数中调用上述封装好的函数进行测试,main 函数为:

  1. int _tmain(int argc, _TCHAR* argv[])
  2. {
  3. // 调用 WMI 去查询进程信息
  4. WMI_EnumProcess();
  5. system("pause");
  6. return 0;
  7. }

测试结果:

运行程序,进程信息成功显示。所以测试成功。

总结

在VS中使用WMI,流程总是这么几步。要查询其他信息,也只是更改下WQL查询语句以及查询结果的显示而已。如果你学过数据库,或者了解过数据库相关的知识的话,你会发现WQL查询语句和数据SQL语句是很相似的。那是因为,我们之前提到的,WMI也有自己的数据库,也会在数据库里存储一些信息。

参考文档

WMI入门(一):什么是WMI

WMI技术介绍和应用——WMI概述

Windows黑客编程技术详解》一书

上传的附件 cloud_download WMI_Test.7z ( 138.34kb, 5次下载 )

发送私信

只愿岁月静好,别无所求

20
文章数
13
评论数
最近文章
eject