研究背景
因项目需要,一直在网上调研获取Windows硬件信息的解决方案,要求稳定性高、可靠性强、兼容性好。在网上绝大多数的博主推荐使用WMIC获取硬件信息,WMIC是微软官方推出的一款Windows Management Instrumentation (WMI)命令行实用工具,在使用了一段时间后发现,WMIC在一些机器上会出现偶尔获取硬件信息失败的问题,比如使用命令“wmic cpu get processorid”获取到的处理器ID为空,再次执行此命令则获取成功,除此之外个别电脑中没有此工具导致无法获取硬件信息。为了解决此问题,查阅了微软官方的文档,发现官方已经宣布在Windows 10 21H1版本以后弃用WMIC,以Windows Power Shell取而代之。
技术探索
使用微软官方提供的API("__cpuidex")获取CPU的ID,使用多台电脑进行测试后,发现win7、win10电脑上稳定性和可靠性较高,但是在win11企业版和Windows Server 2016上获取到的ID是错的,即获取到的ID和使用WMIC获取到的ID不一致,故放弃了该接口。
使用微软官方提供的API("DeviceIoControl")获取硬盘序列号,使用多台电脑测试后,发现部分机器获取到的硬盘序列号存在字节序问题,为了处理字节序问题,编写了用于测试系统大小端的程序,发现是小端则转为大端,但字节序问题依旧存在,故弃用了该接口。
查阅了微软官方提供的WMI文档后,开始使用"Windows Power Shell"工具利用WQL进行硬件信息查询,在多台电脑中以此获取硬盘序列号和CPU的ID,发现稳定性和可靠性相对来说要好一些,30余台测试机涵盖了多数的主流操作系统,如:Win7、Win10、Win11、Windows Server 2012、Windows Server 2016,其中在Windows Server 2016中获取硬盘序列号出现了错误,现象为执行"SELECT SerialNumber FROM Win32_PhysicalMedia"WQL语句获取硬盘序列号时,查询结果偶尔出现乱码、偶尔为空、偶尔正确。
解决方案:
经过长时间的摸索,最终决定使用WQL查询WMI的方式获取硬件信息,此办法可靠性、稳定性、兼容性要高一些。注意在部分机器上如Win7,使用WQL查询到的硬盘序列号为字符串显示,而使用WMIC查询到的硬盘序列号为十六进制显示,可以将WQL查询到的结果进行字节序转换,然后转为Hex同WMIC查询结果进行对比。
示例代码:
①使用API获取处理器ID
/*在win11企业版和Windows Server 2016 上获取到的处理器ID是错的*/
string GetCPUID() {
INT32 dwBuf[4];
string strCPUId;
char buf[32] = { 0 };
__cpuidex(dwBuf, 1, 1);
//printf("%08X%08X\n", dwBuf[3], dwBuf[0]);
memset(buf, 0, 32);
sprintf_s(buf, 32, "%08X", dwBuf[3]);
strCPUId += buf;
memset(buf, 0, 32);
sprintf_s(buf, 32, "%08X", dwBuf[0]);
strCPUId += buf;
return strCPUId;
}
②使用WMIC获取硬盘序列号
string GetDiskByCmd()
{
string a;
string b;
string ider;
BOOL bret = FALSE;
//disk drive
const long MAX_COMMAND_SIZE = 10000; // 命令行输出缓冲大小
WCHAR szFetCmd[] = L"wmic diskdrive where index=0 get serialnumber"; // 获取DiskDrive命令行
const string strEnSearch = "SerialNumber"; // DiskDrive序列号的前导信息
HANDLE hReadPipe = NULL; //读取管道
HANDLE hWritePipe = NULL; //写入管道
PROCESS_INFORMATION pi; //进程信息
STARTUPINFO si; //控制命令行窗口信息
SECURITY_ATTRIBUTES sa; //安全属性
char szBuffer[MAX_COMMAND_SIZE + 1] = { 0 }; // 放置命令行结果的输出缓冲区
string strBuffer;
unsigned long count = 0;
long ipos = 0;
memset(&pi, 0, sizeof(pi));
memset(&si, 0, sizeof(si));
memset(&sa, 0, sizeof(sa));
pi.hProcess = NULL;
pi.hThread = NULL;
si.cb = sizeof(STARTUPINFO);
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
//1.0 创建管道
bret = CreatePipe(&hReadPipe, &hWritePipe, &sa, 0);
if (!bret)
{
//关闭所有的句柄
CloseHandle(hWritePipe);
CloseHandle(hReadPipe);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return "";
}
else
{
//2.0 设置命令行窗口的信息为指定的读写管道
GetStartupInfo(&si);
si.hStdError = hWritePipe;
si.hStdOutput = hWritePipe;
si.wShowWindow = SW_HIDE; //隐藏命令行窗口
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
//3.0 创建获取命令行的进程
bret = CreateProcess(NULL, szFetCmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
if (!bret)
{
//关闭所有的句柄
CloseHandle(hWritePipe);
CloseHandle(hReadPipe);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return "";
}
else
{
//4.0 读取返回的数据
WaitForSingleObject(pi.hProcess, 500/*INFINITE*/);
bret = ReadFile(hReadPipe, szBuffer, MAX_COMMAND_SIZE, &count, 0);
if (!bret)
{
CloseHandle(hWritePipe);
CloseHandle(hReadPipe);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return "";
}
else
{
//5.0 查找DISK序列号
strBuffer = szBuffer;
ipos = (long)strBuffer.find(strEnSearch);
if (ipos < 0) // 没有找到
{
CloseHandle(hWritePipe);
CloseHandle(hReadPipe);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return "";
}
else
{
strBuffer = strBuffer.substr(ipos + strEnSearch.length());
memset(szBuffer, 0x00, sizeof(szBuffer));
strcpy_s(szBuffer, strBuffer.c_str());
char temp[512];
memset(temp, 0, sizeof(temp));
int index = 0;
for (size_t i = 0; i < strBuffer.size(); i++)
{
if (strBuffer[i] != ' ' && strBuffer[i] != '\n' && strBuffer[i] != '\r')
{
temp[index] = strBuffer[i];
if (strBuffer[i + 1] != ' ' && strBuffer[i + 1] != '\n' && strBuffer[i + 1] != '\r')
{
index++;
}
else
{
a = "/";
temp[index + 1] = a[0];
index = index + 2;
}
}
}
b = temp;
ider = b.substr(0, b.length() - 1);
//关闭所有的句柄
CloseHandle(hWritePipe);
CloseHandle(hReadPipe);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return ider;
}
}
}
}
}
③使用WQL查询硬件信息
#include "stdafx.h"
#include "QueryHardwareInfoByWQL.h"
WmiQueryResult getWmiQueryResult(std::wstring wmiQuery, std::wstring propNameOfResultObject, bool allowEmptyItems = false) {
WmiQueryResult retVal;
retVal.Error = WmiQueryError::None;
retVal.ErrorDescription = L"";
HRESULT hres;
IWbemLocator *pLoc = NULL;
IWbemServices *pSvc = NULL;
IEnumWbemClassObject* pEnumerator = NULL;
IWbemClassObject *pclsObj = NULL;
VARIANT vtProp;
// Step 1: --------------------------------------------------
// Initialize COM. ------------------------------------------
hres = CoInitializeEx(0, COINIT_MULTITHREADED);
if (FAILED(hres))
{
retVal.Error = WmiQueryError::ComInitializationFailure;
retVal.ErrorDescription = L"Failed to initialize COM library. Error code : " + std::to_wstring(hres);
}
else
{
// Step 2: --------------------------------------------------
// Set general COM security levels --------------------------
// note: JUCE Framework users should comment this call out,
// as this does not need to be initialized to run the query.
// see https://social.msdn.microsoft.com/Forums/en-US/48b5626a-0f0f-4321-aecd-17871c7fa283/unable-to-call-coinitializesecurity?forum=windowscompatibility
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))
{
retVal.Error = WmiQueryError::SecurityInitializationFailure;
retVal.ErrorDescription = L"Failed to initialize security. Error code : " + std::to_wstring(hres);
}
else
{
// Step 3: ---------------------------------------------------
// Obtain the initial locator to WMI -------------------------
pLoc = NULL;
hres = CoCreateInstance(
CLSID_WbemLocator,
0,
CLSCTX_INPROC_SERVER,
IID_IWbemLocator, (LPVOID *)&pLoc);
if (FAILED(hres))
{
retVal.Error = WmiQueryError::IWbemLocatorFailure;
retVal.ErrorDescription = L"Failed to create IWbemLocator object. Error code : " + std::to_wstring(hres);
}
else
{
// Step 4: -----------------------------------------------------
// Connect to WMI through the IWbemLocator::ConnectServer method
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
);
// Connected to ROOT\\CIMV2 WMI namespace
if (FAILED(hres))
{
retVal.Error = WmiQueryError::IWbemServiceConnectionFailure;
retVal.ErrorDescription = L"Could not connect to Wbem service.. Error code : " + std::to_wstring(hres);
}
else
{
// 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))
{
retVal.Error = WmiQueryError::BlanketProxySetFailure;
retVal.ErrorDescription = L"Could not set proxy blanket. Error code : " + std::to_wstring(hres);
}
else
{
// Step 6: --------------------------------------------------
// Use the IWbemServices pointer to make requests of WMI ----
// For example, get the name of the operating system
pEnumerator = NULL;
hres = pSvc->ExecQuery(
bstr_t("WQL"),
bstr_t(wmiQuery.c_str()),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&pEnumerator);
if (FAILED(hres))
{
retVal.Error = WmiQueryError::BadQueryFailure;
retVal.ErrorDescription = L"Bad query. Error code : " + std::to_wstring(hres);
}
else
{
// Step 7: -------------------------------------------------
// Get the data from the query in step 6 -------------------
pclsObj = NULL;
ULONG uReturn = 0;
while (pEnumerator)
{
HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1,
&pclsObj, &uReturn);
if (0 == uReturn)
{
break;
}
// VARIANT vtProp;
// Get the value of desired property
hr = pclsObj->Get(propNameOfResultObject.c_str(), 0, &vtProp, 0, 0);
if (S_OK != hr) {
retVal.Error = WmiQueryError::PropertyExtractionFailure;
retVal.ErrorDescription = L"Couldn't extract property: " + propNameOfResultObject + L" from result of query. Error code : " + std::to_wstring(hr);
}
else {
BSTR val = vtProp.bstrVal;
// Sometimes val might be NULL even when result is S_OK
// Convert NULL to empty string (otherwise "std::wstring(val)" would throw exception)
if (NULL == val) {
if (allowEmptyItems) {
retVal.ResultList.push_back(std::wstring(L""));
}
}
else {
retVal.ResultList.push_back(std::wstring(val));
}
}
}
}
}
}
}
}
}
// Cleanup
// ========
VariantClear(&vtProp);
if (pclsObj)
pclsObj->Release();
if (pSvc)
pSvc->Release();
if (pLoc)
pLoc->Release();
if (pEnumerator)
pEnumerator->Release();
CoUninitialize();
return retVal;
}
wstring queryAndPrintResult(std::wstring query, std::wstring propNameOfResultObject)
{
WmiQueryResult res;
res = getWmiQueryResult(query, propNameOfResultObject);
if (res.Error != WmiQueryError::None) {
wcout << "Got this error while executing query: " << endl;
wcout << res.ErrorDescription << std::endl;
return L""; // Exitting function
}
//wstring RetWStr;
//for (const auto& item : res.ResultList) {
std::wcout << item << std::endl;
// RetWStr.append(item+L"\n");
//}
//return RetWStr;
if (res.ResultList.empty()) return L"";
return *res.ResultList.begin();
}