Windows获取硬件信息

研究背景

因项目需要,一直在网上调研获取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();
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值