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))
{
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);
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;
CComInitializer comInitizlier;
if (!comInitizlier.IsInitialized())
{
return false;
}
HRESULT hres = ::CoInitializeSecurity(
NULL,
-1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL );
if (FAILED(hres))
{
wcout << "Failed to initialize security. Error code = 0x"
<< hex << hres << endl;
return false; }
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; }
CComPtr<IWbemServices> wmiService = NULL;
hres = wmiNamespaceLocator->ConnectServer(
_bstr_t(L"ROOT\\CIMV2"), NULL, NULL, 0, NULL, 0, 0, &wmiService );
if (FAILED(hres))
{
wcout << "Could not connect. Error code = 0x"
<< hex << hres << endl;
return false; }
hres = CoSetProxyBlanket(
wmiService, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE );
if (FAILED(hres))
{
wcout << "Could not set proxy blanket. Error code = 0x"
<< hex << hres << endl;
return false; }
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; }
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);
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