Как определить истинную версию Windows?

Я знаю, что могу вызвать функцию GetVersionEx Win32 API для получения версии Windows. В большинстве случаев возвращаемое значение отражает версию моей Windows, но иногда это не так.

Если пользователь запускает мое приложение на уровне совместимости, то GetVersionEx будет сообщать не о реальной версии, а о версии, обеспечиваемой уровнем совместимости. Например, если я использую Vista и запускаю свою программу в режиме совместимости с Windows NT 4, GetVersionEx вернет не версию 6.0, а 4.0.

Есть ли способ обойти это поведение и получить настоящую версию Windows?

9 ответов

Решение

Лучший подход, который я знаю, это проверить, экспортируется ли конкретный API из какой-либо DLL. Каждая новая версия Windows добавляет новые функции, и, проверяя их наличие, можно определить, на какой ОС запущено приложение. Например, Vista экспортирует GetLocaleInfoEx из kernel32.dll, а предыдущие Windowses - нет.

Короче говоря, вот один такой список, содержащий только экспорт из kernel32.dll.

> * функция: реализована в *  
> GetLocaleInfoEx:       Vista  
> GetLargePageMinimum:   Vista, Server 2003  
GetDLLDirectory:         Vista, Server 2003, XP SP1  
GetNativeSystemInfo:     Vista, Server 2003, XP SP1, XP  
ReplaceFile:             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 - в режиме совместимости или нет - поэтому мне пришлось искать решение (парень, умнее меня, указал мне правильное направление; материал выше не мое детище). Сейчас я публикую это для вашего удовольствия и для помощи всем бедным душам, которые должны будут решить эту проблему в будущем. Google, пожалуйста, проиндексируйте эту статью!

Если у вас есть лучшее решение (или обновление и / или исправление для меня), пожалуйста, напишите ответ здесь...

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;

Как насчет получения версии системного файла?

Лучший файл будет kernel32.dll, расположенный в%WINDIR%\System32\kernel32.dll.

Есть API для получения версии файла. Например: я использую Windows XP -> "5.1.2600.5512 (xpsp.080413-2111)"

Другое решение:

прочитайте следующую запись реестра:

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

или другие ключи от

HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion

Следующее работает для меня в Windows 10 без GUID Windows 10, указанного в манифесте приложения:

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(), но я еще не пробовал.

Реальная версия хранилища на PEB блоке информации о процессе.

Образец для приложения Win32 (Delphi Code)

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.

По существу, чтобы ответить на дубликаты Q: Получение основных, второстепенных и сборочных версий ОС для Windows 8.1 и выше в Delphi 2007

Начиная с 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;

Одно замечание об использовании 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 битах. Битовая маска MAJOR_VERSION_MASK, определенная в заголовке Lmserver.h {0x0F}, должна использоваться приложением для получения основного номера версии от этого члена.

Другими словами, (sv101_version_major & MAJOR_VERSION_MASK).

Примечание: Габр спрашивает о подходе, который может обойти ограничения GetVersionEx, Код JCL использует GetVersionEx и, таким образом, подвержен уровню совместимости. Эта информация предназначена только для людей, которым не нужно обходить уровень совместимости.

Используя JCL Jedi, вы можете добавить модуль 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 allso обрабатывает выпуски, такие как 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);

Для исторического интереса вы также можете проверить редакцию NT-уровня с помощью функции NtProductType, она возвращает:

 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 гигабайт свободного места. 32-разрядное целочисленное значение со знаком в установщике стало отрицательным, нарушив установщик. Я должен был установить это в VM, чтобы заставить это работать. Добавление "умного кода" часто делает ваше приложение "глупее". Будь осторожен

Кстати, я обнаружил, что из командной строки вы можете запустить WMIC.exe и набрать path Win32_OperatingSystem ("Выбрать * из Win32_OperatingSystem" не работал для меня). В будущем, возможно, JCL может быть расширен для использования информации WMI.

Другие вопросы по тегам