28 июля 2021

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

    WinAPI – это огромный монстр с неисчислимым количеством функций, которые запросто могут дублировать функционал друг друга. Это позволяет решить одну задачу различными способами. Так и для определения версии установленной на компьютере операционной системы существует несколько путей. Давайте посмотрим на сколько они эффективны.
    "Классикой жанра" для определения версии Windows является функция GetVersion. Ее старший товарищ, функция GetVersionEx, используется Delphi в TOSVersion из System.SysUtils. Она возвращает структуру OSVERSIONINFOEXW, которая содержит информацию об операционной системе.
procedure TMainForm.CheckTOSVersion;
begin
  mInfo.Lines.Add('* TOSVersion');
  mInfo.Lines.Add(TOSVersion.ToString);
  mInfo.Lines.Add('Name: ' + TOSVersion.Name);
  mInfo.Lines.Add(Format('Version: %d.%d.%d', [TOSVersion.Major, TOSVersion.Minor, TOSVersion.Build]));
  mInfo.Lines.Add('Build: ' + TOSVersion.Build.ToString);
end;
    Самый простой способ определить версию Windows – это прочитать ее из реестра Windows.
procedure TMainForm.CheckRegistry;
 var
   reg: TRegistry;
begin
  mInfo.Lines.Add('* Registry');
  reg := TRegistry.Create(KEY_READ);
  try
    reg.RootKey := HKEY_LOCAL_MACHINE;
    if reg.OpenKey('SOFTWARE\Microsoft\Windows NT\CurrentVersion', False) then
      begin
        mInfo.Lines.Add('Name: ' + reg.ReadString('ProductName'));
        mInfo.Lines.Add(Format('Version: %d.%d.%s', [reg.ReadInteger('CurrentMajorVersionNumber'),
                                                     reg.ReadInteger('CurrentMinorVersionNumber'),
                                                     reg.ReadString('CurrentBuildNumber')]));
        mInfo.Lines.Add('Build: ' + reg.ReadString('CurrentBuildNumber'));
      end;
  finally
    reg.Free;
  end;
end;
    Windows Management Instrumentation (WMI) имеет множество объектов из которых можно получить информацию об аппаратном обеспечении и операционной системе компьютера. WMI-класс Win32_OperatingSystem представляет доступ к почти 70 параметрам, описывающим операционную систему. Среди которых есть и информация о ее версии.
procedure TMainForm.CheckWMI;
const
  wbemFlagForwardOnly = $00000020;
var
  SWbemLocator  : OleVariant;
  SWbemService  : OleVariant;
  SWbemObjectSet: OleVariant;
  SWbemObject   : OleVariant;
  Enum          : IEnumVariant;
  pceltFetched  : LongWord;
begin
  mInfo.Lines.Add('* Windows Management Instrumentation (WMI)');
  CoInitialize(nil);
  try
    SWbemLocator   := CreateOleObject('WbemScripting.SWbemLocator');
    SWbemService   := SWbemLocator.ConnectServer('', 'root\CIMV2');
    SWbemObjectSet := SWbemService.ExecQuery('SELECT Caption, Version, BuildNumber FROM Win32_OperatingSystem', 'WQL', wbemFlagForwardOnly);
    Enum           := IUnknown(SWbemObjectSet._NewEnum) as IEnumVariant;
    if Enum.Next(1, SWbemObject, pceltFetched) = S_OK then
      begin
        mInfo.Lines.Add('Name: ' + SWbemObject.Caption);
        mInfo.Lines.Add('Version: ' + SWbemObject.Version);
        mInfo.Lines.Add('Build: ' + SWbemObject.BuildNumber);
        SWbemObject := Unassigned;
      end;
  finally
    CoUninitialize;
  end;
end;
Используя этот способ можно получить информацию не только о компьютере, на котором запущена программа, но и о другом компьютере. Для подключения к удаленному компьютеру в метод ConnectServer необходимо передать имя компьютера, имя пользователя и его пароль.
    Для проверки версии операционной системы из драйвера Windows SDK предоставляет эквивалент функции GetVersionEx – RtlGetVersion. Хоть в ее описании и написано "kernel-mode", но никто не запрещает использовать ее вместо GetVersionEx в обычной программе ("user-mode"):
procedure TMainForm.CheckRtlGetVersion;
const
  STATUS_SUCCESS = $00000000;
type
  TFuncRtlGetVersion = function(var RTL_OSVERSIONINFOEXW): DWORD; stdcall;
var
  RtlGetVersion: TFuncRtlGetVersion;
  VerInfo: TOSVersionInfoExW;
