Как определить настоящую версию Windows?
Вопрос
Я знаю, что могу вызвать функцию GetVersionEx Win32 API, чтобы получить версию Windows.В большинстве случаев возвращаемое значение отражает версию моей Windows, но иногда это не так.
Если пользователь запускает мое приложение на уровне совместимости, GetVersionEx будет сообщать не о реальной версии, а о версии, реализованной на уровне совместимости.Например, если я использую Vista и запускаю свою программу в режиме совместимости с «Windows NT 4», GetVersionEx вернет не версию 6.0, а 4.0.
Есть ли способ обойти это поведение и получить настоящую версию Windows?
Решение
Лучший известный мне подход — проверить, экспортируется ли конкретный API из какой-либо DLL.Каждая новая версия Windows добавляет новые функции, и, проверив наличие этих функций, можно определить, в какой ОС работает приложение.Например, Vista экспортирует GetLocaleInfoEx из kernel32.dll, в то время как в предыдущих версиях Windows этого не было.
Короче говоря, вот один такой список, содержащий только экспорт из kernel32.dll.
> *function: implemented in* > 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 Beep: Vista, Server 2003, XP SP1, XP, 2000, ME, 98, 95 OSR2, 95
Написать функцию для определения реальной версии ОС несложно;просто перейдите от новейшей ОС к самой старой и используйте GetProcAddress для проверки экспортированных API.Реализация этого на любом языке должна быть тривиальной.
Следующий код в Delphi был извлечен из бесплатного ДСиВин32 библиотека):
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 }
--- обновлено 9 октября 2009 г.
Оказывается, на Vista SP1 и выше очень сложно обнаружить «недокументированную» ОС.Взгляните на Изменения API показывает, что все функции Windows 2008 также реализованы в Vista SP1 и что все функции Windows 7 также реализованы в Windows 2008 R2.Очень жаль :(
--- конец обновления
Кстати, с этой проблемой я столкнулся на практике.У нас (компании, в которой я работаю) есть программа, которая на самом деле не была готова для 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
реальная версия хранится в блоке 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.
У меня в 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()», но я еще не пробовал.
Примечание: Габр спрашивает о подходе, который может обойти ограничения 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);
Для исторического интереса вы также можете проверить версию уровня 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-битное целое число со знаком в программе установки стало отрицательным, что привело к поломке программы установки.Мне пришлось установить его на виртуальную машину, чтобы заставить его работать.Добавление «умного кода» часто делает ваше приложение «глупее».Будьте осторожны.
Кстати, я обнаружил, что из командной строки можно запустить WMIC.exe и набрать path Win32_OperatingSystem
(«Выбрать * из Win32_OperatingSystem» у меня не сработало).В будущем, возможно, JCL можно будет расширить для использования информации WMI.
По сути, чтобы ответить на повторяющийся вопрос: Получение основной и дополнительной версий ОС, а также версий сборки для 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 битах.Приложение, определяемое в заголовке LMServer.H, должна использоваться приложением для получения основного номера версии от этого члена, определяется в BitMask, определенный в заголовке lmserver.h.
Другими словами, (sv101_version_major и MAJOR_VERSION_MASK).