40

我知道我可以调用 GetVersionEx Win32 API 函数来检索 Windows 版本。在大多数情况下,返回值反映了我的 Windows 版本,但有时并非如此。

如果用户在兼容层下运行我的应用程序,则 GetVersionEx 不会报告真实版本,而是兼容层强制执行的版本。例如,如果我正在运行 Vista 并在“Windows NT 4”兼容模式下执行我的程序,GetVersionEx 不会返回 6.0 版本而是 4.0。

有没有办法绕过这种行为并获得真正的 Windows 版本?

4

9 回答 9

29

我知道的最好方法是检查是否从某个 DLL 导出了特定的 API。每个新的 Windows 版本都添加了新功能,通过检查这些功能的存在,我们可以知道应用程序在哪个操作系统上运行。例如,Vista 从 kernel32.dll 导出GetLocaleInfoEx,而以前的 Windows 没有。

长话短说,这里有一个这样的列表,其中只包含 kernel32.dll 的导出。

> *功能:实现于*  
> GetLocaleInfoEx:Vista  
> GetLargePageMinimum:Vista、Server 2003  
获取DLL目录:Vista、Server 2003、XP SP1  
GetNativeSystemInfo:Vista、Server 2003、XP SP1、XP  
替换文件:Vista、Server 2003、XP SP1、XP、2000  
OpenThread:Vista、Server 2003、XP SP1、XP、2000、ME  
GetThreadPriorityBoost:Vista、Server 2003、XP SP1、XP、2000、NT 4  
IsDebuggerPresent:Vista、Server 2003、XP SP1、XP、2000、ME、NT 4、98   
GetDiskFreeSpaceEx:Vista、Server 2003、XP SP1、XP、2000、ME、NT 4、98、95 OSR2  
ConnectNamedPipe:Vista、Server 2003、XP SP1、XP、2000、NT 4、NT 3  
哔声:Vista、Server 2003、XP SP1、XP、2000、ME、98、95 OSR2、95  

编写确定真实操作系统版本的函数很简单;只需从最新的操作系统到最旧的操作系统,并使用GetProcAddress检查导出的 API。用任何语言实现这一点都应该是微不足道的。

Delphi 中的以下代码是从免费的DSiWin32库中提取的):

TDSiWindowsVersion = (wvUnknown, wvWin31, wvWin95, wvWin95OSR2, wvWin98,
  wvWin98SE, wvWinME, wvWin9x, wvWinNT3, wvWinNT4, wvWin2000, wvWinXP,
  wvWinNT, wvWinServer2003, wvWinVista);

function DSiGetWindowsVersion: TDSiWindowsVersion;
var
  versionInfo: TOSVersionInfo;
begin
  versionInfo.dwOSVersionInfoSize := SizeOf(versionInfo);
  GetVersionEx(versionInfo);
  Result := wvUnknown;
  case versionInfo.dwPlatformID of
    VER_PLATFORM_WIN32s: Result := wvWin31;
    VER_PLATFORM_WIN32_WINDOWS:
      case versionInfo.dwMinorVersion of
        0:
          if Trim(versionInfo.szCSDVersion[1]) = 'B' then
            Result := wvWin95OSR2
          else
            Result := wvWin95;
        10:
          if Trim(versionInfo.szCSDVersion[1]) = 'A' then
            Result := wvWin98SE
          else
            Result := wvWin98;
        90:
          if (versionInfo.dwBuildNumber = 73010104) then
             Result := wvWinME;
           else
             Result := wvWin9x;
      end; //case versionInfo.dwMinorVersion
    VER_PLATFORM_WIN32_NT:
      case versionInfo.dwMajorVersion of
        3: Result := wvWinNT3;
        4: Result := wvWinNT4;
        5:
          case versionInfo.dwMinorVersion of
            0: Result := wvWin2000;
            1: Result := wvWinXP;
            2: Result := wvWinServer2003;
            else Result := wvWinNT
          end; //case versionInfo.dwMinorVersion
        6: Result := wvWinVista;
      end; //case versionInfo.dwMajorVersion
    end; //versionInfo.dwPlatformID
end; { DSiGetWindowsVersion }

