0%

C++使用WMI获取硬件信息

  1. 简介
  2. 常用功能函数
    1. 查询主板UUID
    2. 获取处理器ID
    3. 查询进程是否存在
  3. 在C++中创建WMI应用程序

简介

Windows Management Instrumentation

WMI全称Windows Management Instrumentation,是基于Windows的操作系统的数据管理和操作套件。用来获取系统硬件信息的Powershell命令wmic就是基于WMI的命令行工具,可以用如下命令获取相关硬件信息:

获取主板UUID

1
2
3
wmic csproduct get uuid
# UUID
# FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF

获取处理器ID

1
2
3
wmic cpu get processorid
# ProcessorId
# BFEBFBFF000906EA

获取硬盘序列号

1
2
3
4
5
6
wmic diskdrive get SerialNumber
# SerialNumber
# WD-WCC6Y2DN901N
# 1835A1802479
# WXM1AB78PVK9
# 0025_385B_01B1_1C84.

更多关于wmic的用法可以参考:Using the WMI Command-Line Tools

这篇博客的主要内容是如何通过C++调用WMI的相关接口以获取系统信息。

常用功能函数

关于如何创建WMI调用接口见后续章节,这边提供几个常用的查询系统信息的函数。这里面用到的ClassKeys作为标识符,属于Windows系统中的操作系统类(Operating System Classes)的名称,可以在微软官网查到完整列表:Operating System Classes - Win32 apps | Microsoft Docs

查询主板UUID

该UUID来自SMBIOS(System Management BIOS),可以作为主板设备的通用唯一标识符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
bool getUUID(std::string* uuid)
{
std::vector<std::string> keys;
keys.push_back("UUID");

std::vector<std::string> vals;

if (0 != GetWMIClassKeys("Win32_ComputerSystemProduct", keys, &vals, nullptr)) return false;

if (vals.size() == 1 && !vals[0].empty() && uuid != nullptr)
{
*uuid = vals[0];
return true;
}
else
{
return false;
}
}

对应的wmic指令:

1
wmic csproduct get uuid

获取处理器ID

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
bool getCPUID(std::string* cpuid)
{
std::vector<std::string> keys;
keys.push_back("ProcessorId");

std::vector<std::string> vals;

if (0 != GetWMIClassKeys("Win32_Processor", keys, &vals, nullptr)) return false;

if (vals.size() == 1 && !vals[0].empty() && cpuid != nullptr)
{
*cpuid = vals[0];
return true;
}
else
{
return false;
}
}

对应的wmic指令:

1
wmic cpu get processorid

查询进程是否存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bool findProcess(const std::string& processName)
{
std::vector<std::string> keys;
keys.push_back("Caption");
std::vector<std::string> vals;

if (0 != GetWMIClassKeys("Win32_Process", keys, &vals, nullptr)) return false;

for (auto itr = vals.begin(); itr != vals.end(); itr++)
{
if (itr->compare(processName) == 0) return true;
}
return false;
}

对应的wmic指令:

1
2
# powershell
wmic process get Caption | find /i `"chrome.exe`"
1
2
# cmd
wmic process get Caption | find /i "chrome.exe"

命令行工具也可以用tasklist

1
2
# cmd
tasklist | find /i "chrome.exe"

在C++中创建WMI应用程序

Creating a WMI Application Using C++

创建WMI应用程序的流程如以上教程所示,也可以在后面的实现代码查看一二。这边实现了一个通用的函数接口便于调用,主要功能为从WMI类中查询指定的字段属性。函数接口如下:

1
2
3
4
5
6
7
8
9
/// <summary>
/// 获取WMI类的成员属性值
/// </summary>
/// <param name="WMI_Class_Name">WMI类名</param>
/// <param name="Key_List">需要查询的键列表</param>
/// <param name="Value_List">得到的结果列表。按键的顺序交错排列</param>
/// <param name="Result">程序执行结果描述字符串</param>
/// <returns>返回值,0为成功,非0失败</returns>
int GetWMIClassKeys(const std::string& WMI_Class_Name, const std::vector<std::string>& Key_List, std::vector<std::string>* Value_List, std::string* Result);

