Freebie Friday: Windows version info

TMS Software Delphi  Components

Coincidence or not, but 26 years ago, in 1995, not only Delphi 1 was released by Borland but Microsoft also released Windows 95 that from that moment on skyrocketed in popularity and went for world domination as operating system.
Who would have thought in 1995 that 26 years later, detecting on what Windows operating system your software is running would be more complex than ever?
Over the years, Microsoft released not only new major versions almost every 2 to 3 years but also all sorts of editions like the Windows NT operating system for example. 
In 2015 Microsoft released Windows 10 and decided it would continue to release incremental updates all under the Windows 10 moniker but also here Microsoft went on to make it available in different editions (Education, Home, Pro, Enterprise) and at the same time continued to release major updates to its server operating system Windows 2016 Server and Windows 2019 Server.

Needless to say that proper Windows operating system version detection became non-trivial over the years. Added to this complexity is the fact that Microsoft decided to create a mechanism to return Windows version information to applications through its APIs different from the real Windows version and this depending on the application manifest. The reason for this approach was obviously for ensuring old applications would continue to work thinking they were running on older Windows operating systems, but it doesn’t make things easier. 

So, forget about all this history, forget about all this complexity as after all, it is Friday today and we are heading to the weekend to celebrate the 26th anniversary of Delphi. This #FreebieFriday brings you one routine GetOperatingSystem() that returns the Windows version and also the version number as string:

procedure GetOperatingSystem(var AName: string; var AVersion: string);
const
  SM_SERVERR2 = 89;
  VER_NT_WORKSTATION = $0000001;

type
  pfnRtlGetVersion = function(var RTL_OSVERSIONINFOEXW): DWORD; stdcall;

var
  osVerInfo: TOSVersionInfoEx;
  majorVer, minorVer, spmajorVer, spminorVer, buildVer, edition: Cardinal;
  ver: RTL_OSVERSIONINFOEXW;
  RtlGetVersion: pfnRtlGetVersion;

  procedure GetUnmanistedVersion(var majv,minv,buildv: cardinal);
  begin
    @RtlGetVersion := GetProcAddress(GetModuleHandle('ntdll.dll'), 'RtlGetVersion');
    if Assigned(RtlGetVersion) then
    begin
      ZeroMemory(@ver, SizeOf(ver));
      ver.dwOSVersionInfoSize := SizeOf(ver);

      if RtlGetVersion(ver) = 0 then
      begin
        majv := ver.dwMajorVersion;
        minv := ver.dwMinorVersion;
        buildv := ver.dwBuildNumber;
      end;
    end;
  end;

  function GetWindows10Edition: string;
  begin
    Result := 'Windows 10';
    GetProductInfo(majorVer, minorVer, spmajorVer, spminorVer, edition);

    case edition and $FF of
    $62..$65: Result := 'Windows 10 Home';
    $79..$7A: Result := 'Windows 10 Education';
    $46,$04,$48,$1B,$54,$7D,$7E,$81,$82: Result := 'Windows 10 Enterprise';
    $30,$31,$A1,$A2: Result := 'Windows 10 Pro';
    end;
  end;