function DSiGetTrueWindowsVersion: TDSiWindowsVersion;

  function ExportsAPI(module: HMODULE; const apiName: string): boolean;
  begin
    Result := GetProcAddress(module, PChar(apiName)) <> nil;
  end; { ExportsAPI }

var
  hKernel32: HMODULE;

begin { DSiGetTrueWindowsVersion }
  hKernel32 := GetModuleHandle('kernel32');
  Win32Check(hKernel32 <> 0);
  if ExportsAPI(hKernel32, 'GetLocaleInfoEx') then
    Result := wvWinVista
  else if ExportsAPI(hKernel32, 'GetLargePageMinimum') then
    Result := wvWinServer2003
  else if ExportsAPI(hKernel32, 'GetNativeSystemInfo') then
    Result := wvWinXP
  else if ExportsAPI(hKernel32, 'ReplaceFile') then
    Result := wvWin2000
  else if ExportsAPI(hKernel32, 'OpenThread') then
    Result := wvWinME
  else if ExportsAPI(hKernel32, 'GetThreadPriorityBoost') then
    Result := wvWinNT4
  else if ExportsAPI(hKernel32, 'IsDebuggerPresent') then  //is also in NT4!
    Result := wvWin98
  else if ExportsAPI(hKernel32, 'GetDiskFreeSpaceEx') then  //is also in NT4!
    Result := wvWin95OSR2
  else if ExportsAPI(hKernel32, 'ConnectNamedPipe') then
    Result := wvWinNT3
  else if ExportsAPI(hKernel32, 'Beep') then
    Result := wvWin95
  else // we have no idea
    Result := DSiGetWindowsVersion;
end; { DSiGetTrueWindowsVersion }

--- 更新于 2009-10-09

