C/C++ 運用VMI介面查詢系統資訊

lyshark發表於2023-11-18

Windows Management Instrumentation(WMI)是一種用於管理和監視Windows作業系統的框架。它為開發人員、系統管理員和自動化工具提供了一種標準的介面,透過這個介面,可以獲取有關計算機系統硬體、作業系統和應用程式的資訊,以及對系統進行管理和控制的能力。

WMI允許透過程式設計方式查詢系統資訊、監視效能、執行管理任務等。它提供了一種統一的方式來訪問和管理Windows作業系統的各個方面,而無需瞭解底層實現細節。透過WMI,可以使用各種程式語言(如C#、VBScript、PowerShell等)來執行諸如查詢系統資訊、監控效能、配置系統設定等任務。

當需要透過WMI程式設計提取引數時,我們就需要使用WQL(Windows Management Instrumentation Query Language)它是一種查詢語言,專門用於查詢Windows Management Instrumentation (WMI)資料。WMI 是Windows作業系統中用於管理和監視的框架,而WQL則是用於與WMI進行互動的查詢語言。

WQL 的語法類似於 SQL(Structured Query Language),使用WQL可以執行各種查詢來檢索關於計算機系統、硬體、軟體和其他管理資訊的資料。這些查詢可以用於編寫指令碼、管理任務、監視效能等。為了方便查詢獲取引數這裡提供一個簡單的查詢工具供大家查詢使用,下載後開啟,其預設查詢的是Win32_ComputerSystem也就是系統的基本引數資訊;

如果我們需要獲取其他資訊,比如得到計算機中所安裝的所有Windows服務資訊,可以執行SELECT * FROM Win32_Service語句,當然也有許多其他的通用語句可以讓我們使用,例如如下幾種常用的語句。

  • 查詢所有安裝的軟體
    • SELECT * FROM Win32_Product
  • 查詢所有邏輯磁碟的資訊
    • SELECT * FROM Win32_LogicalDisk
  • 查詢所有網路介面卡的資訊
    • SELECT * FROM Win32_NetworkAdapter
  • 查詢作業系統資訊
    • SELECT * FROM Win32_OperatingSystem
  • 查詢所有正在執行的程式
    • SELECT * FROM Win32_Process
  • 查詢所有使用者賬戶資訊
    • SELECT * FROM Win32_UserAccount
  • 查詢系統啟動項
    • SELECT * FROM Win32_StartupCommand
  • 查詢實體記憶體
    • SELECT * FROM Win32_PhysicalMemory

如上圖所示,查詢將返回Win32_Service類中所有服務的資訊。你可以根據需要編寫更復雜的查詢,以滿足特定的管理或監視要求。

為了讓讀者更加方便的使用查詢功能,此處我封裝了一個SelectQuerySQL查詢函式,該函式需要傳入特定的查詢語句,特定的查詢欄位以及返回值緩衝區,此時只需要讀取緩衝區內的資料即可得到查詢結果。

#define _CRT_SECURE_NO_WARNINGS
#define _WIN32_DCOM
#define _CRT_NONSTDC_NO_DEPRECATE

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

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

using namespace std;

// 去掉字串中的空格
void Trims(char* data)
{
    int i = -1, j = 0;
    int ch = ' ';

    while (data[++i] != '\0')
    {
        if (data[i] != ch)
        {
            data[j++] = data[i];
        }
    }
    data[j] = '\0';
}

// 通用查詢方法,每次查詢一條
bool SelectQuerySQL(LPCWSTR SQL, LPCWSTR Key, OUT char OutBuf[1024])
{
    HRESULT hres;

    CoUninitialize();
    hres = CoInitializeEx(0, COINIT_MULTITHREADED);
    if (FAILED(hres))
    {
        return false;
    }
    hres = CoInitializeSecurity(0, -1, 0, 0, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, 0, EOAC_NONE, 0);
    if (FAILED(hres))
    {
        CoUninitialize();
        return false;
    }
    IWbemLocator* pLoc = NULL;
    hres = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pLoc);
    if (FAILED(hres))
    {
        CoUninitialize();
        return false;
    }
    IWbemServices* pSvc = NULL;
    hres = pLoc->ConnectServer(_bstr_t(L"ROOT\\CIMV2"), 0, 0, 0, 0, 0, 0, &pSvc);
    if (FAILED(hres))
    {
        pLoc->Release();
        CoUninitialize();
        return false;
    }
    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))
    {
        pSvc->Release();
        pLoc->Release();
        CoUninitialize();
        return false;
    }

    IEnumWbemClassObject* pEnumerator = NULL;

    // 執行WSQL語句
    hres = pSvc->ExecQuery(bstr_t("WQL"),bstr_t(SQL),WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,NULL,&pEnumerator);
    if (FAILED(hres))
    {
        pSvc->Release();
        pLoc->Release();
        CoUninitialize();
        return false;
    }
    IWbemClassObject* pclsObj;
    ULONG uReturn = 0;
    while (pEnumerator)
    {
        HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);
        if (0 == uReturn)
        {
            break;
        }
        VARIANT vtProp;

        // 獲取到指定Key欄位
        hr = pclsObj->Get(Key, 0, &vtProp, 0, 0);

        // 將獲取到的資料返回給OutBuf
        wcstombs(OutBuf, vtProp.bstrVal, 1024);
        VariantClear(&vtProp);
        pclsObj->Release();
    }
    pSvc->Release();
    pLoc->Release();
    pEnumerator->Release();
    CoUninitialize();
    return true;
}

