借助WMI获取当前程序运行硬盘的硬盘序列号

Reading time ~8 minutes

1.什么是WMI?

WMI全称Windows Management Instrumentation,是Microsoft基于Web的企业管理(WBEM)的实现,WBEM是一项行业倡议,旨在开发用于在企业环境中访问管理信息的标准技术….其他不再赘述,详情见MSDN - About WMI

The CIMWin32 WMI providers支持CimWin32.dll中支持的类,由核心的CIM WMI类和电源管理事件,以下用到了Win32_DiskDrive, Win32_DiskDriveToDiskPartition, Win32_LogicalDiskToPartition等类。相关链接WMI providers

2. 开始

如果系统只有一个硬盘,那其实就可以直接通过命令行直接获取结果,例如:

wmic diskdrive where index=0 get serialnumber

倘若不只只有一个硬盘呢?有以下思路:

1.首先获取程序所在逻辑硬盘位置 
2.通过逻辑硬盘位置和硬盘的序列号关联表查询序列号
3.获取到当前硬盘序列号

其中的难点在于第二步,如何获取硬盘的逻辑分区情况,这时刚好可以通过WQL查询获得,以下是通过MSDN连接WMI例子而加工的代码:

HRESULT hres;

// Step 1: --------------------------------------------------
// Initialize COM. ------------------------------------------

hres = CoInitializeEx(0, COINIT_MULTITHREADED);
if (FAILED(hres))
{
    cout << "Failed to initialize COM library. Error code = 0x"
        << hex << hres << endl;
    return 1;                  // Program has failed.
}

// Step 2: --------------------------------------------------
// Set general COM security levels --------------------------
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
);

// note: 此处步骤1,2 如果已经初始化过,第二次会false,所以视情况调用

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

// Step 3: ---------------------------------------------------
// 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."
        << " Err code = 0x"
        << hex << hres << endl;
    CoUninitialize();
    return 1;                 // Program has failed.
}

// Step 4: -----------------------------------------------------
// Connect to WMI through the IWbemLocator::ConnectServer method

IWbemServices* 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
);

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

cout << "Connected to ROOT\\CIMV2 WMI namespace" << endl;


// 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
    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))
{
    cout << "Could not set proxy blanket. Error code = 0x"
        << hex << hres << endl;
    pSvc->Release();
    pLoc->Release();
    CoUninitialize();
    return 1;               // Program has failed.
}

// Step 6: --------------------------------------------------
// Use the IWbemServices pointer to make requests of WMI ----

// For example, get the name of the operating system
IEnumWbemClassObject* pEnumerator = nullptr;

hres = pSvc->ExecQuery(
    bstr_t("WQL"),
    bstr_t("SELECT * FROM Win32_DiskDrive"),
    WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
    NULL,
    &pEnumerator);

// note: 
// 1. 这里通过查询得到DEVICEID = \\\\.\\PHYSICALDRIVE0, 然而还需要将\ 替换为 \\

// 2. WQL语句中“=”左右不能有空格,否则ExecQuery之后会查询失败
//    如:ASSOCIATORS OF {Win32_DiskDrive.DeviceID=\" xxx \"}就会失败

// 3. gcc环境中 bstr_t("xxx") 可能会编译报错, 使用 "BSTR xxx = SysAllocString(L"sql .. ") " 和 SysFreeString(xxx) 组合代替
//    如:bstr_t("WQL") ——> BSTR strQueryLanguage = SysAllocString(L"WQL"); SysFreeString(strQueryLanguage) 

//hres = pSvc->ExecQuery(
//    bstr_t("WQL"),
//    bstr_t("ASSOCIATORS OF {Win32_DiskDrive.DeviceID=\"\\\\\\\\.\\\\PHYSICALDRIVE0\"} WHERE AssocClass=Win32_DiskDriveToDiskPartition"),
//    //bstr_t("ASSOCIATORS OF {Win32_LogicalDisk.DeviceID=\"C:\"}"),
//    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;               // Program has failed.
}

