设为首页 收藏本站
查看: 517|回复: 0

[经验分享] 也谈如何获取真实正确的 Windows 系统版本号

[复制链接]

尚未签到

发表于 2017-6-28 13:09:45 | 显示全部楼层 |阅读模式

  •   关于 GetVersion 系列接口
      关于如何获取 Windows 系统版本号的话题,网上已经有了太多的帖子。但个人觉得总结的都不尽全面,或者没有给出比较稳定的解决方案。
      众所周知,获取 Windows 系统版本的 API 是 GetVersion 和 GetVersionEx。这两个 API 的使用也都相当简单,一直被广泛使用 (下文中,我们将这两个统称为 GetVersion 系列)。后来在 Windows XP 中微软引入了应用程序兼容模式,可以选择兼容之前老的系统。可能很多人并不知道其具体的实现原理,以及能造成如何影响,微软官网开始也未对此详细说明。但直到后来,随着 Windows Vista、Windows 7 等发布,开始有人在网上反映这个 API 并不能获取到真正的系统版本号,我也尝试过测试各种获取系统版本信息的 API,才慢慢发现应用兼容性设置其中一个影响就是获取到错误的系统版本号。即,将程序设置为兼容旧的操作系统,则 GetVersion 系列接口获取到的版本就是该兼容系统的版本,也就是与当前系统实际版本不符,是错误的。
      与此同时,Windows XP 中也引入了主题,引入了 manifest 文件 (Visual Studio 中称之为清单文件) 的概念。关于这个清单文件,我在这篇文章中讨论程序执行权限也提到过,此文暂不详述。清单文件中的设置会影响到程序的某些行为,比如是否使用主题、是否以管理员权限执行、程序支持的操作系统列表、是否受 DPI 缩放影响等。这里我们只讨论其中“支持的操作系统列表”这部分,因为这部分现在也会影响到在新版操作系统中调用 GetVersion 系列的结果。
      虽然微软的官网对于该系列 API 的行为进行了说明,但毕竟实践才是唯一标准。为了搞清楚各个系统版本的 GetVersion 系列接口结果行为有何不同,我详细测试后,将其整理如下:



    是否嵌入清单程序无清单文件或清单未指定支持当前系统程序有清单文件且清单指定支持当前系统
    兼容模式设置未设置兼容模式设置兼容模式未设置兼容模式设置兼容模式
    Windows 20005.05.0[1]5.0[2]5.0[2]
    Windows XP (x86)5.1兼容模式设置的兼容系统版本5.1[3]5.1[3]
    Windows XP (x64)5.2兼容模式设置的兼容系统版本5.2[3]5.2[3]
    Windows Vista6.0兼容模式设置的兼容系统版本6.0兼容模式设置的兼容系统版本
    Windows 76.1兼容模式设置的兼容系统版本6.1兼容模式设置的兼容系统版本
    Windows 86.2兼容模式设置的兼容系统版本,最高 6.2。6.2兼容模式设置的兼容系统版本,最高 6.2。
    Windows 8.16.2兼容模式设置的兼容系统版本,最高 6.2。6.3兼容模式设置的兼容系统版本,最高 6.3。
    Windows 106.2兼容模式设置的兼容系统版本,最高 6.2。10.0兼容模式设置的兼容系统版本,最高 10.0。
    [1] Windows 2000 不支持兼容模式,因此结果不受影响。

    [2] Windows 2000 不支持清单文件,因此结果不受影响。

    [3] Windows XP 不支持清单文件中指定的支持操作系统列表。
      可以看得出来,结果惨不忍睹,这还怎么能让人放心使用?实际上 GetVersion 系列接口的行为变更从 XP 时代就有了,然而微软开始并没有在 MSDN 上给出相关提示,也没有多少人留意。起初微软是为了设置应用程序以兼容模式运行,将其 hook 并返回错误的结果,这个反而带来了更大的麻烦,再加上 manifest 的引入,使得这个 API 完全被微软玩坏。到 Windows 8 时代,该页面才注明该 API 已被废弃,并且给出其他的解决方案。只能说,这两个 API 走到今天这条路,微软也是没办法,这个行为一开始被修改就注定了今天被抛弃的结果。

  •   官方推荐的备用方案
      此外,微软也提供其他的几个 API 用来判断 (不能获取) 系统版本是否为特定版本,只是鲜为人知,使用频率较低。从 GetVersion 系列被抛弃开始,这些 API 才在 MSDN 被列出在 GetVersionEx 的说明页面,作为其他的备选方案。

    •   IsOS
        这个 API 是判断特定版本的,但是最高支持也就到 Windows 2003。此后,微软 MSDN 页面未对参数进行更新。

    •   VerifyVersionInfo
        该 API 要求配合 VerSetConditionMask 使用,才能进行后续判断。微软已经将 VerifyVersionInfo 封装为更易使用的函数。使用这些函数需包含 VersionHelpers.h 头文件,只有安装了较新版本的 Visual Studio 或 Windows SDK 才支持。

      • IsWindowsXPOrGreater
      • IsWindowsXPSP1OrGreater
      • IsWindowsXPSP2OrGreater
      • IsWindowsXPSP3OrGreater
      • IsWindowsVistaOrGreater
      • IsWindowsVistaSP1OrGreater
      • IsWindowsVistaSP2OrGreater
      • IsWindows7OrGreater
      • IsWindows7SP1OrGreater
      • IsWindows8OrGreater
      • IsWindows8Point1OrGreater
        要求程序已嵌入清单文件且清单指定支持 Windows 8.1 及更高版本系统,否则返回 false。
      • IsWindows10OrGreater
        要求程序已嵌入清单文件且清单指定支持 Windows 10 及更高版本系统,否则返回 false。
      • IsWindowsServer
      • IsWindowsVersionOrGreater
        这组函数依然只能够用来判断 (不能获取) 系统版本。而且,根据 MSDN 的说明,其中的部分依然受到清单文件影响,但未测试。

    •   NetWkstaGetInfo
        这个 API 也是微软官方推荐的获取系统版本号的替代品之一。



      #include <windows.h>
      #include <lm.h>
      #pragma comment(lib, "netapi32.lib")
      DWORD PASCAL GetVersion( void )
      {
      DWORD dwVersion = 0;
      WKSTA_INFO_100 *wkstaInfo = NULL;
      NET_API_STATUS netStatus = NetWkstaGetInfo(NULL, 100, (BYTE **)&wkstaInfo);
      if (netStatus == NERR_Success)
      {
      DWORD dwMajVer = wkstaInfo->wki100_ver_major;
      DWORD dwMinVer = wkstaInfo->wki100_ver_minor;
      dwVersion = (DWORD)MAKELONG(dwMinVer, dwMajVer);
      NetApiBufferFree(wkstaInfo);
      }
      return dwVersion;
      }
        经测试,获取的版本正常,不受兼容性和清单文件的影响,但在 dll 中调用会失败。



  •   非官方备用方案
      这里提供一些在网上搜索到的其他方案。由于部分使用了系统内部接口甚至数据结构,不保证后续依然有效。

    •   查询 kernel32.dll 版本
        通常情况下 kernel32.dll 的版本号和系统是同步的,但如果微软哪天不遵守这个约定,这个方法就不好用了。有的程序则是查询 ntoskrnl.exe 的版本信息,原理类似。



      #include <windows.h>
      #include <shlwapi.h>
      #pragma comment(lib, "shlwapi.lib")
      #pragma comment(lib, "version.lib")
      DWORD PASCAL GetKernelVersion( void )
      {
      DWORD dwVersion = 0;
      WCHAR szDLLName[MAX_PATH] = { 0 };
      HRESULT hr = SHGetFolderPathW(NULL, CSIDL_SYSTEM, NULL, SHGFP_TYPE_CURRENT, szDLLName);
      if ((hr == S_OK) && PathAppendW(szDLLName, L"kernel32.dll"))
      {
      DWORD dwVerInfoSize = GetFileVersionInfoSizeW(szDLLName, NULL);
      if (dwVerInfoSize > 0)
      {
      HANDLE hHeap = GetProcessHeap();
      LPVOID pvVerInfoData = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, dwVerInfoSize);
      if (pvVerInfoData != NULL)
      {
      if (GetFileVersionInfoW(szDLLName, 0, dwVerInfoSize, pvVerInfoData))
      {
      UINT ulLength = 0;
      VS_FIXEDFILEINFO *pvffi = NULL;
      if (VerQueryValueW(pvVerInfoData, L"\\", (LPVOID *)&pvffi, &ulLength))
      {
      dwVersion = pvffi->dwFileVersionMS;
      }
      }
      HeapFree(hHeap, 0, pvVerInfoData);
      }
      }
      }
      return dwVersion;
      }
        很不幸,经测试,如果程序没有嵌入清单文件,在 Windows 8.1 或 Windows 10,这个方法获取的结果也是 6.2,也就是说仍然受到清单文件的影响,有可能得到错误的结果。

    •   读取 kernel32.dll 版本
        什么,还有个读取?那么查询和读取有什么分别?没看到上面最后一行吗,连获取文件版本信息的 API 都拿不到正确结果了,微软还有什么能相信?好吧,你不给我正确结果,我就直接分析二进制总行了吧!



      #include <windows.h>
      DWORD PASCAL ReadKernelVersion( void )
      {
      DWORD dwVersion = 0;
      HMODULE hinstDLL = LoadLibraryExW(L"kernel32.dll", NULL, LOAD_LIBRARY_AS_DATAFILE);
      if (hinstDLL != NULL)
      {
      HRSRC hResInfo = FindResource(hinstDLL, MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION);
      if (hResInfo != NULL)
      {
      HGLOBAL hResData = LoadResource(hinstDLL, hResInfo);
      if (hResData != NULL)
      {
      static const WCHAR wszVerInfo[] = L"VS_VERSION_INFO";
      struct VS_VERSIONINFO {
      WORD wLength;
      WORD wValueLength;
      WORD wType;
      WCHAR szKey[ARRAYSIZE(wszVerInfo)];
      VS_FIXEDFILEINFO Value;
      WORD Children[];
      } *lpVI = (struct VS_VERSIONINFO *)LockResource(hResData);
      if ( (lpVI != NULL) && (lstrcmpiW(lpVI->szKey, wszVerInfo) == 0) && (lpVI->wValueLength > 0) )
      {
      dwVersion = lpVI->Value.dwFileVersionMS;
      }
      }
      }
      FreeLibrary(hinstDLL);
      }
      return dwVersion;
      }
        很高兴的告诉大家,这个结果即使在 Windows 8.1 或 Windows 10 上,也都依然是正确的。

    •   读取 PEB 数据结构
        PEB 结构是 Windows 系统的内部接口,读取的数据是最底层的,但是也正因为是内部结构,微软随时有可能变动。下面的结构体只是简略定义,对不需要或者不重点关注的成员进行了省略或者使用了 PVOID 指针来代替。务必注意,此方法仅供参考,如后期 Windows 系统变更数据结构,造成任何蓝屏死机问题,本人概不负责。



      #include <windows.h>
      typedef struct _PEB {
      BOOLEAN InheritedAddressSpace;
      BOOLEAN ReadImageFileExecOptions;
      BOOLEAN BeingDebugged;
      BOOLEAN BitField;
      HANDLE Mutant;
      PVOID ImageBaseAddress;
      PVOID Ldr;
      PVOID ProcessParameters;
      PVOID SubSystemData;
      PVOID ProcessHeap;
      PVOID FastPebLock;
      PVOID AtlThunkSListPtr;
      PVOID SparePtr2;
      ULONG EnvironmentUpdateCount;
      PVOID KernelCallbackTable;
      ULONG SystemReserved[1];
      ULONG SpareUlong;
      PVOID FreeList;
      ULONG TlsExpansionCounter;
      PVOID TlsBitmap;
      ULONG TlsBitmapBits[2];
      PVOID ReadOnlySharedMemoryBase;
      PVOID ReadOnlySharedMemoryHeap;
      PVOID *ReadOnlyStaticServerData;
      PVOID AnsiCodePageData;
      PVOID OemCodePageData;
      PVOID UnicodeCaseTableData;
      ULONG NumberOfProcessors;
      ULONG NtGlobalFlag;
      LARGE_INTEGER CriticalSectionTimeout;
      SIZE_T HeapSegmentReserve;
      SIZE_T HeapSegmentCommit;
      SIZE_T HeapDeCommitTotalFreeThreshold;
      SIZE_T HeapDeCommitFreeBlockThreshold;
      ULONG NumberOfHeaps;
      ULONG MaximumNumberOfHeaps;
      PVOID *ProcessHeaps;
      PVOID GdiSharedHandleTable;
      PVOID ProcessStarterHelper;
      ULONG GdiDCAttributeList;
      PVOID LoaderLock;
      ULONG OSMajorVersion;
      ULONG OSMinorVersion;
      USHORT OSBuildNumber;
      USHORT OSCSDVersion;
      ULONG OSPlatformId;
      } PEB, *PPEB;
      typedef struct _TEB {
      NT_TIB NtTib;
      PVOID EnvironmentPointer;
      struct {
      HANDLE UniqueProcess;
      HANDLE UniqueThread;
      } ClientId;
      PVOID ActiveRpcHandle;
      PVOID ThreadLocalStoragePointer;
      PEB *ProcessEnvironmentBlock;
      } TEB, *PTEB;
      DWORD PASCAL GetVersionPEB( void )
      {
      DWORD dwVersion = 0;
      TEB *lpTeb = NtCurrentTeb();
      if (lpTeb != NULL)
      {
      PEB *lpPeb = lpTeb->ProcessEnvironmentBlock;
      if (lpPeb != NULL)
      {
      DWORD dwMajVer = lpPeb->OSMajorVersion;
      DWORD dwMinVer = lpPeb->OSMinorVersion;
      dwVersion = (DWORD)MAKELONG(dwMinVer, dwMajVer);
      }
      }
      return dwVersion;
      }
        再次很高兴的告诉你,这个结果截止 Windows 10,也都能获取到正确的版本号。

    •   RtlGetVersion
        使用时通常都是从 ntdll.dll 中动态加载,本人就不列出详细代码,仅以静态调用作为示例。



      NTSTATUS NTAPI RtlGetVersion(
      RTL_OSVERSIONINFOW *lpVersionInformation
      );
      DWORD PASCAL GetVersionRtl( void )
      {
      DWORD dwVersion = 0;
      RTL_OSVERSIONINFOEXW osvi = { 0 };
      osvi.dwOSVersionInfoSize = sizeof(osvi);
      NTSTATUS status = RtlGetVersion((RTL_OSVERSIONINFOW *)&osvi);
      if (status == STATUS_SUCCESS)
      {
      DWORD dwMajVer = osvi.dwMajorVersion;
      DWORD dwMinVer = osvi.dwMinorVersion;
      dwVersion = (DWORD)MAKELONG(dwMinVer, dwMajVer);
      }
      return dwVersion;
      }
        很遗憾,在我的 Windows 10 上获取的结果是 6.2,仍可能受到清单文件的影响。

    •   RtlGetNtVersionNumbers
        同上,从 ntdll.dll 中加载。此接口系高手反编译所得,微软并未放出任何文档,请谨慎使用。Windows 2000 不支持,Windows XP 起支持。



      void NTAPI RtlGetNtVersionNumbers(
      DWORD *lpdwMajorVersion,
      DWORD *lpdwMinorVersion,
      DWORD *lpdwBuildNumber
      );
      DWORD PASCAL GetVersionRtl( void )
      {
      DWORD dwMajorVersion = 0;
      DWORD dwMinorVersion = 0;
      RtlGetNtVersionNumbers(&dwMajorVersion, &dwMinorVersion, NULL);
      DWORD dwVersion = (DWORD)MAKELONG(dwMinorVersion, dwMajorVersion);
      return dwVersion;
      }
        在 Windows 10 上获取的结果是 10.0,目前看来是不会出问题的。




运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-389018-1-1.html 上篇帖子: Windows Server 2012 R2 多用户配置问题 下篇帖子: windows下webrtc的编译 2016(转)
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表