Just Let It Flow

January 29, 2011

WMI’m Going Speed Dating

Filed under: Code,Windows — adeyblue @ 12:21 am

All WMI queries that give or take datetime values, do so in a certain format called CIM_DATETIME. For scripting guys, this is only a minor inconvenience. The following listing is the entire code to display the OS install date in various formats:

' Create a new datetime object.
Set dateTime = CreateObject("WbemScripting.SWbemDateTime")
 
' Retrieve a WMI object that contains a datetime value.
for each os in GetObject( _
    "winmgmts:").InstancesOf ("Win32_OperatingSystem")
 
    ' The InstallDate property is a CIM_DATETIME. 
    MsgBox os.InstallDate
    dateTime.Value = os.InstallDate
 
    ' Display the year of installation.
    MsgBox "This OS was installed in the year " & dateTime.Year
 
    ' Display the installation date using the VT_DATE format.
    MsgBox "Full installation date (VT_DATE format) is " _
    & dateTime.GetVarDate
 
    ' Display the installation date using the FILETIME format.
    MsgBox "Full installation date (FILETIME format) is " _
    & dateTime.GetFileTime 
next
Set datetime = Nothing

For the native coders among us, the standard Win32 time functions neither produce nor consume this format meaning we have to perform some jiggery poker to get it to play nice.

The veritable Dutch naval spoon (since it’s not quite so universal to be an army knife) needed to perform the conversion is SWbemDateTime. Unlike the normal WMI classes which reqire gobs of IWbemClassObject::Put-ting and IWbemServices::ExecMethod-ing to call a single function, SWbemDateTime is a normal COM interface and is created in the usual way:

ISWbemDateTime* pConverter = NULL;
CoCreateInstance(CLSID_SWbemDateTime, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pConverter);

The reason I called it a naval spoon is that it only supports two formats to convert to and from: a FILETIME in the form of a BSTR; and an OLE DATE. Conversions from a CIM_DATETIME usage is simply:

// FROM a Cim_DateTime
pWbemTime->put_Value(cimDateTime); // set the value gained from Win32_OperatingSystem.InstallDate
BSTR fileTime;
pWbemTime->GetFileTime(bInLocalTime, &fileTime); // don't forget to SysFreeString fileTime
DATE oleDate;
pWbemTime->GetVarDate(bInLocalTime, &oleDate);

Going the other way isn’t any more difficult:

// TO a Cim_DateTime
//
// first convert the file time to a string
std::wostringstream converter;
converter << ((ULONGLONG(fileTime.dwHighPart) << 32) | fileTime.dwLowPart;
BSTR fileTimeString = SysAllocString(converter.str().c_str()); // don't forget to SysFreeString
pWbemTime->SetFileTime(fileTimeString, bInLocalTime);
BSTR cimDateTime;
pWbemTime->get_Value(&cimDateTime); // don't forget to SysFreeString
//
// even simpler from a DATE
//
pWbemTime->SetVarDate(oleDate, bInLocalTime);
BSTR cimDateTime;
pWbemTime->get_Value(&cimDateTime); // don't forget to SysFreeString

Strictly speaking, these two formats are also useless to the time functions, those take FILETIMEs as a structure with two 32-bit integers rather than a string representation of the full 64-bit number and they don’t interact with DATEs at all. This is less of a problem than the CIM_DATETIME one, as converting them to usable formats only requires an extra function in each case.

All in all, using SWbemDateTime is one the less painful and less verbose parts of WMI with pretty much a one-to-one mapping between VBScript lines and C++ lines. As a full example, here’s the VBScript above, translated into C++:

#define WIN32_LEAN_AND_MEAN
#define _UINCODE
#define UNICODE
#define _WIN32_WINNT 0x0501
#define WIN32_DCOM
#include <windows.h>
#include <iostream>
#include <sstream>
#include <malloc.h>
#include <cstdlib>
#include <ole2.h>
#include <wbemidl.h>
 
#pragma comment(lib, "wbemuuid.lib")
#pragma comment(lib, "oleaut32.lib")
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "kernel32.lib")
 
void ExitOnFailedHR(HRESULT hr, const char* condition, const char* file, int line)
{
    if(FAILED(hr))
    {
        std::cerr << file << ':' << line << ' ' << condition << " failed with hresult error 0x" << std::hex << hr << std::endl;
        exit(static_cast<int>(hr));
    }
}
#define EXIT_ON_FAILED_HR(x) ExitOnFailedHR((x), #x, __FILE__, __LINE__)
 
template<class VariantRet, class DataType>
void GetFromWBemClass_(
    IWbemClassObject* pObj,
    LPCWSTR name,
    VariantRet (VARIANT::* accessor),
    DataType& data
)
{
    VARIANT vt;
    VariantInit(&vt);
    CIMTYPE varType = CIM_EMPTY;
    EXIT_ON_FAILED_HR(pObj->Get(name, 0, &vt, &varType, NULL));
    if((varType != CIM_EMPTY) && (varType != CIM_ILLEGAL) && (V_VT(&vt) != VT_NULL))
    {
        // fun with pointer to members
        data = vt.*accessor;
    }
    else VariantClear(&vt);
}
 