// Step 7: -------------------------------------------------
// Get the data from the query in step 6 -------------------

IWbemClassObject* pclsObj = nullptr;
ULONG uReturn = 0;

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

    if (0 == uReturn)
    {
        break;
    }

    VARIANT vtProp;

    // Get the value of the Name property
    hr = pclsObj->Get(L"DeviceID", 0, &vtProp, 0, 0);
    //wcout << "DeviceID : " << vtProp.bstrVal << endl;
    wstring deviceID = vtProp.bstrVal;
    wcout << deviceID << endl;

    hr = pclsObj->Get(L"SerialNumber", 0, &vtProp, 0, 0);
    wstring serialNumber = vtProp.bstrVal;
    wcout << "SerialNumber : " << serialNumber << endl;

    VariantClear(&vtProp);
    pclsObj->Release();

    replace(deviceID, L"\\", L"\\\\");

    wstring query;
    query.append(L"ASSOCIATORS OF {Win32_DiskDrive.DeviceID=\"");
    query.append(deviceID);
    query.append(L"\"} WHERE AssocClass=Win32_DiskDriveToDiskPartition");

    IEnumWbemClassObject* partitionEnumerator = nullptr;
    hres = pSvc->ExecQuery(
        bstr_t("WQL"),
        bstr_t(query.c_str()),
        WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
        nullptr,
        &partitionEnumerator);

    IWbemClassObject* diskPartition = nullptr;
    while (partitionEnumerator)
    {
        HRESULT hr = partitionEnumerator->Next(WBEM_INFINITE, 1,
            &diskPartition, &uReturn);

        if (0 == uReturn)
        {
            break;
        }
        VARIANT vtProp;

        // Get the value of the Name property
        hr = diskPartition->Get(L"DeviceID", 0, &vtProp, 0, 0);
        wstring diskPartitionDeviceID = vtProp.bstrVal;
        wcout << "diskPartitionDeviceID : " << diskPartitionDeviceID << endl;

        VariantClear(&vtProp);

        diskPartition->Release();
        diskPartition = nullptr;

        wstring query;
        query.append(L"ASSOCIATORS OF {Win32_DiskPartition.DeviceID=\"");
        query.append(diskPartitionDeviceID);
        query.append(L"\"} WHERE AssocClass=Win32_LogicalDiskToPartition");

        IEnumWbemClassObject* logicalDiskEnumerator = nullptr;
        hres = pSvc->ExecQuery(
            bstr_t("WQL"),
            bstr_t(query.c_str()),
            WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
            nullptr,
            &logicalDiskEnumerator);

        IWbemClassObject* logicalPartition = nullptr;
        while (logicalDiskEnumerator) {

            HRESULT hr = logicalDiskEnumerator->Next(WBEM_INFINITE, 1,
                &logicalPartition, &uReturn);

            if (0 == uReturn)
            {
                break;
            }

            VARIANT vtProp;

            hr = logicalPartition->Get(L"DeviceID", 0, &vtProp, 0, 0);
            wstring logicalDiskDeviceID = vtProp.bstrVal;
            wcout << "logicalDiskDeviceID : " << logicalDiskDeviceID << endl; // 通过上方得到的SerialNumber和此处的logicalDiskDeviceID建立map就完成了步骤2

            VariantClear(&vtProp);

            logicalPartition->Release();
            logicalPartition = nullptr;

        }
    }
}

// Cleanup
// ========

pSvc->Release();
pLoc->Release();
pEnumerator->Release();
CoUninitialize();

return 0;   // Program successfully completed.

代码中踩坑的地方做了注释,记录这些以备不时之需。

其中WQL查询参考文章How Can I Correlate Logical Drives and Physical Disks?

Vm下Ubuntu编译Qt5.15遇到的坑

ubuntu20.0编译qt15.0踩过的坑 Continue reading

Rc4算法的实现

Published on March 30, 2021

一些奇怪的位运算

Published on March 22, 2021