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