Part 2: Overcoming Windows 8.1's Deprecation of GetVersionEx and GetVersion APIs






4.69/5 (10 votes)
Get Windows Version from WMI
Introduction
As I mentioned in Part 1 of this series with Windows 8.1, a code sample like this will always return inaccurate data. That’s because GetVersionEx
and GetVersionAPI
will always indicate presence of Windows 8.0, not Windows 8.1 or greater.
OSVERSIONINFOEX versionInfo;
versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
POSVERSIONINFO pVersionInfo = (POSVERSIONINFO)&versionInfo;
if(::GetVersionEx(pVersionInfo))
{
//test to see if this is windows 8.1 fails
if(6 == versionInfo.dwMajorVersion && 3 == versionInfo.dwMinorVersion)
{
wcout<<_T("Windows 8.1 Detected")<<endl;
}
else if(6 == versionInfo.dwMajorVersion && 2 == versionInfo.dwMinorVersion)
{
wcout<<_T("Windows 8.0 Detected")<<endl;
}
}
In Part 1, I had mentioned that there is an alternative way that involves use of COM. This method may be easier to adapt to be used on .NET Framework but that is out of the scope of this article. This method has one downside - it involves use of WMI which means Windows Management Instrumentation cannot be disabled. To be clear if WMI Service is stopped, WMI Service will be started automatically. However, if because of some consideration, you decided to disable WMI Service this method would not be a viable method to retrieve Windows version.
Credit: I used some Microsoft WMI code samples in this code block.
Background
Please look at Part 1.
Using the Code
First, we need to make sure COM gets initialized and destroyed.
class CComInitializer
{
public:
CComInitializer() :m_bInitialized(false)
{
HRESULT hres = CoInitializeEx(0, COINIT_MULTITHREADED);
m_bInitialized = SUCCEEDED(hres);
}
virtual ~CComInitializer()
{
if (m_bInitialized)
{
CoUninitialize();
m_bInitialized = false;
}
}
bool IsInitialized()const
{
return m_bInitialized;
}
bool m_bInitialized;
};
We also need a way to get STL string
out of Com Variant (could have used ATL's CComVariant
instead):
wstring GetWmiPropertyValue
(LPCTSTR pcszPropertyName, CComPtr<IWbemClassObject> clsObj)
{
VARIANT vtProp;
wstring strPropValue;
HRESULT hr = clsObj->Get(pcszPropertyName, 0, &vtProp, 0, 0);
if (SUCCEEDED(hr))
{
if (vtProp.vt == VT_BSTR)
{
strPropValue = vtProp.bstrVal;
}
else if (vtProp.vt == VT_UI2)
{
TCHAR cszVersion[20] = { 0 };
_itow_s(vtProp.uiVal, &cszVersion[0], 20, 10);
strPropValue = cszVersion;
}
else if (vtProp.vt == VT_I4)
{
TCHAR cszVersion[20] = { 0 };
_itow_s(vtProp.uiVal, &cszVersion[0], 20, 10);
strPropValue = cszVersion;
}
VariantClear(&vtProp);
return strPropValue;
}
return _T("Error");
}
We also need a function that would convert a null
-terminated string
to a set 32-bit values that would represent the version. I am sure this function has lots of room for added robustness.
bool GetVersionFromString(LPCTSTR pcszVersion, DWORD&
majorVersion,DWORD& minorVersion,DWORD& buildNumber)
{
const TCHAR chVersionSeparator = '.';
majorVersion = 0;
minorVersion = 0;
buildNumber = 0;
const size_t nMaxVersionParts = 3;
const size_t versionMaxLen = 10;
const size_t versionMaxLenTerm = versionMaxLen + 1;
TCHAR szVersion[versionMaxLenTerm] = { 0 };
const TCHAR cszNullTerm = _T('\0');
if (pcszVersion != NULL)
{
size_t lenTotal = _tcslen(pcszVersion);
size_t counter = 0;
const TCHAR * pczSeparatorLocation = NULL;
size_t offset = 0;
do
{
counter++;
const TCHAR * pcszStartSearchFrom = pcszVersion + offset;
pczSeparatorLocation =
_tcschr(pcszStartSearchFrom, chVersionSeparator);
if (pczSeparatorLocation != NULL)
{
size_t len = pczSeparatorLocation - pcszStartSearchFrom;
_tcsnset_s(szVersion,
versionMaxLenTerm,cszNullTerm, versionMaxLenTerm);
_tcsncpy_s(szVersion,
versionMaxLenTerm, pcszStartSearchFrom, len);
if (cszNullTerm != szVersion)
{
int intValue = _tstoi(szVersion);
if (1 == counter)
{
majorVersion = intValue;
}
else if (2 == counter)
{
minorVersion = intValue;
}
else if (2 == counter)
{
buildNumber = intValue;
break;
}
}
else
{
break;
}
offset = (pczSeparatorLocation - pcszVersion)+1;
if (offset >= lenTotal)
{
break;
}
}
else
{
int intValue = _wtoi(pcszStartSearchFrom);
if (1 == counter)
{
majorVersion = intValue;
}
else if (2 == counter)
{
minorVersion = intValue;
}
else if (3 == counter)
{
buildNumber = intValue;
}
break;
}
} while (pczSeparatorLocation != NULL && (pcszVersion + lenTotal) >
pczSeparatorLocation && nMaxVersionParts > counter);
// buildnumber without major or minor version would make no sense.
return (majorVersion >0 || minorVersion >0);
}
return false;
}
Finally, our HelperFunction
that initializes WMI Service Client executes a query and retrieves the version from WMI's Win32_OperatingSystem
class. Towards the end of this code snippet, I have bolded a section towards the bottom which is actually where most of the "magic" happens. Make sure you do not retrieve more than you need from Win32_OperatingSystem
class because some of the other values such as How much Free Ram your system has could slow things down.
bool GetWindowsVersionFromWMI(DWORD& majorVersion,
DWORD& minorVersion, DWORD& buildNumber)
{
majorVersion = 0;
minorVersion = 0;
buildNumber = 0;
//Com Init
CComInitializer comInitizlier;
if (!comInitizlier.IsInitialized())
{
return false;
}
HRESULT 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
);
if (FAILED(hres))
{
wcout << "Failed to initialize security. Error code = 0x"
<< hex << hres << endl;
return false; // Program has failed.
}
// Obtain the initial locator to WMI -------------------------
CComPtr<IWbemLocator> wmiNamespaceLocator;
hres = ::CoCreateInstance(
CLSID_WbemLocator,
0,
CLSCTX_INPROC_SERVER,
IID_IWbemLocator, (LPVOID *)&wmiNamespaceLocator);
if (FAILED(hres))
{
wcout << "Failed to create IWbemLocator object."
<< " Err code = 0x"
<< hex << hres << endl;
return false; // Program has failed.
}
// Step 4: -----------------------------------------------------
// Connect to WMI through the IWbemLocator::ConnectServer method
CComPtr<IWbemServices> wmiService = NULL;
// Connect to the root\cimv2 namespace with
// the current user and obtain pointer pSvc
// to make IWbemServices calls.
hres = wmiNamespaceLocator->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
&wmiService // pointer to IWbemServices proxy
);
if (FAILED(hres))
{
wcout << "Could not connect. Error code = 0x"
<< hex << hres << endl;
return false; // Program has failed.
}
// Step 5: --------------------------------------------------
// Set security levels on the proxy -------------------------
hres = CoSetProxyBlanket(
wmiService, // Indicates the proxy to set
RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx
RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx
NULL, // Server principal name
RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx
RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
NULL, // client identity
EOAC_NONE // proxy capabilities
);
if (FAILED(hres))
{
wcout << "Could not set proxy blanket. Error code = 0x"
<< hex << hres << endl;
return false; // Program has failed.
}
// Step 6: --------------------------------------------------
// Use the IWbemServices pointer to make requests of WMI ----
CComPtr<IEnumWbemClassObject> wmiClassEnumerator = NULL;
hres = wmiService->ExecQuery(
bstr_t("WQL"),
bstr_t("SELECT Version FROM Win32_OperatingSystem"),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&wmiClassEnumerator);
if (FAILED(hres))
{
wcout << "Query for operating system name failed."
<< " Error code = 0x"
<< hex << hres << endl;
return false; // Program has failed.
}
//get query Data
//no reason to loop through since we only one row worth of data.
if (wmiClassEnumerator)
{
ULONG uReturn = 0;
CComPtr<IWbemClassObject> clsObj;
HRESULT hr = wmiClassEnumerator->Next(WBEM_INFINITE, 1,
&clsObj, &uReturn);
if (0 == uReturn)
{
return false;
}
wstring strOSVersion = GetWmiPropertyValue(_T("Version"), clsObj);
//wcout << "
//OS Version : " << strOSVersion.c_str() << endl;
return GetVersionFromString(strOSVersion.c_str(),
majorVersion, minorVersion, buildNumber);
}
return false;
}
and of course, our main function that glues it all together.
int _tmain(int argc, _TCHAR* argv[])
{
DWORD majorVersion=0, minorVersion=0,buildNumber=0;
if (GetWindowsVersionFromWMI(majorVersion, minorVersion, buildNumber))
{
wcout << "
OS MajorVersion : " << majorVersion << endl;
wcout << "
OS Minor : " << minorVersion << endl;
wcout << "
OS BuildNumber : " << buildNumber<< endl;
}
return 0;
}
Points of Interest
Assuming my High Resolution Timer code is correct, this method of fetching Windows Version takes about 30-70 milliseconds which is 2-4 times slower than Part 1. But if you have an application that utilizes WMI already this might be something that is worthwhile.
Build Number: This method has one distinct upside over Part 1 and that is the fact that you can retrieve Windows Build Number.
Please note that this code is a sample and should not be deemed as production-ready.
History
- 5th November, 2013: Initial version