windows平台通用获取硬件信息

windows平台通用获取硬件信息

前言

   本次项目需求之一是获取从xp到win10的所有系统版本的硬件信息。windows常用获取硬件信息的方法都是通过控制台执行wmic命令,然后解析输出获取对应的硬件信息,例如获取硬盘信息:

1
wmic diskdrive get model,serialnumber

但这种方法存在局限性,在xp上控制台无法交互,也就是说虽然wmic可以获取到结果,但是外部进程无法通过进程通信获取返回结果。

解决方案

  通过msdn查询wmic相关信息,发现wmic也是调用wmi的com接口实现的,所以直接使用wmi的com接口就可以避免进程间通信,直接获取结果。为了使用方便,我简单封装了一下这个接口的调用,代码如下:

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
BOOL ManageWMIInterface(const BSTR &querySql, const QList<std::wstring> ¶mList, QStringList &resultList)

{
HRESULT hres;
// Step 1: 初始化COM
// hres = CoInitializeEx(0, COINIT_MULTITHREADED); //这种方式初始化错误
hres = CoInitialize(0);

if (FAILED(hres)) {
qWarning() << "Failed to initialize COM library. Error code = 0x"
<< hex << hres << endl;
return false; // Program has failed.
}
// Step 2: 设置COM的安全认证级别

//在实际使用过程中,我发现如果这一步不注释掉的话,程序总是返回false,注释掉之后程序反而可以正常运行,原因未知

// Note: If you are using Windows 2000, you need to specify -
// the default authentication credentials for a user by using
// a SOLE_AUTHENTICATION_LIST structure in the pAuthList ----
// parameter of CoInitializeSecurity ------------------------
// hres = CoInitializeSecurity(
// NULL,
// -1, // COM negotiates service
// NULL, // Authentication services
// NULL, // Reserved
// RPC_C_AUTHN_LEVEL_DEFAULT, // authentication
// RPC_C_IMP_LEVEL_IMPERSONATE, // Impersonation
// NULL, // Authentication info
// EOAC_NONE, // Additional capabilities
// NULL // Reserved
// );

// if (FAILED(hres)) {
// cout << "Failed to initialize security. Error code = 0x"
// << hex << hres << endl;
// CoUninitialize();
// return false; // Program has failed.
// }

// Step 3: 获得WMI连接COM接口
CLSID CLSID_WbemLocator = {0x4590F811, 0x1D3A, 0x11D0, {0x89, 0x1F, 0, 0xAA, 0, 0x4B, 0x2E, 0x24}};
IWbemLocator *pLoc = nullptr;
hres = CoCreateInstance(
CLSID_WbemLocator,
nullptr,
CLSCTX_INPROC_SERVER,
IID_IWbemLocator, (LPVOID *)&pLoc);

if (FAILED(hres)) {
qWarning() << "Failed to create IWbemLocator object."
<< " Err code = 0x"
<< hex << hres << endl;
CoUninitialize();
return false; // Program has failed.
}


// Step 4: 通过连接接口连接WMI的内核对象名"ROOT//CIMV2"
IWbemServices *pSvc = nullptr;

hres = pLoc->ConnectServer(

_bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace
NULL, // User name. nullptr = current user
NULL, // User password. nullptr = current
0, // Locale. nullptr indicates current
NULL, // Security flags.
0, // Authority (e.g. Kerberos)
0, // Context object
&pSvc // pointer to IWbemServices proxy
);

if (FAILED(hres)) {
qWarning() << "Could not connect. Error code = 0x"
<< hex << hres << endl;
pLoc->Release();
CoUninitialize();
return false; // Program has failed.
}


// Step 5: 设置请求代理的安全级别
hres = CoSetProxyBlanket(
pSvc, // Indicates the proxy to set
RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx
RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx
nullptr, // Server principal name
RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx
RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
nullptr, // client identity
EOAC_NONE // proxy capabilities
);
if (FAILED(hres)) {
qWarning() << "Could not set proxy blanket. Error code = 0x"
<< hex << hres << endl;
pSvc->Release();
pLoc->Release();
CoUninitialize();
return false; // Program has failed.
}
// Step 6: 通过请求代理来向WMI发送请求----
// For example, get the name of the operating system
IEnumWbemClassObject *pEnumerator = nullptr;
BSTR wql = SysAllocString(L"WQL");
hres = pSvc->ExecQuery(
wql,


querySql,//只需要通过修改这里的查询语句,就可以实现对MAC地址等其他信息的查询
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
nullptr,
&pEnumerator);

if (FAILED(hres)) {
qWarning() << "Query for Network Adapter Configuration failed."
<< " Error code = 0x"
<< hex << hres << endl;
pSvc->Release();
pLoc->Release();
CoUninitialize();
return false; // Program has failed.
}
// Step 7: 循环枚举所有的结果对象

IWbemClassObject *pclsObj = nullptr;
ULONG uReturn = 0;

while (pEnumerator) {
HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1,
&pclsObj, &uReturn);
if (0 == uReturn) {
break;
}

foreach (std::wstring param, paramList) {
VARIANT vtProp;
VariantInit(&vtProp);

hr = pclsObj->Get(param.data(), 0, &vtProp, 0, 0);//查询不同的硬件信息,除了修改上面的查询语句,这里的字段也要修改

if (!FAILED(hr)) {
if (vtProp.bstrVal)
resultList.append(QString::fromStdWString(vtProp.bstrVal));
else
resultList.append(QString::fromStdWString(L""));
}
VariantClear(&vtProp);
}

}//end while

