The point is to get the serial number of one of the hard drives on Windows. My code is based on an example from MSDN. I tried to make it an exception-safe, self-contained function. It works, to the best of my knowledge. All comments are welcome, especially about correctness, exception safety and performance issues.
#define _WIN32_DCOM
#include <Wbemidl.h>
#include <comdef.h>
#include <memory>
#include <optional>
#include <string_view>
#pragma comment(lib, "wbemuuid.lib")
namespace ComWrappers {
struct ComDeleter final {
template <class T>
auto operator()(T* const p) const noexcept -> void {
if (p) p->Release();
}
};
template <class T>
using ComPtr = ::std::unique_ptr<T, ComDeleter>;
struct ComInit final {
private:
bool _succeeded;
public:
ComInit() noexcept
: _succeeded{ SUCCEEDED(::CoInitializeEx(nullptr, COINIT_MULTITHREADED)) }
{}
[[nodiscard]] operator bool() const noexcept { return _succeeded; }
~ComInit() noexcept {
if (_succeeded) ::CoUninitialize();
}
};
} // namespace ComWrappers
[[nodiscard]] auto GetHardDriveSerialNumber() noexcept -> ::std::optional<::std::wstring> {
using namespace ::ComWrappers;
// Initialize COM
ComInit comOwner;
if (!comOwner) return ::std::nullopt;
// locator to WMI
ComPtr<::IWbemLocator> loc = nullptr;
{
::IWbemLocator* pLoc = nullptr;
auto const hres =
::CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER,
IID_IWbemLocator, reinterpret_cast<LPVOID*>(&pLoc));
if (FAILED(hres)) return ::std::nullopt;
loc.reset(pLoc);
}
ComPtr<::IWbemServices> svc = nullptr;
{ // Connect to WMI
::IWbemServices* pSvc = nullptr;
auto const hres = loc->ConnectServer(
_bstr_t(L"ROOT\\CIMV2"),
nullptr, nullptr, 0, 0, 0, 0,
&pSvc
);
if (FAILED(hres)) return ::std::nullopt;
svc.reset(pSvc);
}
{ // Set security levels
auto const hres = ::CoSetProxyBlanket(
svc.get(),
RPC_C_AUTHN_WINNT,
RPC_C_AUTHZ_NONE,
nullptr,
RPC_C_AUTHN_LEVEL_CALL,
RPC_C_IMP_LEVEL_IMPERSONATE,
nullptr,
EOAC_NONE
);
if (FAILED(hres)) return ::std::nullopt;
}
ComPtr<::IEnumWbemClassObject> enumerator = nullptr;
{ // Make requests of WMI
::IEnumWbemClassObject* pEnumerator = nullptr;
auto const hres = svc->ExecQuery(
bstr_t("WQL"), bstr_t("SELECT SerialNumber FROM Win32_PhysicalMedia"),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, nullptr,
&pEnumerator);
if (FAILED(hres)) return ::std::nullopt;
enumerator.reset(pEnumerator);
}
::std::optional<::std::wstring> result = ::std::nullopt;
// Get the data
while (true) {
ComPtr<::IWbemClassObject> clsObj = nullptr;
{
::IWbemClassObject* pclsObj = nullptr;
ULONG uReturn = 0;
auto const hres = enumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);
if (FAILED(hres) || uReturn == 0) break;
clsObj.reset(pclsObj);
}
VARIANT vtProp{};
// Get serial number
auto const hres = clsObj->Get(L"SerialNumber", 0, &vtProp, 0, 0);
if (FAILED(hres)) continue;
// RAII for variant class
struct VariantOwner {
VARIANT* v;
~VariantOwner() noexcept {
::VariantClear(v);
}
} variantOwner{ &vtProp };
// can't allow empty strings
if (*vtProp.bstrVal == '\0') continue;
try {
result = vtProp.bstrVal;
}
catch (...) {
// no need to do anything as
// a failure would return the default 'nullopt'
}
break;
}
return result;
}
// Example test driver
#include <iostream>
auto main() -> int {
auto const hid = GetHardDriveSerialNumber();
if (hid) ::std::wcout << *hid << '\n';
else ::std::wcout << "Failed to get serial number\n";
}
std::unique_ptr
for COM. I've found that most people have not stumbled onto that trick. Good adding RAII to COM VARIENT, but shouldn't that be part of theCOM_wrappers
namespace and library? \$\endgroup\$VariantOwner
class should only callVariantClear
ifclsObj->Get
succeeds. For that reason, it's a bit too specialized and I didn't want to put to the general namespace. \$\endgroup\$RPC_E_CHANGED_MODE
? \$\endgroup\$