begin
  mInfo.Lines.Add('* RtlGetVersion');
  @RtlGetVersion := GetProcAddress(GetModuleHandle('ntdll.dll'), 'RtlGetVersion');
  if Assigned(RtlGetVersion) then
    begin
      ZeroMemory(@VerInfo, SizeOf(VerInfo));
      VerInfo.dwOSVersionInfoSize := SizeOf(VerInfo);
      if RtlGetVersion(VerInfo) = STATUS_SUCCESS then
        begin
          mInfo.Lines.Add(Format('Version: %d.%d.%d', [VerInfo.dwMajorVersion, VerInfo.dwMinorVersion, VerInfo.dwBuildNumber]));
          mInfo.Lines.Add('Build: ' + VerInfo.dwBuildNumber.ToString);
        end;
  end
end;
    И последний вариант, который я хочу рассмотреть – это использование для определения версии Windows функций управления сетью. Среди них есть две функции: NetWkstaGetInfo – возвращает информацию о конфигурации рабочей станции и NetServerGetInfo – возвращает информацию о конфигурации сервера. Эти функции предназначены для работы с удаленным компьютером, но если вместо имени компьютера в первом параметре передать nil, то они возвращают информацию о локальном компьютере. Они идентичны, поэтому я приведу пример только с NetWkstaGetInfo.
procedure TMainForm.CheckNetWkstaGetInfo;
const
  NetApiDll = 'netapi32.dll';
  NERR_SUCCESS = 0;

type
  NET_API_STATUS = DWORD;
  TFuncNetWkstaGetInfo = function(ServerName: LPWSTR; Level: DWORD; var BufPtr: Pointer): NET_API_STATUS; stdcall;
  TFuncNetApiBufferFree = function (BufPtr: Pointer): NET_API_STATUS; stdcall;

  WKSTA_INFO_100 = record
     wki100_platform_id: DWORD;
     wki100_computername: LPWSTR;
     wki100_langroup: LPWSTR;
     wki100_ver_major: DWORD;
     wki100_ver_minor: DWORD;
  end;
  PWKSTA_INFO_100 = ^WKSTA_INFO_100 ;

var
  NetWkstaGetInfo : TFuncNetWkstaGetInfo;
  NetApiBufferFree: TFuncNetApiBufferFree;
  BufPtr: Pointer;
begin
  mInfo.Lines.Add('* NetWkstaGetInfo');
  @NetWkstaGetInfo  := GetProcAddress(GetModuleHandle(NetApiDll), 'NetWkstaGetInfo');
  @NetApiBufferFree := GetProcAddress(GetModuleHandle(NetApiDll), 'NetApiBufferFree');
  if Assigned(NetWkstaGetInfo) and Assigned(NetApiBufferFree) then
    begin
      BufPtr := nil;
      if NetWkstaGetInfo(nil, 100, BufPtr) = NERR_SUCCESS then
        with PWKSTA_INFO_100(BufPtr)^ do
          mInfo.Lines.Add(Format('Version: %d.%d', [wki100_ver_major, wki100_ver_minor]));
      if Assigned(BufPtr) then
        NetApiBufferFree(BufPtr);
    end;
end;
    При запуске 64-х битной версии программы мы видим, что все функции указали одинаковый номер версии Windows.
Версия Windows 10
А вот в результатах работы 32-х битной версии программы есть небольшой глюк – из реестра в "Name" вместо "Windows 10 Pro" читается "Windows 10 Enterprise".
    Усложним задачу и запустим тестовую программу в режиме совместимости с Windows 7.
Режим совместимости с Windows 7
Картина печальная.
Версия Windows 10 в режиме совместимости с Windows 7
Функции GetVersionEx (TOSVersion) и RtlGetVersion вернули информацию, не о установленной на компьютер операционной системе, а о выбранной для совместимости. Но это не ошибка, так задумано их разработчиками. Чтение из регистра вернуло смесь Windows 7 и Windows 10. Реальную версию Windows из режима совместимости удалось получить с помощью WMI и функций управления сетью. У функций GetVersion и GetVersionEx, а значит и у TOSVersion, есть еще одна проблема. Если программа скомпилирована без манифеста или в ее манифесте не указана поддержка Windows 10, то они работают не корректно.
    Давайте запустим эту тестовую программу на Windows 11.
Версия Windows 11
Итак, на сегодня мы имеем Windows 10.0.22000. Правильно недавно сказал мой сын: "Windows 11 – это Windows 10 с новой красивой графической оболочкой". Надеюсь, что перед релизом Windows 11 в Microsoft не забудут поменять ее номер версии на 11.

Комментариев нет:

Отправить комментарий