// 释放资源
if (pSvc)
pSvc->Release();
if (pLoc)
pLoc->Release();
if (pEnumerator)
pEnumerator->Release();
if (pclsObj)
pclsObj->Release();
CoUninitialize();
return true; // Program successfully completed.

}

调用接口示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
BSTR sql = SysAllocString(L"SELECT * FROM Win32_NetworkAdapter WHERE (MACAddress IS NOT NULL) AND ((PNPDeviceID LIKE 'PCI%') OR (PNPDeviceID LIKE 'USB%'))");
QList<std::wstring> paramList = {L"name", L"macaddress", L"NetworkAddresses"};
QList< QStringList> resultList;
if (ManageWMIInterface(sql, paramList, resultList)) {
foreach (QStringList list, resultList) {
if (list.count() == 3) {
strAdapters.append(list.at(0));
strMACs.append(list.at(1));
strIPs.append("");
}
}

}

从使用方法不难发现,其实wmi的调用接口就是传入sql语句查询指定的硬件信息,通过sql的查询条件筛选出需要的数据,这里是为了查询出物理网卡信息,并去掉虚拟网卡。

mingw使用wmi的注意事项

1、模块引入

&emsp;&emsp;在Qt中使用com接口需要在工程中引入axcontainer模块,不然相关com接口无法通过编译,如下:

1
QT       += core gui network svg  xml axcontainer

2、CLSID_WbemLocator

&emsp;&emsp;在msvc编译器中这个对象的值已经通过宏定义取到了,但是在mingw中没有实现这个接口,导致编译不过,所以只能自己定义该对象。

1
CLSID CLSID_WbemLocator = {0x4590F811, 0x1D3A, 0x11D0, {0x89, 0x1F, 0, 0xAA, 0, 0x4B, 0x2E, 0x24}};

这个对象的值是从注册表中取得的,经查询所有版本的windows系统这个对象在注册表中的值都是固定的。

硬盘信息特例

&emsp;&emsp;实际测试当中发现wmi在xp中无法获取硬盘的serialnumber,数据库中并没有这一列的信息。这里通过DeviceIoControl获取。经msdn查询,该接口支持xp及以上的系统。使用示例如下:

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
char *flipAndCodeBytes(int iPos, int iFlip, const char *pcszStr, char *pcszBuf)

{
int iI;
int iJ = 0;
int iK = 0;

pcszBuf [0] = '\0';
if (iPos <= 0)
return pcszBuf;

if (! iJ) {
char cP = 0;
// First try to gather all characters representing hex digits only.
iJ = 1;
iK = 0;
pcszBuf[iK] = 0;
for (iI = iPos; iJ && !(pcszStr[iI] == '\0'); ++iI) {
char cC = tolower(pcszStr[iI]);
if (isspace(cC))
cC = '0';
++cP;
pcszBuf[iK] <<= 4;

if (cC >= '0' && cC <= '9')
pcszBuf[iK] |= (char)(cC - '0');
else if (cC >= 'a' && cC <= 'f')
pcszBuf[iK] |= (char)(cC - 'a' + 10);
else {
iJ = 0;
break;
}

if (cP == 2) {
if ((pcszBuf[iK] != '\0') && ! isprint(pcszBuf[iK])) {
iJ = 0;
break;
}
++iK;
cP = 0;
pcszBuf[iK] = 0;
}

}
}

if (! iJ) {
// There are non-digit characters, gather them as is.
iJ = 1;
iK = 0;
for (iI = iPos; iJ && (pcszStr[iI] != '\0'); ++iI) {
char cC = pcszStr[iI];

if (! isprint(cC)) {
iJ = 0;
break;
}

pcszBuf[iK++] = cC;
}
}

if (! iJ) {
// The characters are not there or are not printable.
iK = 0;
}

pcszBuf[iK] = '\0';

if (iFlip)
// Flip adjacent characters
for (iJ = 0; iJ < iK; iJ += 2) {
char t = pcszBuf[iJ];
pcszBuf[iJ] = pcszBuf[iJ + 1];
pcszBuf[iJ + 1] = t;
}

// Trim any beginning and end space
iI = iJ = -1;
for (iK = 0; (pcszBuf[iK] != '\0'); ++iK) {
if (! isspace(pcszBuf[iK])) {
if (iI < 0)
iI = iK;
iJ = iK;
}
}

if ((iI >= 0) && (iJ >= 0)) {
for (iK = iI; (iK <= iJ) && (pcszBuf[iK] != '\0'); ++iK)
pcszBuf[iK - iI] = pcszBuf[iK];
pcszBuf[iK - iI] = '\0';
}

return pcszBuf;
}