该函数通过WMI_Class_Name指定要查询的WMI类名,完整类名参考:Win32 Provider

通过Key_List指定要查询的属性列表,输出结果位于Value_List中,如果指定了多个键且查询结果包含多条数据,则Value_List根据Key_List的位置交错排列;

Result是输出结果指针,用来存放报错信息,允许为空指针,若程序执行顺利则不会有内容。

一个调用示例如下所示:

1
2
3
4
5
6
7
8
/*
* 获取系统进程列表
*/
std::vector<std::string> keys;
keys.push_back("Caption");
keys.push_back("CommandLine");
std::vector<std::string> vals;
if (0 != GetWMIClassKeys("Win32_Process", keys, &vals, nullptr)) return false;

image-20211226122931710

函数的完整实现如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
#include <vector>
#include <string>
#include <sstream>

#include <comdef.h>
#include <Wbemidl.h>

#pragma comment(lib, "wbemuuid.lib")

std::wstring s2ws(const std::string& s)
{
int len;
int slen = static_cast<int>(s.length()) + 1;
len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slen, NULL, 0);
wchar_t* buf = new wchar_t[len];
MultiByteToWideChar(CP_ACP, 0, s.c_str(), slen, buf, len);
std::wstring ws(buf);
delete[] buf;
return ws;
}

std::string ws2s(const std::wstring& ws)
{
int len;
int slen = static_cast<int>(ws.length()) + 1;
len = WideCharToMultiByte(CP_ACP, 0, ws.c_str(), slen, NULL, 0, NULL, NULL);
char* buf = new char[len];
WideCharToMultiByte(CP_ACP, 0, ws.c_str(), slen, buf, len, NULL, NULL);
std::string s(buf);
delete[] buf;
return s;
}

