如何检测Windows 10:C++

28

我多年前编写了一款PC审计工具,并一直保持更新。其中一个基本功能是报告正在审计的PC上运行的Windows版本,而我始终使用GetVersionEx调用来实现这一点。

这在Windows 8及以下版本中有效,但在Windows 10下不受支持,实际上Windows 10返回的值与Windows 8一样为8.2。微软似乎没有引入任何直接替代方案,建议您检查所需的特定功能,而不是查看操作系统版本,但出于审核目的,我实际上需要操作系统名称。

“扫描器”是一个C++程序,必须在非特权账户下运行,因此我认为我读到的另一个建议-获取系统DLL(例如kernel32.dll)的版本将无法起作用,因为这些文件夹通常对用户不可访问。

欢迎提出任何其他建议/想法!


对于审计工具,你不应该依赖于检测特定版本(10?哪个版本的10?),而是使用操作系统如何描述自己。这样未来的版本就不会破坏代码。 - Ben Voigt
1
此外,非特权帐户肯定可以从系统 DLL(例如 kernel32.dll)中读取。 - Ben Voigt
1
你不会检测到Windows 10!Windows 10会检测到你! - Ferruccio
投票重新开放,因为目标不同(编程语言也不同)。检测Windows 10与获取Windows版本号是不同的(有点震惊)。(诚然,一些障碍是相同的或至少相关的。)此外,另一个问题是针对C#的。 - Adrian McCarthy
7个回答

17
从Windows 8.1开始,GetVersion()GetVersionEx()受应用程序清单的影响:
随着Windows 8.1的发布,GetVersionEx API的行为在返回操作系统版本的值方面发生了变化。GetVersionEx函数返回的值现在取决于应用程序的清单。
未针对Windows 8.1或Windows 10进行清单的应用程序将返回Windows 8操作系统版本值(6.2)。一旦应用程序为特定操作系统版本进行了清单,GetVersionEx将始终返回该应用程序在未来版本中为其清单制定的版本。要为Windows 8.1或Windows 10制作应用程序清单,请参阅Targeting your application for Windows
较新的版本助手函数只是VerifyVersionInfo()的包装器。从Windows 10开始,它现在也受到展现的影响。
Windows 10: 当应用程序调用VerifyVersionInfo时,如果lpVersionInfo参数设置为指定Windows 8.1或Windows 10,则对于没有针对Windows 8.1或Windows 10的兼容性清单的应用程序,返回false,即使当前操作系统版本是Windows 8.1或Windows 10。具体来说,VerifyVersionInfo具有以下行为:
  • 如果应用程序没有清单,则VerifyVersionInfo的行为就像操作系统版本是Windows 8(6.2)。
  • 如果应用程序具有包含与Windows 8.1相对应的GUID的清单,则VerifyVersionInfo的行为就像操作系统版本是Windows 8.1(6.3)。
  • 如果应用程序具有包含与Windows 10相对应的GUID的清单,则VerifyVersionInfo的行为就像操作系统版本是Windows 10(10.0)。
版本辅助函数使用VerifyVersionInfo函数,因此存在和内容的清单会同样影响IsWindows8Point1OrGreaterIsWindows10OrGreater的行为。 要使您的应用程序适用于Windows 8.1或Windows 10,请参阅将应用程序定位到Windows
使用 RtlGetVersion(), NetServerGetInfo(), 或 NetWkstaGetInfo() 来获取真实的操作系统版本,不受表象影响。它们都报告准确的操作系统版本,并且不受表象影响(尚未?)。 (Microsoft 曾建议查询系统 DLL 的文件版本,但当 Windows 没有更新系统 DLL 版本以匹配时,他们 停止 推荐这种方法。)

我不理解你回答中的“VerQueryValue用于获取文件版本信息的\StringFileInfo\<lang><codepage>\ProductVersion子块”。 "\StringFileInfo\<lang><codepage>\ProductVersion"代表什么?文件版本信息的子块是什么意思? - Sahil Singh
@SahilSingh,VerQueryValue() 的文档中已经涵盖了这些信息,并且有大量的在线示例/教程展示如何使用 VerQueryValue()。在提问之前,请花些时间进行一些研究。 - Remy Lebeau