有了上述函式的封裝,那麼實現查詢就變得很容易了,當我們需要查詢CPU序列號時,可以直接執行SELECT * FROM win32_Processor並取出裡面的ProcessorId欄位,使用函式時可以總結為如下所示的案例;

int main(int argc, char *argv[])
{
	char RefBuffer[1024] = { 0 };
	bool ref = false;

	ref = SelectQuerySQL(L"SELECT * FROM win32_Processor", L"ProcessorId", RefBuffer);
	std::cout << "獲取CPU序列號: " << RefBuffer << std::endl;
	Trims(RefBuffer);

	system("pause");
	return 0;
}

輸出效果如下所示;

根據這個查詢方法,我們就可以得到系統的各類韌體序列號,這對於軟體認證尤為重要;

int main(int argc, char *argv[])
{
	char RefBuffer[1024] = { 0 };
	bool ref = false;

	ref = SelectQuerySQL(L"SELECT * FROM win32_Processor", L"ProcessorId", RefBuffer);
	std::cout << "獲取CPU序列號: " << RefBuffer << std::endl;
	Trims(RefBuffer);

	ref = SelectQuerySQL(L"SELECT * FROM Win32_BaseBoard", L"SerialNumber", RefBuffer);
	std::cout << "獲取主機板ID號: " << RefBuffer << std::endl;
	Trims(RefBuffer);

	ref = SelectQuerySQL(L"SELECT * FROM Win32_BIOS", L"SerialNumber", RefBuffer);
	std::cout << "獲取BIOS序列號: " << RefBuffer << std::endl;
	Trims(RefBuffer);

	ref = SelectQuerySQL(L"SELECT * FROM Win32_PhysicalMemory", L"SerialNumber", RefBuffer);
	std::cout << "獲取記憶體序列號: " << RefBuffer << std::endl;
	Trims(RefBuffer);

	ref = SelectQuerySQL(L"SELECT * FROM Win32_DiskDrive WHERE Index = 0", L"SerialNumber", RefBuffer);
	Trims(RefBuffer);
	std::cout << "獲取硬碟序列號: " << RefBuffer << std::endl;

	system("pause");
	return 0;
}

輸出效果如下所示;

當然,有時我們也需要一次性輸出多個引數,某些資料存在多條記錄,在輸出時也需要增加一些程式碼,我們以Win32_LogicalDisk為例,程式碼需要進行一定的改進,在迴圈時分別取出不同的欄位,此時的查詢函式需要相應的做一些改進,如下是查詢函式需要變化的位置。

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

    // 輸出磁碟機代號欄位
    VARIANT vtProp_DeviceID;
    VARIANT vtProp_FreeSpace;
    VARIANT vtProp_Size;

    // 獲取到指定Key欄位
    hr = pclsObj->Get(L"DeviceID", 0, &vtProp_DeviceID, 0, 0);
    hr = pclsObj->Get(L"FreeSpace", 0, &vtProp_FreeSpace, 0, 0);
    hr = pclsObj->Get(L"Size", 0, &vtProp_Size, 0, 0);

    // 轉換資料為字串
    char x[32], y[32], z[32];
    wcstombs(x, vtProp_DeviceID.bstrVal, 32);
    wcstombs(y, vtProp_FreeSpace.bstrVal, 32);
    wcstombs(z, vtProp_Size.bstrVal, 32);

    std::cout << "分割槽: " << x << std::endl;
    std::cout << "剩餘: " << y << std::endl;
    std::cout << "容量: " << z << std::endl;

    // 清理
    VariantClear(&vtProp_DeviceID);
    VariantClear(&vtProp_FreeSpace);
    VariantClear(&vtProp_Size);
    pclsObj->Release();
}

此外,在查詢引數上也應該修改為對應的SELECT * FROM Win32_LogicalDisk查詢磁碟;

int main(int argc, char *argv[])
{
	char RefBuffer[1024] = { 0 };
	bool ref = false;

	ref = SelectQuerySQL(L"SELECT * FROM Win32_LogicalDisk", L"ProcessorId", RefBuffer);
	Trims(RefBuffer);

	system("pause");
	return 0;
}

此時,當再一次執行這段程式碼,就可以查詢到當前系統中所有的磁碟資訊,如下圖所示;

相關文章