bool readPhysicalDriveSerialnumber(const QStringList &driveNameList, QStringList &soList)

{
foreach (const QString &driveName, driveNameList) {
HANDLE hPhysicalDriveIOCTL = nullptr;

// Windows NT, Windows 2000, Windows XP - admin rights not required
hPhysicalDriveIOCTL = CreateFileA(driveName.toStdString().data(), 0,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, 0, NULL);
if (hPhysicalDriveIOCTL == INVALID_HANDLE_VALUE) {
qDebug() << QString("创建hPhysicalDriveIOCTL(%1)失败").arg(driveName);
continue;
} else {
STORAGE_PROPERTY_QUERY query;
DWORD dwBytesReturned = 0;
char cszBuffer [10000];

memset((void *) & query, 0, sizeof(query));
query.PropertyId = StorageDeviceProperty;
query.QueryType = PropertyStandardQuery;

memset(cszBuffer, 0, sizeof(cszBuffer));

if (DeviceIoControl(hPhysicalDriveIOCTL, IOCTL_STORAGE_QUERY_PROPERTY,
& query,
sizeof(query),
& cszBuffer,
sizeof(cszBuffer),
& dwBytesReturned, NULL)) {
STORAGE_DEVICE_DESCRIPTOR *descrip = (STORAGE_DEVICE_DESCRIPTOR *) & cszBuffer;
char cszSerialNumber [1000];
memset(cszSerialNumber, 0, 1000);
flipAndCodeBytes(descrip -> SerialNumberOffset,
0, cszBuffer, cszSerialNumber);
soList.append(QString::fromLocal8Bit(cszSerialNumber));
CloseHandle(hPhysicalDriveIOCTL);
}
}
}
return soList.count() > 0;
}

这里flipAndCodeBytes()是用来从所有结果cszBuffer中根据指定offset获取指定的数据。需要注意的是由于xp和win7的序列号原始数据是40位的hex数据,flipAndCodeBytes()返回的是转化过的字符串,但是因为大小端问题,需要转化,代码如下:

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
QString revertQString(const QString &src)

{
if (src.isEmpty())
return "";
QString dest(src.count(), 0);
for (int i = 0; i < src.size() - 1; i += 2) {
QChar temp = src[i];
dest[i] = src[i + 1];
dest[i + 1] = temp;
}
//奇数情况
if (src.count() % 2 == 1) {
dest[src.count() - 1] = src[src.count() - 1];
}
return dest;
}


QStringList tempList;
ret = readPhysicalDriveSerialnumber(driveNameList, tempList);

QString osVersion = QSysInfo::productVersion();



foreach (QString serial, tempList) {

QByteArray buffer = serial.toLocal8Bit();
//xp win7 serialnumber是40位hex,readPhysicalDriveSerialnumber已经转为字符串,但需要转换大小端
//win8 win10是字符串格式
if (osVersion.contains("xp", Qt::CaseInsensitive) || osVersion.contains("7", Qt::CaseInsensitive)) {
resultList.append(revertQString(serial));
} else {
resultList.append(serial);
}
}

总结

&emsp;&emsp;使用wmi接口可以在xp及以上系统获取绝大部分硬件信息,只需要处理xp系统获取硬盘信息时,使用DeviceIoControl接口即可。注意wmware的虚拟机没有硬盘信息,根本无法获取,virtualbox虚拟机没有这个问题。


windows平台通用获取硬件信息
http://yoursite.com/2020/06/24/windows平台通用获取硬件信息/
作者
还在输入
发布于
2020年6月24日
许可协议