如何检测真实的Windows版本?
题
我知道我可以调用 GetVersionEx Win32 API 函数来检索 Windows 版本。在大多数情况下,返回值反映了我的 Windows 版本,但有时并非如此。
如果用户在兼容层下运行我的应用程序,则 GetVersionEx 将不会报告真实版本,而是报告兼容层强制执行的版本。例如,如果我运行 Vista 并在“Windows NT 4”兼容模式下执行我的程序,GetVersionEx 将不会返回版本 6.0,而是返回 4.0。
有没有办法绕过这种行为并获得真正的 Windows 版本?
解决方案
我知道的最好方法是检查特定 API 是否从某个 DLL 导出。每个新的 Windows 版本都会添加新功能,通过检查这些功能的存在,我们可以判断应用程序正在哪个操作系统上运行。例如,Vista 导出 获取区域设置信息Ex 来自 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
编写确定真实操作系统版本的函数很简单;只需从最新的操作系统到最旧的操作系统并使用 获取进程地址 检查导出的 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 上运行这个应用程序——无论是否兼容模式——所以我必须找到一个解决方案(一个比我聪明的人给我指明了正确的方向;上面的东西不是我的创意)。现在我发布它是为了让您高兴并帮助所有将来必须解决这个问题的可怜的灵魂。谷歌,请索引这篇文章!
如果您有更好的解决方案(或我的升级和/或修复),请在此处发布答案......
其他提示
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,但应用程序清单中没有列出 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()”,但我还没有尝试过。
笔记: 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 信息。
本质上是为了回答重复的问题: 在 Delphi 2007 中获取 Windows 8.1 及更高版本的操作系统主要版本、次要版本和构建版本
从 W2K 开始您可以使用 网络服务器获取信息. 。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标头{0x0f}中定义的major_version_mask bitmask,应用程序应使用该成员获得主要版本号。
换句话说,(sv101_version_major & MAJOR_VERSION_MASK)。