How to detect true Windows version?

23,033

Solution 1

The best approach I know is to check if specific API is exported from some DLL. Each new Windows version adds new functions and by checking the existance of those functions one can tell which OS the application is running on. For example, Vista exports GetLocaleInfoEx from kernel32.dll while previous Windowses didn't.

To cut the long story short, here is one such list containing only exports from 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  

Writing the function to determine the real OS version is simple; just proceed from newest OS to oldest and use GetProcAddress to check exported APIs. Implementing this in any language should be trivial.

The following code in Delphi was extracted from the free DSiWin32 library):

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 }

--- updated 2009-10-09

It turns out that it gets very hard to do an "undocumented" OS detection on Vista SP1 and higher. A look at the API changes shows that all Windows 2008 functions are also implemented in Vista SP1 and that all Windows 7 functions are also implemented in Windows 2008 R2. Too bad :(

--- end of update

FWIW, this is a problem I encountered in practice. We (the company I work for) have a program that was not really Vista-ready when Vista was released (and some weeks after that ...). It was not working under the compatibility layer either. (Some DirectX problems. Don't ask.)

We didn't want too-smart-for-their-own-good users to run this app on Vista at all - compatibility mode or not - so I had to find a solution (a guy smarter than me pointed me into right direction; the stuff above is not my brainchild). Now I'm posting it for your pleasure and to help all poor souls that will have to solve this problem in the future. Google, please index this article!

If you have a better solution (or an upgrade and/or fix for mine), please post an answer here ...

Solution 2

WMI QUery:

"Select * from Win32_OperatingSystem"

EDIT: Actually better would be:

"Select Version from Win32_OperatingSystem"

You could implement this in Delphi like so:

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;

Solution 3

How about obtaining the version of a system file?

The best file would be kernel32.dll, located in %WINDIR%\System32\kernel32.dll.

There are APIs to obtain the file version. eg: I'm using Windows XP -> "5.1.2600.5512 (xpsp.080413-2111)"

Solution 4

Another solution:

read the following registry entry:

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

or other keys from

HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion

Solution 5

real version store on PEB block of process information.

Sample for Win32 app (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.

Share:
23,033
med
Author by

med

I'm a long-time Delphi programmer, writer for Monitor and Blaise Pascal magazines and a frequent contributor to the Delphi-SI community. I'm also the sole writer of the The Delphi Geek blog and proud author of the popular Delphi multithreading library - OmniThreadLibrary. Read more about me on Careers 2.0.

Updated on July 12, 2022

Comments

  • med
    med almost 2 years

    I know I can call the GetVersionEx Win32 API function to retrieve Windows version. In most cases returned value reflects the version of my Windows, but sometimes that is not so.

    If a user runs my application under the compatibility layer, then GetVersionEx won't be reporting the real version but the version enforced by the compatibility layer. For example, if I'm running Vista and execute my program in "Windows NT 4" compatibility mode, GetVersionEx won't return version 6.0 but 4.0.

    Is there a way to bypass this behaviour and get true Windows version?

  • med
    med over 15 years
    One problem with WMI - it was only introduced in Windows 2000. If you know your code won't be running on 9x or NT 3/4 then the WMI approach is fine.
  • Jacob
    Jacob over 15 years
    I so want to edit this post to align the versions in the first section, but I don't have enough reputation.
  • Camilo Martin
    Camilo Martin over 14 years
    Does anyone still run 9x or NT?
  • Sam
    Sam over 13 years
    Now, that is elegant. I'm glad I kept reading rather than begin using the GetVersionEx option. Keep it simple and beautiful.
  • Esben von Buchwald
    Esben von Buchwald about 13 years
    Reading strings from the registry and parsing them is not a good idea unless it is specifically documented by Microsoft. Are you certain the ProductName is not localized to different languages? Are you sure you got every variant of the product name? The above advice is the exact kind of thing that makes the App Compat team over at Microsoft pull their hair out. -1
  • botismarius
    botismarius about 13 years
    Well, then they should provide an API/an official solution to get this information. Hiding information is rarely a good thing. As you can see, every solution is only a work-around.
  • Warren  P
    Warren P about 13 years
    this should be pretty easy to translate to C#.
  • Anders
    Anders over 12 years
    Not sure if that table can be trusted, AFAIK Beep exists on NT4 and ConnectNamedPipe on 9x
  • Warren  P
    Warren P over 12 years
    The JEDI JCL can determine Server versions as well.
  • med
    med over 12 years
    @WarrenP, which JCL function would that be?
  • Warren  P
    Warren P over 12 years
    There's actually a lot of cool Windows-Version-check stuff in JCL, so I created an answer for this question.
  • med
    med over 12 years
    JCL just calls GetVersionEx and is therefore tricked by the compatibility layer.
  • Warren  P
    Warren P over 12 years
    Oh, that's important to note. I'll see about logging a bug in JCL and adding a GetRealWindowsVersion function. In which case, we'd need to use an API check approach like yours, to defeating the compatibility layer.
  • Warren  P
    Warren P over 12 years
    Is WMI weird or what? "Select" doesn't work on my WMI, but "path Win32_OperatingSystem" did work. Is WMI a crazy piece of under-documented wonkiness, or what?
  • EBGreen
    EBGreen over 12 years
    So you are accessing wmi through the wmic console app it sounds like. When I say WMI query I am talking about accessing it via a method that supports the WQL query language (I realize that is redundant) which WMIC does not. So to answer your question, some portions of WMI are not particularly well documented because any software vendor can create classes in WMI pretty much at will much like the registry, but the portions that are created by MS and especially the portions dealing with the OS are actually quite well documented.
  • Sheng Jiang 蒋晟
    Sheng Jiang 蒋晟 over 12 years
    Note Windows service packs often add back-ported dll exports from later versions.
  • oɔɯǝɹ
    oɔɯǝɹ about 11 years
    Besides being an overley complex method, this isn't really going to help with future versions of Windows
  • David Heffernan
    David Heffernan almost 9 years
    This is certainly not elegant
  • MartynA
    MartynA almost 9 years
    Might be more useful to wait until you can test against Win10, seeing as the topic has been under active discussion in this more recent q: stackoverflow.com/questions/31753092
  • Remy Lebeau
    Remy Lebeau almost 9 years
    I tested this code On Windows 10 Preview (I don't have the release version yet). Without a Windows 10 GUID in the manifest, NetServerGetInfo() (and also RtlGetVersion() in ntdll.dll) reports the version as 10.0, whereas GetVersionEx() reports the version as 6.2 as documented.
  • FredS
    FredS almost 9 years
    Good to know, I need to wait until the "Something Happened" error is resolved :)
  • Free Consulting
    Free Consulting almost 9 years
    That spurious stdcall makes the whole snippet highly suspicious.
  • AlainD
    AlainD about 7 years
    My Delphi 7 application is running on Windows 8.1. Invoking RtlGetVersion from ntdll.dll returns "5.1" (the version numbers for Windows XP). This is the same as the numbers returned from GetVersionEx. Haven't tried the Netapi32 function yet.
  • AlainD
    AlainD about 7 years
    When I read CurrentVersion on my Windows 8.1 laptop, astonishingly it comes back with "5.1" (the version numbers for Windows XP)! This was using an older Delphi 7 application without a manifest specifically targeting Windows 8.1. Using regedit, the value is definitely "6.3". This suggests Windows must be deliberately intercepting the read and applying some sort of compatibility shim. The only method I found to be reliable is to read the version numbers of a core system dll (I chose kernel32.dll) as per the other suggestion from botismarius. Seems a bit "hacky" but it works.
  • AlainD
    AlainD about 7 years
    Continuing from above, ProductName returns "Microsoft Windows XP" when the actual entry is "Windows 8.1"!
  • botismarius
    botismarius about 7 years
    @AlainD seems that Microsoft is taking the compatibility to the extreme pole.
  • Server Overflow
    Server Overflow almost 7 years
    @remylebeau - Which of these 3 methods you think is more reliable for future compatibility (future versions of Windows)? RtlGetVersion?
  • Remy Lebeau
    Remy Lebeau almost 7 years
    @AlainD works fine for me and everyone else who uses it (numerous posts on SO showing that). Do you, by chance, have your EXE running in XP Compatibility mode? Without Compatibility Mode, and lacking a suitable manifest, GetVersionEx would be reporting the version number of Windows 8, not XP.
  • Remy Lebeau
    Remy Lebeau almost 7 years
    Thus is the approach that MSDN recommends.
  • AlainD
    AlainD almost 7 years
    @RemyLebeau: Yes, well spotted! When I disabled XP Compatibility Mode, the operating system version returns as Major=6 and Minor=2 (ie. Windows 8). Have since tried this on Windows 10 with the same result.
  • osexpert
    osexpert almost 6 years
    Here is list you can use to support later OS (Win 7 etc.) geoffchappell.com/studies/windows/win32/kernel32/api