// helper macro
#define GetFromWBemClass(pObj, name, variantField, data) \
    GetFromWBemClass_(pObj, name, &VARIANT::##variantField##, data)
 
// A stack version of SysAllocString
#define DECL_STACK_BSTR(name, wideString) \
    BSTR name = NULL; \
    { \
        UINT bytesReq = sizeof(wideString) + sizeof(UINT); \
        UINT* temp = static_cast<UINT*>(_alloca(bytesReq)); \
        *temp = bytesReq - sizeof(UINT); \
        ++temp; \
        wmemcpy((WCHAR*)temp, wideString, ARRAYSIZE(wideString)); \
        name = (BSTR)temp; \
    }
 
void PrintSystemTime(const SYSTEMTIME& sysTime)
{
    std::cout 
        << "Day: " << sysTime.wDay
        << "\nMonth: " << sysTime.wMonth
        << "\nYear: " << sysTime.wYear
        << "\nTime: " << sysTime.wHour << ':' << sysTime.wMinute << ':' << sysTime.wSecond
        << std::endl;
}
 
void PrintOLEDateAndConversions(DATE oleDate)
{
    std::cout << "In OLE Date format, the install date was " << oleDate << '\n';
    // you can turn an OLE date to SYSTEMTIME using this simple function
    SYSTEMTIME st;
    VariantTimeToSystemTime(oleDate, &st);
    std::cout << "OLE Date -> SystemTime returned\n";
    PrintSystemTime(st);
    FILETIME ft;
    SystemTimeToFileTime(&st, &ft);
    std::cout << "OLE Date -> FileTime returned\n";
    std::cout << ((ULONGLONG(ft.dwHighDateTime) << 32) | ft.dwLowDateTime) << " 100-nansecond intervals since Jan 1, 1601\n";
}
 
void PrintBSTRFileTimeAndConversions(BSTR bstrTime)
{
    std::wcout << L"In BSTR Filetime format, the install date was \"" << bstrTime << L"\"\n";
    // you can turn an BSTR filetime to a normal file time like this
    // or you could also use wcstoull or StrToInt64Ex
    ULARGE_INTEGER temp;
    std::wstringstream converter;
    converter << bstrTime;
    converter >> temp.QuadPart;
    // copy to a filetime struct
    FILETIME ft = {temp.LowPart, temp.HighPart};
    SYSTEMTIME st;
    FileTimeToSystemTime(&ft, &st);
    // print it out
    std::cout << "BSTR Filetime -> SystemTime returned\n";
    PrintSystemTime(st);
    std::cout << "BSTR FileTime -> FileTime returned\n";
    // you could also have printed temp.QuadPart here instead of the bit shifting and or-ing
    std::cout << ((ULONGLONG(ft.dwHighDateTime) << 32) | ft.dwLowDateTime) << " 100-nansecond intervals since Jan 1, 1601\n";
}
 
void PrintDateConversions(BSTR cimDate)
{
    std::wcout << L"In CIM_DATETIME format, the install date was " << cimDate << L'\n';
    // create our SWbemDateTime
    ISWbemDateTime* pTime = NULL;
    EXIT_ON_FAILED_HR(CoCreateInstance(CLSID_SWbemDateTime, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pTime)));
    // set the value to convert from
    pTime->put_Value(cimDate);
    // convert it to an OLE DATE
    DATE oleDate = 0;
    pTime->GetVarDate(VARIANT_TRUE, &oleDate);
    // print and convert the ole date
    PrintOLEDateAndConversions(oleDate);
    // and then convert it to a stringified filetime
    BSTR fileTimeBSTR;
    pTime->GetFileTime(VARIANT_TRUE, &fileTimeBSTR);
    // and print and convert that
    PrintBSTRFileTimeAndConversions(fileTimeBSTR);
    // cleanup
    SysFreeString(fileTimeBSTR);
    pTime->Release();
}
 
int main()
{
    // init com
    EXIT_ON_FAILED_HR(CoInitializeEx(NULL, COINIT_MULTITHREADED));
    // initialize default security
    EXIT_ON_FAILED_HR(CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL));
    // create the connector
    IWbemLocator* pLoc = NULL;
    EXIT_ON_FAILED_HR(CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pLoc)));
    IWbemServices* pServices = NULL;
    // connect to default wmi namespace
    DECL_STACK_BSTR(rootNamespace, L"root\\cimv2");
    EXIT_ON_FAILED_HR(pLoc->ConnectServer(rootNamespace, NULL, NULL, NULL, 0, NULL, NULL, &pServices));
    // no more need for the the locator
    pLoc->Release();
    // query for the OS details, only one instance is ever returned
    DECL_STACK_BSTR(wql, L"WQL");
    DECL_STACK_BSTR(osQuery, L"Select * from Win32_OperatingSystem");
    IEnumWbemClassObject* pEnum = NULL;
    EXIT_ON_FAILED_HR(pServices->ExecQuery(wql, osQuery, WBEM_FLAG_FORWARD_ONLY, NULL, &pEnum));
    IWbemClassObject* pWin32Os = NULL;
    ULONG returned = 0;
    EXIT_ON_FAILED_HR(pEnum->Next(WBEM_INFINITE, 1, &pWin32Os, &returned));
    // get the InstallDate property from the class
    BSTR installDate;
    GetFromWBemClass(pWin32Os, L"InstallDate", bstrVal, installDate);
    // clean up the interfaces
    pEnum->Release();
    pWin32Os->Release();
    pServices->Release();
    // print the date
    PrintDateConversions(installDate);
    // final cleanup
    SysFreeString(installDate);
    CoUninitialize();
    return 0;
}
    Notes

  • The S prefix in SWbemDateTime means the object is marked as safe for scripting
  • SWbemDateTime isn’t available on Windows 2000
  • You can run WMI queries without scripting or code by running WBemTest from the command line

No Comments

No comments yet.

RSS feed for comments on this post.

Sorry, the comment form is closed at this time.

Powered by WordPress