int GetWMIClassKeys(const std::string& WMI_Class_Name, const std::vector<std::string>& Key_List, std::vector<std::string>* Value_List, std::string* Result)
{
if (Value_List == nullptr)
{
if (Result != nullptr)
{
*Result = "结果列表指针为空";
}
return 1;
}

HRESULT hres;

// Step 1: --------------------------------------------------
// Initialize COM. ------------------------------------------
// 初始化 COM -----------------------------------------------

hres = CoInitializeEx(0, COINIT_MULTITHREADED);

// 是否释放初始化。在该函数中初始化成功且设置安全等级成功后(默认情况),需要释放初始化
// 如果初始化失败或者设置安全等级失败,则无需释放(说明在其他地方初始化过了,在这里不需要额外释放)
bool bUnInit = true;
if (FAILED(hres))
{
bUnInit = false;
//if (Result != nullptr)
//{
// std::stringstream ss;
// ss << "初始化COM失败,错误代码:0x"
// << std::hex << hres << std::endl;
// *Result = ss.str();
//}

//return 1; // Program has failed.
}

// Step 2: --------------------------------------------------
// Set general COM security levels --------------------------
// 设置通用COM安全等级 --------------------------------------

hres = CoInitializeSecurity(
NULL,
-1, // COM authentication
NULL, // Authentication services
NULL, // Reserved
RPC_C_AUTHN_LEVEL_DEFAULT, // Default authentication
RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation
NULL, // Authentication info
EOAC_NONE, // Additional capabilities
NULL // Reserved
);


if (FAILED(hres))
{
bUnInit = false;
//if (Result != nullptr)
//{
// std::stringstream ss;
// ss << "设置安全级别失败,错误代码:0x"
// << std::hex << hres << std::endl;
// *Result = ss.str();
//}
//CoUninitialize();
//return 1; // Program has failed.
}

// Step 3: ---------------------------------------------------
// Obtain the initial locator to WMI -------------------------
// 获取 WMI 的初始定位器 -------------------------------------

IWbemLocator* pLoc = NULL;

hres = CoCreateInstance(
CLSID_WbemLocator,
0,
CLSCTX_INPROC_SERVER,
IID_IWbemLocator, (LPVOID*)&pLoc);

if (FAILED(hres))
{
if (Result != nullptr)
{
std::stringstream ss;
ss << "创建IWbemLocator对象失败,错误代码:0x"
<< std::hex << hres << std::endl;
*Result = ss.str();
}
if (bUnInit)
{
CoUninitialize();
}
return 1; // Program has failed.
}

// Step 4: -----------------------------------------------------
// Connect to WMI through the IWbemLocator::ConnectServer method
// 通过IWbemLocator::ConnectServer()方法连接到WMI --------------

IWbemServices* pSvc = NULL;

// Connect to the root\cimv2 namespace with
// the current user and obtain pointer pSvc
// to make IWbemServices calls.
hres = pLoc->ConnectServer(
_bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace
NULL, // User name. NULL = current user
NULL, // User password. NULL = current
0, // Locale. NULL indicates current
NULL, // Security flags.
0, // Authority (for example, Kerberos)
0, // Context object
&pSvc // pointer to IWbemServices proxy
);

if (FAILED(hres))
{
if (Result != nullptr)
{
std::stringstream ss;
ss << "连接WMI失败,错误代码:0x"
<< std::hex << hres << std::endl;
*Result = ss.str();
}
pLoc->Release();
if (bUnInit)
{
CoUninitialize();
}
return 1; // Program has failed.
}

// cout << "Connected to ROOT\\CIMV2 WMI namespace" << endl;


// Step 5: --------------------------------------------------
// Set security levels on the proxy -------------------------
// 在代理商设置安全级别 -------------------------------------

hres = CoSetProxyBlanket(
pSvc, // Indicates the proxy to set
RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx
RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx
NULL, // Server principal name
RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx
RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
NULL, // client identity
EOAC_NONE // proxy capabilities
);

if (FAILED(hres))
{
if (Result != nullptr)
{
std::stringstream ss;
ss << "CoSetProxyBlanket()无法设置代理安全等级,错误代码:0x"
<< std::hex << hres << std::endl;
*Result = ss.str();
}
pSvc->Release();
pLoc->Release();
if (bUnInit)
{
CoUninitialize();
}
return 1; // Program has failed.
}

// Step 6: --------------------------------------------------
// Use the IWbemServices pointer to make requests of WMI ----
// 使用IWbemServices时针创建WMI请求 -------------------------

// For example, get the name of the operating system
IEnumWbemClassObject* pEnumerator = NULL;
hres = pSvc->ExecQuery(
bstr_t("WQL"),
bstr_t((std::string("SELECT * FROM ") + WMI_Class_Name).c_str()),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&pEnumerator);

if (FAILED(hres))
{
if (Result != nullptr)
{
std::stringstream ss;
ss << "WQL查询失败,错误代码:0x"
<< std::hex << hres << std::endl;
*Result = ss.str();
}
pSvc->Release();
pLoc->Release();
if (bUnInit)
{
CoUninitialize();
}
return 1; // Program has failed.
}

// Step 7: -------------------------------------------------
// Get the data from the query in step 6 -------------------
// 获取上一步的查询结果 ------------------------------------

Value_List->clear();
IWbemClassObject* pclsObj = NULL;
ULONG uReturn = 0;

// 循环针对WQL查询存在多个结果的情况,比如存在许多行的表
// 或者某个类的查询结果有多条(如进程)
// 查询结果根据键的顺序交错排列
while (pEnumerator)
{
HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1,
&pclsObj, &uReturn);

if (0 == uReturn)
{
break;
}

for (auto itr = Key_List.begin(); itr != Key_List.end(); itr++)
{
VARIANT vtProp;

// Get the value of the Name property
//hr = pclsObj->Get(L"UUID", 0, &vtProp, 0, 0);
hr = pclsObj->Get(s2ws(*itr).c_str(), 0, &vtProp, 0, 0);
if (FAILED(hr))
{
Value_List->push_back("");
}
else
{
if (vtProp.vt == 3)
{
Value_List->push_back(std::to_string(vtProp.lVal));
}
else // if (vtProp.vt == 8)
{
Value_List->push_back(ws2s(vtProp.bstrVal));
}
}
VariantClear(&vtProp);
}
pclsObj->Release();
}

// Cleanup
// ========

pSvc->Release();
pLoc->Release();
pEnumerator->Release();
if (bUnInit)
{
CoUninitialize();
}

return 0;
}