Search code examples
windowsdelphiwinapiversion

How to detect true Windows version?


I know I can call the GetVersionEx Win32 API function to retrieve the Windows version. In most cases, the 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 behavior and get the true Windows version?


Solution

  • 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 ...