Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VC10.0

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

4.69/5 (11 votes)
5 Nov 2013CPOL2 min read 36.2K  
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.

C++
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.

C++
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):

C++
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.

C++
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.

C++
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.

C++
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)