user1169502
user1169502

Reputation: 398

C++ How to detect Windows 10

I have written a PC auditing tool many years ago and have been keeping it up to date. One of basic functions is reporting the version of Windows running on the PC being audited for which I have always used the GetVersionEx call.

This works up to and including Windows 8 but is not supported under Windows 10 and indeed Windows 10 returns 8.2 just as windows 8 does. Microsoft do not seem to have introduced anything as a direct replacement suggesting instead that you check for specific features required rather than looking at the OS but for the purpose of the audit I actually want the OS name.

The 'scanner' is a C++ program which must run under non-privileged accounts so I don't think another suggestion I have read - picking up the version of a system DLL such as kernel32.dll will work as these folders are typically not accessible to users.

Any other suggestions/thoughts are most welcome!

Upvotes: 28

Views: 36519

Answers (7)

Remy Lebeau
Remy Lebeau

Reputation: 598001

Starting in Windows 8.1, GetVersion() and GetVersionEx() are subject to application manifestation:

With the release of Windows 8.1, the behavior of the GetVersionEx API has changed in the value it will return for the operating system version. The value returned by the GetVersionEx function now depends on how the application is manifested.

Applications not manifested for Windows 8.1 or Windows 10 will return the Windows 8 OS version value (6.2). Once an application is manifested for a given operating system version, GetVersionEx will always return the version that the application is manifested for in future releases. To manifest your applications for Windows 8.1 or Windows 10, refer to Targeting your application for Windows.

The newer Version Helper functions are simply wrappers for VerifyVersionInfo(). Starting in Windows 10, it is now subject to manifestation as well:

Windows 10: VerifyVersionInfo returns false when called by applications that do not have a compatibility manifest for Windows 8.1 or Windows 10 if the lpVersionInfo parameter is set so that it specifies Windows 8.1 or Windows 10, even when the current operating system version is Windows 8.1 or Windows 10. Specifically, VerifyVersionInfo has the following behavior:

  • If the application has no manifest, VerifyVersionInfo behaves as if the operation system version is Windows 8 (6.2).
  • If the application has a manifest that contains the GUID that corresponds to Windows 8.1, VerifyVersionInfo behaves as if the operation system version is Windows 8.1 (6.3).
  • If the application has a manifest that contains the GUID that corresponds to Windows 10, VerifyVersionInfo behaves as if the operation system version is Windows 10 (10.0).

The Version Helper functions use the VerifyVersionInfo function, so the behavior IsWindows8Point1OrGreater and IsWindows10OrGreater are similarly affected by the presence and content of the manifest.

To manifest your applications for Windows 8.1 or Windows 10, see Targeting your application for Windows.

To get the true OS version regardless of manifestation use RtlGetVersion(), NetServerGetInfo(), or NetWkstaGetInfo() instead. They all report an accurate OS version and are not subject to manifestation (yet?).