begin
  AName := 'Unknown';
  AVersion := '0';
  // set operating system type flag
  osVerInfo.dwOSVersionInfoSize := SizeOf(TOSVersionInfoEx);
  if GetVersionEx(POSVersionInfo(@osVerInfo)^) then
  begin
    majorVer := osVerInfo.dwMajorVersion;
    minorVer := osVerInfo.dwMinorVersion;
    buildVer := osVerInfo.dwBuildNumber;

    AVersion := majorVer.ToString + '.' + minorVer.ToString + '.' + buildVer.ToString;

    case osVerInfo.dwPlatformId of
      VER_PLATFORM_WIN32_NT: // Windows NT/2000
        begin
          if majorVer <= 4 then
            AName := 'Windows NT'
          else if (majorVer = 5) and (minorVer = 0) then
            AName := 'Windows 2000'
          else if (majorVer = 5) and (minorVer = 1) then
            AName := 'Windows XP'
          else if (majorVer = 5) and (minorVer = 2) then
            AName := 'Windows 2003'
          else if (majorVer = 6) and (minorVer = 0) then
          begin
            AName := 'Windows Vista';

            if osVerInfo.wProductType = VER_NT_WORKSTATION then
              AName := 'Windows Vista'
            else
              AName := 'Windows Server 2008';
          end
          else if (majorVer = 6) and (minorVer = 1) then
          begin
            if osVerInfo.wProductType = VER_NT_WORKSTATION then
               AName := 'Windows 7'
            else
               AName := 'Windows Server 2008R2';
          end
          else if (majorVer = 6) and (minorVer = 2) then
          begin
            GetUnmanistedVersion(majorVer, minorVer, buildVer);

            AVersion := majorVer.ToString + '.' + minorVer.ToString + '.' + buildVer.ToString;

            if (majorVer = 6) and (minorVer = 2) then
            begin
              if osVerInfo.wProductType = VER_NT_WORKSTATION then
                AName := 'Windows 8'
              else
                AName := 'Windows Server 2012'
            end;

            if (majorVer = 6) and (minorVer = 3) then
            begin
              if osVerInfo.wProductType = VER_NT_WORKSTATION then
                AName := 'Windows 8.1'
              else
                AName := 'Windows Server 2012R2'
            end;

            if (majorVer = 10) and (minorVer = 0) then
            begin
              if osVerInfo.wProductType = VER_NT_WORKSTATION then
                AName := 'Windows 10'
              else
              begin
                if osVerInfo.dwBuildNumber >= 17763 then
                  AName := 'Windows Server 2019'
                else
                  AName := 'Windows Server 2016';
              end;
            end;

          end
          else if (majorVer = 6) and (minorVer = 3) then
          begin
            if osVerInfo.wProductType = VER_NT_WORKSTATION then
              AName := 'Windows 8.1'
            else
              AName := 'Windows Server 2012R2'
          end
          else if (majorVer = 6) and (minorVer = 4) then
          begin
            if osVerInfo.wProductType = VER_NT_WORKSTATION then
            begin
              AName := GetWindows10Edition;
            end
            else
            begin
              if osVerInfo.dwBuildNumber >= 17763 then
                AName := 'Windows Server 2019'
              else
                AName := 'Windows Server 2016'
            end;
          end
          else if (majorVer = 10) and (minorVer = 0) then
          begin
            if osVerInfo.wProductType = VER_NT_WORKSTATION then
              AName := GetWindows10Edition
            else
            begin
              if osVerInfo.dwBuildNumber >= 17763 then
                AName := 'Windows Server 2019'
              else
                AName := 'Windows Server 2016'
            end;
          end
          else
            AName := 'Unknown';
        end;
      VER_PLATFORM_WIN32_WINDOWS:  // Windows 9x/ME
        begin
          if (majorVer = 4) and (minorVer = 0) then
            AName := 'Windows 95'
          else if (majorVer = 4) and (minorVer = 10) then
          begin
            if osVerInfo.szCSDVersion[1] = 'A' then
              AName := 'Windows 98 SE'
            else
              AName := 'Windows 98';
          end
          else if (majorVer = 4) and (minorVer = 90) then
            AName := 'Windows ME'
          else
            AName := 'Unknown';
        end;
      else
        AName := 'Unknown';
    end;
  end;
end;

Copy & paste this code and you can use it as:

var
  AName, AVersion: string;
begin
  GetOperatingSystem(AName, AVersion);
  memo.Lines.Text := ANAme +' ' + AVersion;
end;

Here, on this machine, it returns:
Windows 10 Pro 10.0.19041

For what is it worth, this method is also used in the TMS VCL UI Pack component TEXEInfo and can be used by calling the class function:

EXEInfo.GetOperatingSystem: string;

TEXEInfo can in addition to this information extract detailed application version information from the running application or another application with EXEInfo.GetVersionInfoOfApp(EXEName);

Enjoy this #FreebieFriday and don’t hesitate to let us know what other interesting Freebies you wish to see or share an interesting routine you created yourself with fellow Delphi developers!