說實在的,過去一兩年來,每天客戶都跟我抱怨﹔你那個甚麼爛系統,總是有裝置遺失的問題! 小弟實在不堪其擾,因為裝置管理員黃色驚嘆號的產生,也不一定真的是我們的問題,也常常是第三方周邊裝置不給力呀!
當Windows無法正確偵測周邊裝置狀態的時候,就會在裝置管理員該周邊的圖示上顯示一個黃色驚嘆號,英文就是Yellow Bang或是簡稱YB,表示該周邊的驅動程式安裝不完全或是處於無法正常工作的狀態。
由於這種周邊裝置不正常的狀態常常都是在壓力測試下才會發生,例如不斷地重開機、進出Modern Standby S3或是S4休眠狀態的時候,測試個幾千次才發生個幾次,這種狀況不太可能用人工的方式去檢查周邊裝置狀態,所以我們需要一個小程式,每次開機或是進出休眠狀態的時候去檢查,如果發生黃色驚嘆號,就離開壓力測試迴圈,並且回報那個裝置有問題。
在這邊我們透過WMI API才查詢系統周邊的狀況,其中主要就是呼叫IWbemServices 介面來存取 WMI 服務。透過Win32_PnPEntity類別,找出任何一個裝置的ConfigManagerErrorCode不等於0,如果不等於0,就表示該裝置處於不正常的狀態。
詳細的程式碼如下,提供大家做為參考。
//
// 使用WMI API來列出所有在裝置管理員中有黃色驚嘆號的裝置名稱。
//
#include <iostream>
#include <wbemidl.h>
#pragma comment(lib, "wbemuuid.lib")
#include <comdef.h>
#include <ctime>
using namespace std;
int main(int argc, char** argv)
{
HRESULT hres;
// Initialize COM.
hres = CoInitializeEx(0, COINIT_MULTITHREADED);
if (FAILED(hres))
{
cout << "Failed to initialize COM library. Error code = 0x"
<< hex << hres << endl;
return 1;
}
// Set general COM security levels.
hres = CoInitializeSecurity(
NULL,
-1,
NULL,
NULL,
RPC_C_AUTHN_LEVEL_DEFAULT,
RPC_C_IMP_LEVEL_IMPERSONATE,
NULL,
EOAC_NONE,
NULL);
if (FAILED(hres))
{
cout << "Failed to initialize security. Error code = 0x"
<< hex << hres << endl;
CoUninitialize();
return 1;
}
// Obtain the initial locator to WMI.
IWbemLocator* pLoc = NULL;
hres = CoCreateInstance(
CLSID_WbemLocator,
0,
CLSCTX_INPROC_SERVER,
IID_IWbemLocator,
(LPVOID*)&pLoc);
if (FAILED(hres))
{
cout << "Failed to create IWbemLocator object. "
<< "Error code = 0x"
<< hex << hres << endl;
CoUninitialize();
return 1;
}
// Connect to WMI through the IWbemLocator::ConnectServer method.
IWbemServices* pSvc = NULL;
hres = pLoc->ConnectServer(
_bstr_t(L"ROOT\\CIMV2"),
NULL,
NULL,
0,
NULL,
0,
0,
&pSvc);
if (FAILED(hres))
{
cout << "Could not connect. Error code = 0x"
<< hex << hres << endl;
pLoc->Release();
CoUninitialize();
return 1;
}
// Set the IWbemServices proxy so that impersonation
// of the user (client) occurs.
hres = CoSetProxyBlanket(
pSvc,
RPC_C_AUTHN_WINNT,
RPC_C_AUTHZ_NONE,
NULL,
RPC_C_AUTHN_LEVEL_CALL,
RPC_C_IMP_LEVEL_IMPERSONATE,
NULL,
EOAC_NONE);
if (FAILED(hres))
{
cout << "Could not set proxy blanket. Error code = 0x"
<< hex << hres << endl;
pSvc->Release();
pLoc->Release();
CoUninitialize();
return 1;
}
// Use the IWbemServices pointer to make requests of WMI.
IEnumWbemClassObject* pEnumerator = NULL;
hres = pSvc->ExecQuery(
bstr_t("WQL"),
bstr_t("SELECT * FROM Win32_PnPEntity WHERE ConfigManagerErrorCode <> 0"),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&pEnumerator);
if (FAILED(hres))
{
cout << "Query for operating system name failed."
<< " Error code = 0x"
<< hex << hres << endl;
pSvc->Release();
pLoc->Release();
CoUninitialize();
return 1;
}
time_t current_time;
char c_time_string[20];
/* Get the current time. */
current_time = time(NULL);
/* Convert it to local time representation. */
strftime(c_time_string, sizeof(c_time_string), "%m/%d %H:%M:%S", localtime(¤t_time));
// Get the data from the query.
IWbemClassObject* pclsObj = NULL;
ULONG uReturn = 0;
while (pEnumerator)
{
HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);
if (0 == uReturn)
{
wcout << c_time_string << " All device drivers are working fine!" << endl;
break;
}
VARIANT vtProp;
VariantInit(&vtProp);
//hr = pclsObj->Get(L"PNPDeviceID", 0, &vtProp, 0, 0);
//wcout << "PNP Device ID : " << vtProp.bstrVal << endl;
//VariantClear(&vtProp);
hr = pclsObj->Get(L"DeviceID", 0, &vtProp, 0, 0);
wcout << c_time_string << " Device ID : " << vtProp.bstrVal << endl;
VariantClear(&vtProp);
hr = pclsObj->Get(L"Name", 0, &vtProp, 0, 0);
wcout << c_time_string << " Device Name : " << vtProp.bstrVal << endl;
VariantClear(&vtProp);
pclsObj->Release();
}
// Cleanup.
pSvc->Release();
pLoc->Release();
pEnumerator->Release();
CoUninitialize();
return 0;
}
需要注意的是,無論重新開機或是從休眠模式喚醒的情況下,Windows都需要一段時間重新偵測周邊裝置,所以我們必須要等待一段時間之後,再詢問周邊裝置的狀態,而不是立刻執行詢問狀態。這個時間會依據周邊裝置不同,而有所不同,特別是系統忙碌的狀況下,有些USB裝置,甚至會偵測個好幾十秒,都不一定有回應,所以我們建議等個兩三分鐘之後,再開始詢問。
至於重新開機與休眠模式壓力測試的工具,大部分人都是使用微軟所提供的pwrtest的工具,在這邊就不贅述。希望大家都能輕鬆通過系統壓力測試。