(Microsoft used to suggest querying the file version of a system DLL, but they stopped recommending that when Windows didn't update system DLL versions to match.)

Upvotes: 17

Chris Sherlock
Chris Sherlock

Reputation: 3133

FWIW, the LibreOffice project gives the version string via getOSVersion()

OUString WinSalInstance::getOSVersion()
{
    OUStringBuffer aVer(50); // capacity for string like "Windows 6.1 Service Pack 1 build 7601"
    aVer.append("Windows ");
    // GetVersion(Ex) and VersionHelpers (based on VerifyVersionInfo) API are
    // subject to manifest-based behavior since Windows 8.1, so give wrong results.
    // Another approach would be to use NetWkstaGetInfo, but that has some small
    // reported delays (some milliseconds), and might get slower in domains with
    // poor network connections.
    // So go with a solution described at https://web.archive.org/web/20090228100958/http://msdn.microsoft.com/en-us/library/ms724429.aspx
    bool bHaveVerFromKernel32 = false;
    if (HMODULE h_kernel32 = GetModuleHandleW(L"kernel32.dll"))
    {
        wchar_t szPath[MAX_PATH];
        DWORD dwCount = GetModuleFileNameW(h_kernel32, szPath, SAL_N_ELEMENTS(szPath));
        if (dwCount != 0 && dwCount < SAL_N_ELEMENTS(szPath))
        {
            dwCount = GetFileVersionInfoSizeW(szPath, nullptr);
            if (dwCount != 0)
            {
                std::unique_ptr<char[]> ver(new char[dwCount]);
                if (GetFileVersionInfoW(szPath, 0, dwCount, ver.get()) != FALSE)
                {
                    void* pBlock = nullptr;
                    UINT dwBlockSz = 0;
                    if (VerQueryValueW(ver.get(), L"\\", &pBlock, &dwBlockSz) != FALSE && dwBlockSz >= sizeof(VS_FIXEDFILEINFO))
                    {
                        VS_FIXEDFILEINFO* vi1 = static_cast<VS_FIXEDFILEINFO*>(pBlock);
                        aVer.append(OUString::number(HIWORD(vi1->dwProductVersionMS)) + "."
                                    + OUString::number(LOWORD(vi1->dwProductVersionMS)));
                        bHaveVerFromKernel32 = true;
                    }
                }
            }
        }
    }
    // Now use RtlGetVersion (which is not subject to deprecation for GetVersion(Ex) API)
    // to get build number and SP info
    bool bHaveVerFromRtlGetVersion = false;
    if (HMODULE h_ntdll = GetModuleHandleW(L"ntdll.dll"))
    {
        if (auto RtlGetVersion
            = reinterpret_cast<RtlGetVersion_t>(GetProcAddress(h_ntdll, "RtlGetVersion")))
        {
            RTL_OSVERSIONINFOW vi2{}; // initialize with zeroes - a better alternative to memset
            vi2.dwOSVersionInfoSize = sizeof(vi2);
            if (STATUS_SUCCESS == RtlGetVersion(&vi2))
            {
                if (!bHaveVerFromKernel32) // we failed above; let's hope this would be useful
                    aVer.append(OUString::number(vi2.dwMajorVersion) + "."
                                + OUString::number(vi2.dwMinorVersion));
                aVer.append(" ");
                if (vi2.szCSDVersion[0])
                    aVer.append(OUString::Concat(o3tl::toU(vi2.szCSDVersion)) + " ");
                aVer.append("Build " + OUString::number(vi2.dwBuildNumber));
                bHaveVerFromRtlGetVersion = true;
            }
        }
    }
    if (!bHaveVerFromKernel32 && !bHaveVerFromRtlGetVersion)
        aVer.append("unknown");
    return aVer.makeStringAndClear();
}

Upvotes: 0

user541686
user541686

Reputation: 210755

Do not use VersionHelpers.h! It's buggy!

It ignores the user's application compatibility settings.

Instead, use the older Kernel32.dll functions like GetVersion, e.g.:

bool IsWindowsVersionOrGreater(unsigned short version)
{
    return _byteswap_ushort((unsigned short)GetVersion()) >= version;
}

// Usage: IsWindowsVersionOrGreater(_WIN32_WINNT_WINTHRESHOLD)

Upvotes: 2

赫敏璋
赫敏璋

Reputation: 81

2021-01-12 https://stackoverflow.com/a/52122386/1923561 Based on Michael Haephrati's answer, I made adjustments to my code.

enum WindowsOS{
   NotFind,
   Win2000,
   WinXP,
   WinVista,
   Win7,
   Win8,
   Win10
};

WindowsOS GetOsVersionQuick()
{
   using namespace std;
   double ret = 0.0;
   NTSTATUS(WINAPI *RtlGetVersion)(LPOSVERSIONINFOEXW);
   OSVERSIONINFOEXW osInfo;

   *(FARPROC*)&RtlGetVersion = GetProcAddress(GetModuleHandleA("ntdll"), 
   "RtlGetVersion");

   if (NULL != RtlGetVersion)
   {
      osInfo.dwOSVersionInfoSize = sizeof(osInfo);
      RtlGetVersion(&osInfo);
      ret = (double)osInfo.dwMajorVersion;
   }

   if (osInfo.dwMajorVersion == 10 && osInfo.dwMinorVersion == 0)
   {
      cout << "this is windows 10\n";
      return Win10;
   }
   else if (osInfo.dwMajorVersion == 6 && osInfo.dwMinorVersion == 3)
   {
      cout << "this is windows 8.1\n";
      return Win8;
   }
   else if (osInfo.dwMajorVersion == 6 && osInfo.dwMinorVersion == 2)
   {
      cout << "this is windows 8\n";
      return Win8;
   }
   else if (osInfo.dwMajorVersion == 6 && osInfo.dwMinorVersion == 1)
   {
      cout << "this is windows 7 or Windows Server 2008 R2\n";
      return Win7;
   }

   return NotFind;
}

2020-06-14

#include <iostream>
#include <windows.h>
#pragma comment(lib, "Version.lib" )

BOOL GetOsVersion()
{
    wchar_t path[200] = L"C:\\Windows\\System32\\kernel32.dll";
    DWORD dwDummy;
    DWORD dwFVISize = GetFileVersionInfoSize(path, &dwDummy);
    LPBYTE lpVersionInfo = new BYTE[dwFVISize];
    if (GetFileVersionInfo(path, 0, dwFVISize, lpVersionInfo) == 0)
    {
        return FALSE;
    }

    UINT uLen;
    VS_FIXEDFILEINFO* lpFfi;
    BOOL bVer = VerQueryValue(lpVersionInfo, L"\\", (LPVOID*)&lpFfi, &uLen);

    if (!bVer || uLen == 0)
    {
        return FALSE;
    }
    DWORD dwProductVersionMS = lpFfi->dwProductVersionMS;
    if (HIWORD(dwProductVersionMS) == 10 && LOWORD(dwProductVersionMS) == 0)
    {
        cout << "this is windows 10\n";
    }
    else if (HIWORD(dwProductVersionMS) == 6 && LOWORD(dwProductVersionMS) == 3)
    {
        cout << "this is windows 8.1\n";
    }
    else if (HIWORD(dwProductVersionMS) == 6 && LOWORD(dwProductVersionMS) == 2)
    {
        cout << "this is windows 8\n";
    }
    else if (HIWORD(dwProductVersionMS) == 6 && LOWORD(dwProductVersionMS) == 1)
    {
        cout << "this is windows 7 or Windows Server 2008 R2\n";
    }
    else if (HIWORD(dwProductVersionMS) == 6 && LOWORD(dwProductVersionMS) == 0)
    {
        cout << "this is windows Vista or Windows Server 2008\n";
    }
    else if (HIWORD(dwProductVersionMS) == 5 && LOWORD(dwProductVersionMS) == 2)
    {
        cout << "this is windows Server 2003\n";
    }
    else if (HIWORD(dwProductVersionMS) == 5 && LOWORD(dwProductVersionMS) == 1)
    {
        cout << "this is windows Server XP\n";
    }
    else if (HIWORD(dwProductVersionMS) == 5 && LOWORD(dwProductVersionMS) == 0)
    {
        cout << "this is windows 2000\n";
    }
    //else if (lpFfi->dwFileVersionMS == 4 && lpFfi->dwFileVersionLS == 90)
    //{
    //    cout << "this is windows  Me\n";
    //}
    //else if (lpFfi->dwFileVersionMS == 4 && lpFfi->dwFileVersionLS == 10)
    //{
    //    cout << "this is windows  98\n";
    //}
    //else if (lpFfi->dwFileVersionMS == 4 && lpFfi->dwFileVersionLS == 0)
    //{
    //    cout << "this is windows  95\n";
    //}
    return TRUE;
}

After testing the code used to detect win10.

I speculate that this api error, IsWindows10OrGreater, is because the wrong FileVersionMS version is set for kernel32.dll. Use ProductVersionMS version query to get it normally.

https://learn.microsoft.com/en-us/windows/win32/api/versionhelpers/nf-versionhelpers-iswindows10orgreater

Hope it could help everyone!

Upvotes: 5

Michael Haephrati
Michael Haephrati

Reputation: 4245

Use the following function:

double getSysOpType()
{
    double ret = 0.0;
    NTSTATUS(WINAPI *RtlGetVersion)(LPOSVERSIONINFOEXW);
    OSVERSIONINFOEXW osInfo;

    *(FARPROC*)&RtlGetVersion = GetProcAddress(GetModuleHandleA("ntdll"), "RtlGetVersion");

    if (NULL != RtlGetVersion)
    {
        osInfo.dwOSVersionInfoSize = sizeof(osInfo);
        RtlGetVersion(&osInfo);
        ret = (double)osInfo.dwMajorVersion;
    }
    return ret;
}

It will return the Windows version as a double (7, 8, 8.1, 10).

Upvotes: 17

BuvinJ
BuvinJ

Reputation: 11086

I needed this to work on an older version of the VS compiler, and more over within a Qt framework. Here's how I accomplished that.

Add this file GetWinVersion.h to your Qt project:

#ifndef GETWINVERSION
#define GETWINVERSION

#include <QtGlobal>

#ifdef Q_OS_WIN

#include <windows.h>
#include <stdio.h>

float GetWinVersion()
{
    OSVERSIONINFO osvi;
    ZeroMemory( &osvi, sizeof(OSVERSIONINFO) );
    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    return GetVersionEx( &osvi ) ?
           (float)osvi.dwMajorVersion +
           ((float)osvi.dwMinorVersion/10) :
           0.0 ;
}

#endif //Q_OS_WIN

#endif // GETWINVERSION

Add the required linkage in your pro or pri qmake file:

msvc: LIBS += -lKernel32

Implement the helper function like so (note SystemInfo used here is a custom class of mine, but you get the idea...):

#include "GetWinVersion.h"

SystemInfo info;

#ifdef Q_OS_WIN
    info.setPlatform( SystemInfo::WINDOWS );
    switch(QSysInfo::windowsVersion())
    {
    case QSysInfo::WV_32s:        info.setOsName( L"3.1" );     info.setOsVersion( 3.1 ); break;
    case QSysInfo::WV_95:         info.setOsName( L"95" );      info.setOsVersion( 4.0 ); break;
    case QSysInfo::WV_98:         info.setOsName( L"98" );      info.setOsVersion( 4.1 ); break;
    case QSysInfo::WV_Me:         info.setOsName( L"Me" );      info.setOsVersion( 4.9 ); break;
    case QSysInfo::WV_NT:         info.setOsName( L"NT" );      info.setOsVersion( 4.0 ); break;
    case QSysInfo::WV_2000:       info.setOsName( L"2000" );    info.setOsVersion( 5.0 ); break;
    case QSysInfo::WV_XP:         info.setOsName( L"XP" );      info.setOsVersion( 5.1 ); break;
    case QSysInfo::WV_2003:       info.setOsName( L"2003" );    info.setOsVersion( 5.2 ); break;  // Windows Server 2003, Windows Server 2003 R2, Windows Home Server, Windows XP Professional x64 Edition
    case QSysInfo::WV_VISTA:      info.setOsName( L"Vista" );   info.setOsVersion( 6.0 ); break;  // Windows Vista, Windows Server 2008
    case QSysInfo::WV_WINDOWS7:   info.setOsName( L"7" );       info.setOsVersion( 6.1 ); break;  // Windows 7, Windows Server 2008 R2
    case QSysInfo::WV_WINDOWS8:   info.setOsName( L"8" );       info.setOsVersion( 6.2 ); break;  // Windows 8, Windows Server 2012
  // These cases are never reached due to Windows api changes
  // As of Qt 5.5, this not accounted for by QSysInfo::windowsVersion()
  //case QSysInfo::WV_WINDOWS8_1: info.setOsName( L"8.1" );     info.setOsVersion( 6.3 ); break;  // Windows 8.1, Windows Server 2012 R2
  //case QSysInfo::WV_WINDOWS10:  info.setOsName( L"10" );      info.setOsVersion( 10.0 ); break; // Windows 10, Windows Server 2016
    default:
        // On Windows 8.1 & 10, this will only work when the exe
        // contains a manifest which targets the specific OS's
        // you wish to detect.  Else 6.2 (ie. Win 8.0 is returned)
        info.setOsVersion( GetWinVersion() );
        if(      info.osVersion() == 6.3f )  // Windows 8.1, Windows Server 2012 R2
            info.setOsName( L"8.1" );
        else if( info.osVersion() == 10.0f ) // Windows 10, Windows Server 2016
            info.setOsName( L"10" );
        else
            info.setOsName( L"UNKNOWN" );
    }
    info.setOsBits( IsWow64() ? 64 : 32 );
#else
...

Now here's the real key. You need to attach a manifest file to your exe which will "target" the recent Windows versions, else you can't detect them (see the MS docs: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724451%28v=vs.85%29.aspx). Here's an example manifest to do this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
    <assemblyIdentity 
        name="MyOrg.MyDept.MyAppName" 
        version="1.0.0.0" 
        processorArchitecture="x86" 
        type="win32" />
    <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> 
        <application> 
            <!-- Windows 10 --> 
            <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
            <!-- Windows 8.1 -->
            <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
            <!-- Windows 8 -->
            <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
            <!-- Windows 7 -->
            <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>      
            <!-- Windows Vista -->
            <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>          
        </application> 
    </compatibility>
</assembly>

And here's some batch to attach the manifest:

set exeFile=MyApp.exe
set manifestFile=MyApp.manifest
set manifestExe=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin\x64\mt.exe

"%manifestExe%" -manifest "%manifestFile%" -outputresource:"%exeFile%"

In theory, you can use qmake to run that last bit attaching the manifest. I didn't have luck with the examples I found, and just "cheated" with this batch for now...

Upvotes: 2

Robinson
Robinson

Reputation: 10132

GetVersion and GetVersionEx were superseded by various version helper functions. The one you want is IsWindows10OrGreater. They can be found in VersionHelpers.h.

IsWindows10OrGreater is only available in the latest SDK/Visual Studio 2015. You can use IsWindowsVersionOrGreater in the general case however. For example on my 7 box I get TRUE for IsWindowsVersionOrGreater(6, 0, 0).

Remember that the parameters this function takes relate to Windows build number and NOT marketing name. So Windows 8 is build 6.2. Windows 7 is 6.0 etc.

Upvotes: 16

Related Questions