16

GetVersion和GetVersionEx已被各种版本帮助函数取代。您想要的是IsWindows10OrGreater,它们可以在VersionHelpers.h中找到。

IsWindows10OrGreater仅适用于最新的SDK/Visual Studio 2015。但是,在一般情况下,您可以使用IsWindowsVersionOrGreater。例如,在我的7盒子上,我对IsWindowsVersionOrGreater(6, 0, 0)返回TRUE。

请记住,此函数使用的参数与Windows构建号有关,而不是市场名称。 因此,Windows 8的版本号为6.2,Windows 7为6.0等。


11
非常好的API设计。BOOL WINAPI IsWindows10OrGreater(void); 的意思是“判断当前操作系统是否为Windows 10或更高版本”,并返回一个布尔值(TRUE或FALSE)。 - M.M
3
它们只是方便函数。有一个通用的“IsWindowsVersionOrGreater”函数。 - Robinson
3
问题是它们不可用于扫描仪所运行的早期Windows版本。如果可用,我能动态加载它们吗? - user1169502
11
似乎ISWindows10OrGreater(或其更通用的变体IsWindowsVersionOrGreater)不能可靠地指示操作系统。现在的行为取决于应用程序的“清单”如何,这意味着即使在Windows 10上,应用程序也可以告诉您它是在Windows 8.1或Windows 10上运行。如果您没有将应用程序针对Windows 10进行定位,则始终会告诉您Windows 8.1。 - TheDuke
5
IsWindowsXXOrGreater()函数实际上并不是真正的函数,它们只是VerifyVersionInfo()函数的宏包装器,并且现在受到Windows 10中开始出现的表象的影响。 - Remy Lebeau
显示剩余8条评论

15

请使用以下函数:

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;
}

它将返回Windows版本号作为double类型(7、8、8.1、10)。


@Bonfire #包括 <windows.h> - Alexandru Dicu
这对我不起作用... 它会抛出像“RtlGetVersion'未在此范围内声明”的错误。 - Diogenis Siganos
dwMajorVersion 不是 double 类型。 - ssbssa
修改 - 将返回的变量更改为双精度 - Michael Haephrati
5
这段内容有误。从DWORD构造浮点数并不会产生小数,如果产生小数也无法准确地表示8.1。此外,该函数永远不会返回8,它返回10代表Windows 10,而对于所有Windows Vista到8.1版本,都会返回6 - IInspectable
显示剩余5条评论

5

2021年01月12日 根据Michael Haephrati的答案,我对我的代码进行了调整。

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;
}

在测试用于检测win10的代码后,我推测这个API错误,IsWindows10OrGreater,是因为设置了错误的FileVersionMS版本于kernel32.dll中。要正常获取它,请使用ProductVersionMS版本查询。

https://learn.microsoft.com/zh-cn/windows/win32/api/versionhelpers/nf-versionhelpers-iswindows10orgreater

希望能对大家有所帮助!


2

我需要在旧版本的VS编译器中工作,而且还要在Qt框架内进行。下面是我如何实现的。

GetWinVersion.h文件添加到您的Qt项目中:

#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

在您的pro或pri qmake文件中添加所需的链接:
msvc: LIBS += -lKernel32

实现辅助函数如下(注意这里使用的SystemInfo是我的自定义类,但你可以理解为...):

#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
...

现在,这里是真正的关键。您需要将一个清单文件附加到您的exe文件上,以“针对”最近的Windows版本,否则您无法检测到它们(请参见MS文档:https://msdn.microsoft.com/en-us/library/windows/desktop/ms724451%28v=vs.85%29.aspx)。以下是一个示例清单文件:
<?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>

以下是附加清单的批处理代码:

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%"

理论上,您可以使用qmake运行最后一部分并附加清单。我没有运用我找到的例子成功,现在只能通过这个批处理来“作弊”...


2

不要使用 VersionHelpers.h!它存在缺陷!

它会忽略用户的应用兼容设置。

相反地,使用旧版本的 Kernel32.dll 函数,例如 GetVersion,例如:

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

// Usage: IsWindowsVersionOrGreater(_WIN32_WINNT_WINTHRESHOLD)


0

顺便提一下,LibreOffice项目通过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();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接