Introduction
Starting With Windows 8.1 you can no longer use a block of code that looks like this.
OSVERSIONINFOEX versionInfo;
versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
OSVERSIONINFO* pVersionInfo = (POSVERSIONINFO)&versionInfo;
if(::GetVersionEx(pVersionInfo))
{
wcout<<_T("MajorVersion:")<<versionInfo.dwMajorVersion<<endl;
wcout<<_T("MinorVersion:")<<versionInfo.dwMinorVersion<<endl;
}
Please Look Update Section for better ways to achieve this.
Background
I recently upgraded my development box to Windows8.1 from
Windows 8. I was trying to log the version of Windows to the product’s logfile
and suddenly I am faced with an issue where the Windows Version I retrieve from
GetVersionEx
API is actually associated with Windows 8. That is I expected Version 6.3 =
Windows8.1 but I was getting Version 6.2 = Windows8. It was impossible to
believe such an important API would be malfunctioning, so using Hyper-V I created
a Windows8.1 Image from scratch to make sure this was not an issue associated
with Windows Upgrade. But the issue persisted, so I upgraded to Visual Studio
2013, hoping that would solve the issue. The issue persisted and that’s
when I found the following Article
on msdn describing Microsoft’s deprecation of GetVersionEx API. Actually Visual 2013 Marks
GetVersionEx and GetVersion APIs as deprecated and throws an Error if you reference it. Moreover .Net
Framework’s API to retrieve OS version is also “deprecated” (returning inaccurate data). Let’s just say
this move is a big surprise by Microsoft. How is a Web Browser Supposed to Report
an accurate User Agent?
how is a product supposed to report accurate Telemetry Data without obtaining
Operating System version. In My particular case, how am I supposed to log the
version of OS in Diagnostic log files that customer would be shipping to me?
All these Questions led me to believe that Microsoft's Version Helper API that
Ships with Windows
8.1 SDK are not sufficient. But they do provide a very good clue that VerifyVersionInfo
can indeed be used to fill the void.
I decided against doing dynamic LoadLibrary and GetProcAddress Technique to use
"undocumented" from Kernelbase.dll,Kernel32.dll, shell32.dll or shcore.dll and using internal APIs there. It also seemed
not the best idea to load the version of Windows from someplace in Registry. Another
approach would have been to load any key dll that resides in %systemroot%\ system32
folder and then use an API such as DllGetVersion
to retrieve Product Version from that DLL. but I did not like that idea either because a dll itself and can get hotfixed and potentially have different version (I have not seen this occur).
Using the code
Disclaimer: This is not Production-ready code!
If you look close at VerifyVersionInfo and its sister API VerSetConditionMask
there is useful flag called VER_EQUAL
.
We can verify the System is Running Windows 8.1 by using VER_EQUAL FLAG.
BOOL EqualsMajorVersion(DWORD majorVersion)
{
OSVERSIONINFOEX osVersionInfo;
::ZeroMemory(&osVersionInfo, sizeof(OSVERSIONINFOEX));
osVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
osVersionInfo.dwMajorVersion = majorVersion;
ULONGLONG maskCondition = ::VerSetConditionMask(0, VER_MAJORVERSION, VER_EQUAL);
return ::VerifyVersionInfo(&osVersionInfo, VER_MAJORVERSION, maskCondition);
}
BOOL EqualsMinorVersion(DWORD minorVersion)
{
OSVERSIONINFOEX osVersionInfo;
::ZeroMemory(&osVersionInfo, sizeof(OSVERSIONINFOEX));
osVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
osVersionInfo.dwMinorVersion = minorVersion;
ULONGLONG maskCondition = ::VerSetConditionMask(0, VER_MINORVERSION, VER_EQUAL);
return ::VerifyVersionInfo(&osVersionInfo, VER_MINORVERSION, maskCondition);
}
int _tmain(int argc, _TCHAR* argv[])
{
if (EqualsMajorVersion(6) && EqualsMinorVersion(3))
{
wcout << _T("System is Windows 8.1");
}
return 0;
}
That Works and in fact that's very similar to the Mechanism used by VersionHelpers.h From Windows 8.1 SDK.
ERSIONHELPERAPI
IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor)
{
OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0 };
DWORDLONG const dwlConditionMask = VerSetConditionMask(
VerSetConditionMask(
VerSetConditionMask(
0, VER_MAJORVERSION, VER_GREATER_EQUAL),
VER_MINORVERSION, VER_GREATER_EQUAL),
VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
osvi.dwMajorVersion = wMajorVersion;
osvi.dwMinorVersion = wMinorVersion;
osvi.wServicePackMajor = wServicePackMajor;
return VerifyVersionInfoW(&osvi, VER_MAJORVERSION |
VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE;
}
So You might have noticed Windows Versions have been historically predictable. By That I mean we had Windows 3.0, Windows 3.1, Windows NT, 95(4.0), Windows 2000 (5.0), Windows XP (5.1), .... Windows 7(6.1), ..... , Windows 8.1 (6.3). It's not like previous Version was 100.0 and the next version is 213.0. so it fair to say with a small number of iterations and incrementing of a base known version we should able to get the version of windows that we are running on.
So Here are three More Helper Functions to Get ServicePack and ascertain whether this Windows is Workstation or Windows Server.
BOOL EqualsServicePack(WORD servicePackMajor)
{
OSVERSIONINFOEX osVersionInfo;
::ZeroMemory(&osVersionInfo, sizeof(OSVERSIONINFOEX));
osVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
osVersionInfo.wServicePackMajor = servicePackMajor;
ULONGLONG maskCondition = ::VerSetConditionMask(0, VER_SERVICEPACKMAJOR, VER_EQUAL);
return ::VerifyVersionInfo(&osVersionInfo, VER_SERVICEPACKMAJOR, maskCondition);
}
BOOL EqualsProductType(BYTE productType)
{
OSVERSIONINFOEX osVersionInfo;
::ZeroMemory(&osVersionInfo, sizeof(OSVERSIONINFOEX));
osVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
osVersionInfo.wProductType = productType;
ULONGLONG maskCondition = ::VerSetConditionMask(0, VER_PRODUCT_TYPE, VER_EQUAL);
return ::VerifyVersionInfo(&osVersionInfo, VER_PRODUCT_TYPE, maskCondition);
}
BYTE GetProductType()
{
if (EqualsProductType(VER_NT_WORKSTATION))
{
return VER_NT_WORKSTATION;
}
else if (EqualsProductType(VER_NT_SERVER))
{
return VER_NT_SERVER;
}
else if (EqualsProductType(VER_NT_DOMAIN_CONTROLLER))
{
return VER_NT_DOMAIN_CONTROLLER;
}
return 0;}
VerifyVersionInfo API was relased on Windows 2000 (Windows 5.0) so we can setup an array that contains known version of Windows back to Windows 200 and use this API to verify them.
struct WindowsNTOSInfo
{
DWORD dwMajorVersion;
DWORD dwMinorVersion;
WORD wServicePackMajor;
};
struct WindowsNTOSInfoEx :WindowsNTOSInfo
{
BYTE ProductType;
};
const WindowsNTOSInfo KnownVersionsOfWindows[] =
{
{ 6, 3, 0, }, { 6, 2, 0, },
{ 6, 1, 1, }, { 6, 1, 0, },
{ 5, 1, 3, }, { 5, 1, 2, }, { 5, 1, 1, }, { 5, 1, 0, },
{ 6, 0, 2, }, { 6, 0, 1, }, { 6, 0, 0, },
{ 5, 2, 2, }, { 5, 2, 1, }, { 5, 2, 0, },
{ 5, 1, 4, }, { 5, 1, 3, }, { 5, 1, 2, }, { 5, 1, 2, }, { 5, 1, 0, }, };
const size_t n_KnownVersionofWindows = sizeof(KnownVersionsOfWindows) / sizeof(WindowsNTOSInfo);
So Here the Code that retrieves a known Version of Windows.
bool GetKnownWindowsVersion(WindowsNTOSInfoEx& osInfo)
{
for (size_t i = 0; i < n_KnownVersionofWindows; i++)
{
if (EqualsMajorVersion(KnownVersionsOfWindows[i].dwMajorVersion))
{
if (EqualsMinorVersion(KnownVersionsOfWindows[i].dwMinorVersion))
{
if (EqualsServicePack(KnownVersionsOfWindows[i].wServicePackMajor))
{
osInfo.dwMajorVersion = KnownVersionsOfWindows[i].dwMajorVersion;
osInfo.dwMinorVersion = KnownVersionsOfWindows[i].dwMinorVersion;
osInfo.wServicePackMajor = KnownVersionsOfWindows[i].wServicePackMajor;
osInfo.ProductType = GetProductType();
return true;
}
}
}
}
return false;
}
But What about checking unkown version of Windows?
well for that we need to write a few loops and call our helper functions to Verify Major,Minor and Service Pack.
const DWORD MajorVersion_Start = 6;
const DWORD MinorVersion_Start = 3;
const WORD ServicePackVersion_Start = 1;
const DWORD MajorVersion_Max = 10;
const DWORD MinorVersion_Max = 5;
const WORD ServicePackVersion_Max = 4;
bool GetUnkownVersion(WindowsNTOSInfoEx& osInfo)
{
DWORD minorVersionCounterSeed = MinorVersion_Start;
DWORD servicePackCounterSeed = ServicePackVersion_Start;
for (DWORD majorVersion = MajorVersion_Start; majorVersion <= MajorVersion_Max; majorVersion++)
{
if (EqualsMajorVersion(majorVersion))
{
for (DWORD minorVersion = minorVersionCounterSeed;
minorVersion <= MinorVersion_Max; minorVersion++)
{
if (EqualsMinorVersion(minorVersion))
{
osInfo.dwMajorVersion = majorVersion;
osInfo.dwMinorVersion = minorVersion;
osInfo.ProductType = GetProductType();
for (WORD servicePack = servicePackCounterSeed;
servicePack <= ServicePackVersion_Max; servicePack++)
{
if (EqualsServicePack(servicePack))
{
osInfo.wServicePackMajor = servicePack;
break;
}
}
return true;
}
else
{
servicePackCounterSeed = 0;
}
}
}
else
{
minorVersionCounterSeed = 0;
}
}
return false;
}
So then let's just call the before mentioned code with a function to glue them together.
bool GetWindowsVersion(WindowsNTOSInfoEx& osInfo)
{
bool capturedWinVersion = GetKnownWindowsVersion(osInfo);
if (!capturedWinVersion)
{
return GetUnkownVersion(osInfo);
}
return capturedWinVersion;
}
int _tmain(int argc, _TCHAR* argv[])
{
WindowsNTOSInfoEx osInfo;
::ZeroMemory(&osInfo,sizeof(WindowsNTOSInfoEx));
if (GetWindowsVersion(osInfo))
{
wcout << _T("MajorVersion:") << osInfo.dwMajorVersion <<
_T(" VersionMinor:") << osInfo.dwMinorVersion <<
_T(" ServicePackMajor:") << osInfo.wServicePackMajor <<
_T(" ProductType:") << osInfo.ProductType << endl;
}
else
{
wcout << _T("Failed to Detect Windows Version") << endl;
}
return 0;
}
Note:I have only smoke tested this code on Windows 8.1 and Windows XP SP3.
Points of Interest
Performance: This code is actually quite slow!
It can take anywhere from 5-15 Milliseconds on a Physical System. It took upward of 30 Milliseconds on a Virtual Machine to Execute.
Since This API can be can called many times during an Application's life cycle I have included a High Resolution Timer code to measure how lengthy of an operation this is. It appears Calling VerifyVersionInfo is much more expensive the first time. The cost of subsequent calls appear to be significantly less than the First Call.
BuildNumber: I have not seen people attach significant importance to the BuildNumber of Windows and with this approach retrieving BuildNumber can become really expensive. so I skipped over BuildNumber.
I will include another article to fetch Windows Version. But that approach uses COM and may not be desired by some.
A User on this board has provided a better way to do this.
if you include an embeded manifest file as part of your .exe program. GetVersionEx will return the right version on Windows8.1. Note that you can support other operating systems as well. but you must make sure Windows 8.1 is inclucded.
="1.0"="UTF-8"="yes"
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
</application>
</compatibility>
<assemblyIdentity type="win32"
name="SampleCode.GetWindowsVersionInfo"
version="1.0.0.0"
processorArchitecture="x86"
publicKeyToken="0000000000000000"
/>
</assembly>
The Great thing about this approach is that the performance of GetVersionEx is fantastic.
But the issue still remains that your binaries will always missbehave on Newer Version of Windows (since you don't know newever Version of Windows Guid ahead of time).
Also One More issue is that you can not create an application that runs in Compatiblity Mode with previous Windows OS.
you will have to get rid of Visual Studio Deprecation Errors by disabling by doing something like this.
#pragma warning (disable : 4996)
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;
}
}
#pragma warning (default : 4996)
I have also discovered another API that still returns Windows Version without any compatability Manifests required. But it takes a 2-3 miliseconds to execute so it is significantly slower than GetVersionEx API.
bool GetWindowsVersion(DWORD& major, DWORD& minor)
{
LPBYTE pinfoRawData;
if (NERR_Success == NetWkstaGetInfo(NULL, 100, &pinfoRawData))
{
WKSTA_INFO_100 * pworkstationInfo = (WKSTA_INFO_100 *)pinfoRawData;
major = pworkstationInfo->wki100_ver_major;
minor = pworkstationInfo->wki100_ver_minor;
::NetApiBufferFree(pinfoRawData);
return true;
}
return false;
}
int _tmain(int argc, _TCHAR* argv[])
{
DWORD major = 0;
DWORD minor = 0;
if (GetWindowsVersion(major, minor))
{
wcout << _T("Major:") << major << _T("Minor:") << minor << endl;
}
return 0;
}