事实证明,在 Vista SP1 及更高版本上进行“未记录的”操作系统检测变得非常困难。查看API 更改可以看出,所有 Windows 2008 功能也在 Vista SP1 中实现,所有 Windows 7 功能也在 Windows 2008 R2 中实现。太糟糕了 :(

--- 更新结束

FWIW,这是我在实践中遇到的一个问题。我们(我工作的公司)有一个程序,当 Vista 发布时(以及在那之后的几个星期......),它并没有真正准备好 Vista。它也不能在兼容层下工作。(一些 DirectX 问题。不要问。)

我们根本不希望太聪明的用户在 Vista 上运行这个应用程序 - 是否兼容模式 - 所以我必须找到一个解决方案(一个比我聪明的人为我指明了正确的方向;上面的东西不是我的创意)。现在我发布它是为了您的快乐,并帮助所有将来必须解决这个问题的可怜的灵魂。谷歌,请索引这篇文章!

如果您有更好的解决方案(或我的升级和/或修复),请在此处发布答案...

于 2008-09-11T17:25:30.597 回答
26

WMI 查询:

"Select * from Win32_OperatingSystem"

编辑:实际上更好的是:

"Select Version from Win32_OperatingSystem"

你可以像这样在 Delphi 中实现它:

function OperatingSystemDisplayName: string;

  function GetWMIObject(const objectName: string): IDispatch;
  var
    chEaten: Integer;
    BindCtx: IBindCtx;
    Moniker: IMoniker;
  begin
    OleCheck(CreateBindCtx(0, bindCtx));
    OleCheck(MkParseDisplayName(BindCtx, PChar(objectName), chEaten, Moniker));
    OleCheck(Moniker.BindToObject(BindCtx, nil, IDispatch, Result));
  end;

  function VarToString(const Value: OleVariant): string;
  begin
    if VarIsStr(Value) then begin
      Result := Trim(Value);
    end else begin
      Result := '';
    end;
  end;

  function FullVersionString(const Item: OleVariant): string;
  var
    Caption, ServicePack, Version, Architecture: string;
  begin
    Caption := VarToString(Item.Caption);
    ServicePack := VarToString(Item.CSDVersion);
    Version := VarToString(Item.Version);
    Architecture := ArchitectureDisplayName(SystemArchitecture);
    Result := Caption;
    if ServicePack <> '' then begin
      Result := Result + ' ' + ServicePack;
    end;
    Result := Result + ', version ' + Version + ', ' + Architecture;
  end;

var
  objWMIService: OleVariant;
  colItems: OleVariant;
  Item: OleVariant;
  oEnum: IEnumvariant;
  iValue: LongWord;

begin
  Try
    objWMIService := GetWMIObject('winmgmts:\\localhost\root\cimv2');
    colItems := objWMIService.ExecQuery('SELECT Caption, CSDVersion, Version FROM Win32_OperatingSystem', 'WQL', 0);
    oEnum := IUnknown(colItems._NewEnum) as IEnumVariant;
    if oEnum.Next(1, Item, iValue)=0 then begin
      Result := FullVersionString(Item);
      exit;
    end;
  Except
    // yes, I know this is nasty, but come what may I want to use the fallback code below should the WMI code fail
  End;

  (* Fallback, relies on the deprecated function GetVersionEx, reports erroneous values
     when manifest does not contain supportedOS matching the executing system *)
  Result := TOSVersion.ToString;
end;
于 2008-09-11T17:24:24.717 回答
11

如何获取系统文件的版本?

最好的文件是 kernel32.dll,位于 %WINDIR%\System32\kernel32.dll。

有获取文件版本的 API。例如:我使用的是 Windows XP ->“5.1.2600.5512 (xpsp.080413-2111)”

于 2008-09-11T17:37:18.803 回答
8

另一种解决方案:

阅读以下注册表项:

HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProductName

或其他键

HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion
于 2008-09-11T18:49:59.550 回答
6

真实版本存储在进程信息的 PEB 块上。

Win32 应用程序示例(Delphi 代码)

unit RealWindowsVerUnit;

interface

uses
  Windows;

var
  //Real version Windows
  Win32MajorVersionReal: Integer;
  Win32MinorVersionReal: Integer;

implementation

type
  PPEB=^PEB;
  PEB = record
    InheritedAddressSpace: Boolean;
    ReadImageFileExecOptions: Boolean;
    BeingDebugged: Boolean;
    Spare: Boolean;
    Mutant: Cardinal;
    ImageBaseAddress: Pointer;
    LoaderData: Pointer;
    ProcessParameters: Pointer; //PRTL_USER_PROCESS_PARAMETERS;
    SubSystemData: Pointer;
    ProcessHeap: Pointer;
    FastPebLock: Pointer;
    FastPebLockRoutine: Pointer;
    FastPebUnlockRoutine: Pointer;
    EnvironmentUpdateCount: Cardinal;
    KernelCallbackTable: PPointer;
    EventLogSection: Pointer;
    EventLog: Pointer;
    FreeList: Pointer; //PPEB_FREE_BLOCK;
    TlsExpansionCounter: Cardinal;
    TlsBitmap: Pointer;
    TlsBitmapBits: array[0..1] of Cardinal;
    ReadOnlySharedMemoryBase: Pointer;
    ReadOnlySharedMemoryHeap: Pointer;
    ReadOnlyStaticServerData: PPointer;
    AnsiCodePageData: Pointer;
    OemCodePageData: Pointer;
    UnicodeCaseTableData: Pointer;
    NumberOfProcessors: Cardinal;
    NtGlobalFlag: Cardinal;
    Spare2: array[0..3] of Byte;
    CriticalSectionTimeout: LARGE_INTEGER;
    HeapSegmentReserve: Cardinal;
    HeapSegmentCommit: Cardinal;
    HeapDeCommitTotalFreeThreshold: Cardinal;
    HeapDeCommitFreeBlockThreshold: Cardinal;
    NumberOfHeaps: Cardinal;
    MaximumNumberOfHeaps: Cardinal;
    ProcessHeaps: Pointer;
    GdiSharedHandleTable: Pointer;
    ProcessStarterHelper: Pointer;
    GdiDCAttributeList: Pointer;
    LoaderLock: Pointer;
    OSMajorVersion: Cardinal;
    OSMinorVersion: Cardinal;
    OSBuildNumber: Cardinal;
    OSPlatformId: Cardinal;
    ImageSubSystem: Cardinal;
    ImageSubSystemMajorVersion: Cardinal;
    ImageSubSystemMinorVersion: Cardinal;
    GdiHandleBuffer: array [0..33] of Cardinal;
    PostProcessInitRoutine: Cardinal;
    TlsExpansionBitmap: Cardinal;
    TlsExpansionBitmapBits: array [0..127] of Byte;
    SessionId: Cardinal;
  end;

//Get PEB block current win32 process
function GetPDB: PPEB; stdcall;
asm
  MOV EAX, DWORD PTR FS:[30h]
end;

initialization
  //Detect true windows wersion
  Win32MajorVersionReal := GetPDB^.OSMajorVersion;
  Win32MinorVersionReal := GetPDB^.OSMinorVersion;
end.

于 2014-06-21T20:16:43.823 回答
5

以下适用于我在 Windows 10 中没有在应用程序清单中列出的 Windows 10 GUID:

uses
  System.SysUtils, Winapi.Windows;

type
  NET_API_STATUS = DWORD;

  _SERVER_INFO_101 = record
    sv101_platform_id: DWORD;
    sv101_name: LPWSTR;
    sv101_version_major: DWORD;
    sv101_version_minor: DWORD;
    sv101_type: DWORD;
    sv101_comment: LPWSTR;
  end;
  SERVER_INFO_101 = _SERVER_INFO_101;
  PSERVER_INFO_101 = ^SERVER_INFO_101;
  LPSERVER_INFO_101 = PSERVER_INFO_101;

const
  MAJOR_VERSION_MASK = $0F;

function NetServerGetInfo(servername: LPWSTR; level: DWORD; var bufptr): NET_API_STATUS; stdcall; external 'Netapi32.dll';
function NetApiBufferFree(Buffer: LPVOID): NET_API_STATUS; stdcall; external 'Netapi32.dll';

type
  pfnRtlGetVersion = function(var RTL_OSVERSIONINFOEXW): LONG; stdcall;
var
  Buffer: PSERVER_INFO_101;
  ver: RTL_OSVERSIONINFOEXW;
  RtlGetVersion: pfnRtlGetVersion;
begin
  Buffer := nil;

  // Win32MajorVersion and Win32MinorVersion are populated from GetVersionEx()...
  ShowMessage(Format('GetVersionEx: %d.%d', [Win32MajorVersion, Win32MinorVersion])); // shows 6.2, as expected per GetVersionEx() documentation

  @RtlGetVersion := GetProcAddress(GetModuleHandle('ntdll.dll'), 'RtlGetVersion');
  if Assigned(RtlGetVersion) then
  begin
    ZeroMemory(@ver, SizeOf(ver));
    ver.dwOSVersionInfoSize := SizeOf(ver);

    if RtlGetVersion(ver) = 0 then
      ShowMessage(Format('RtlGetVersion: %d.%d', [ver.dwMajorVersion, ver.dwMinorVersion])); // shows 10.0
  end;

  if NetServerGetInfo(nil, 101, Buffer) = NO_ERROR then
  try
    ShowMessage(Format('NetServerGetInfo: %d.%d', [Buffer.sv101_version_major and MAJOR_VERSION_MASK, Buffer.sv101_version_minor])); // shows 10.0
  finally
    NetApiBufferFree(Buffer);
  end;
end.

更新NetWkstaGetInfo()可能也可以工作,类似于“NetServerGetInfo()”,但我还没有尝试过。

于 2015-07-31T23:37:38.100 回答
1

注意: Gabr 正在询问一种可以绕过GetVersionEx. JCL 代码使用 GetVersionEx,因此受兼容层的约束。此信息仅适用于不需要绕过兼容层的人。

使用 Jedi JCL,您可以添加单元 JclSysInfo,并调用函数GetWindowsVersion。它返回一个枚举类型 TWindowsVersion。

目前,JCL 包含所有已发布的 Windows 版本,并且每次 Microsoft 在一个盒子中发布新版本的 Windows 时都会更改:

  TWindowsVersion =
   (wvUnknown, wvWin95, wvWin95OSR2, wvWin98, wvWin98SE, wvWinME,
    wvWinNT31, wvWinNT35, wvWinNT351, wvWinNT4, wvWin2000, wvWinXP,
    wvWin2003, wvWinXP64, wvWin2003R2, wvWinVista, wvWinServer2008,
    wvWin7, wvWinServer2008R2);

如果您想知道您运行的是 64 位 Windows 7 而不是 32 位,请调用JclSysInfo.IsWindows64.

请注意,JCL 也处理版本,如 Pro、Ultimate 等。对于该调用 GetWindowsEdition,它返回以下之一:

TWindowsEdition =
   (weUnknown, weWinXPHome, weWinXPPro, weWinXPHomeN, weWinXPProN, weWinXPHomeK,
    weWinXPProK, weWinXPHomeKN, weWinXPProKN, weWinXPStarter, weWinXPMediaCenter,
    weWinXPTablet, weWinVistaStarter, weWinVistaHomeBasic, weWinVistaHomeBasicN,
    weWinVistaHomePremium, weWinVistaBusiness, weWinVistaBusinessN,
    weWinVistaEnterprise, weWinVistaUltimate, weWin7Starter, weWin7HomeBasic,
    weWin7HomePremium, weWin7Professional, weWin7Enterprise, weWin7Ultimate);

出于历史兴趣,您也可以使用 NtProductType 函数检查 NT 级别的版本,它返回:

 TNtProductType =       (ptUnknown, ptWorkStation, ptServer, ptAdvancedServer,        
        ptPersonal, ptProfessional, ptDatacenterServer, 
        ptEnterprise, ptWebEdition);

请注意,上面检测到“N 个版本”。那是根据欧盟反垄断法规创建的欧盟(欧洲)版本的 Windows。这是 JCL 内部的一个非常好的检测等级。

这是一个示例函数,可帮助您检测 Vista,并在 Vista 上执行一些特殊操作。

function IsSupported:Boolean;
begin
  case GetWindowsVersion of
     wvVista:  result := false; 
    else
      result := true;
  end;
end;

请注意,如果您想做“大于”检查,那么您应该使用其他技术。另请注意,版本检查通常会成为未来破坏的根源。我通常选择警告用户并继续,这样我的二进制代码就不会成为未来的实际破坏源。

最近我尝试安装一个应用程序,安装程序检查了我的驱动器可用空间,但没有安装,因为我有超过 2 GB 的可用空间。安装程序中的 32 位整数有符号值变为负数,从而破坏了安装程序。我必须将它安装到虚拟机中才能使其工作。添加“智能代码”通常会使您的应用程序“愚蠢”。警惕。

顺便说一句,我发现可以从命令行运行 WMIC.exe,然后键入path Win32_OperatingSystem(“从 Win32_OperatingSystem 中选择 *”对我不起作用)。将来也许 JCL 可以扩展到使用 WMI 信息。

于 2011-11-14T15:33:08.600 回答
1

基本上回答重复的问题:在 Delphi 2007 中获取 Windows 8.1 及更高版本的操作系统主要、次要和构建版本

从 W2K 开始,您可以使用NetServerGetInfo。NetServerGetInfo 在 W7 和 W8.1 上返回正确信息,无法在 W10 上进行测试。

function GetWinVersion: string;
var
  Buffer: PServerInfo101;
begin
  Buffer := nil;
  if NetServerGetInfo(nil, 101, Pointer(Buffer)) = NO_ERROR then
  try
     Result := <Build You Version String here>(
      Buffer.sv101_version_major,
      Buffer.sv101_version_minor,
      VER_PLATFORM_WIN32_NT // Save since minimum support begins in W2K
      );
  finally
    NetApiBufferFree(Buffer);
  end;
end;
于 2015-07-31T21:32:05.097 回答
1

关于使用 NetServerGetInfo() 的注意事项,它在 Windows 10 (10240.th1_st1) 上仍然有效...

https://msdn.microsoft.com/en-us/library/windows/desktop/aa370903%28v=vs.85%29.aspx

sv101_version_major

主要版本号和服务器类型。

操作系统的主要版本号以最低有效 4 位指定。服务器类型以最高有效 4 位指定。应用程序应使用 Lmserver.h 标头 {0x0F} 中定义的 MAJOR_VERSION_MASK 位掩码从该成员获取主要版本号。

换句话说,(sv101_version_major & MAJOR_VERSION_MASK)。

于 2015-08-01T18:31:54.313 回答