//---------------------------------------------------------------------
// Windows Version Pascal Source
//
// Create by: Kang Sin-young (2004.01.25 Last Modify)
//---------------------------------------------------------------------

//---------------------------------------------------------------------
// GetWindowsVersion Pascal Source
// From https://github.com/yypbd/yypbd-Delphi-Libs/blob/master/lib/WindowsVersion.pas
// Create by: Young-pil Yang (2014.05.28 Last Modify)
//---------------------------------------------------------------------

unit EM.WinOSVersion;

interface

uses
  WinApi.Windows, WinApi.Messages, SysUtils, Variants, StrUtils, System.Win.Registry;

type
  TGetProductInfo = function (dwOsMajorVer, dwOSMinorVerion, dwSpMajorVer, dwSpMinorVer : DWORD; pdwReturnProductType : PDWORD):Boolean; stdcall;

  WKSTA_INFO_100 = record
    wki100_platform_id: DWORD;
    wki100_computername: LPWSTR;
    wki100_langroup: LPWSTR;
    wki100_ver_major: DWORD;
    wki100_ver_minor: DWORD;
  end;

  LPWKSTA_INFO_100 = ^WKSTA_INFO_100;

  _USER_INFO_0 = record
    usri0_name: LPWSTR;
  end;


  TWinVerSub = record
    Major,
    Minor,
    Build: DWORD;
  end;

  TWinVerInfo = record
    WinID,
    WinVer,
    WinName,
    ServicePack: String;
    Version: TWinVerSub;
  end;

  _OSVERSIONINFOEXA = record
    dwOSVersionInfoSize: DWORD;
    dwMajorVersion: DWORD;
    dwMinorVersion: DWORD;
    dwBuildNumber: DWORD;
    dwPlatformId: DWORD;
    szCSDVersion: array[0..127] of AnsiChar; { Maintenance string for PSS usage }
    wServicePackMajor: WORD;
    wServicePackMinor: WORD;
    wSuiteMask: WORD;
    wProductType: BYTE;
    wReserved: BYTE;
  end;
  {$EXTERNALSYM _OSVERSIONINFOEXA}
  _OSVERSIONINFOEXW = record
    dwOSVersionInfoSize: DWORD;
    dwMajorVersion: DWORD;
    dwMinorVersion: DWORD;
    dwBuildNumber: DWORD;
    dwPlatformId: DWORD;
    szCSDVersion: array[0..127] of WideChar; { Maintenance string for PSS usage }
    wServicePackMajor: WORD;
    wServicePackMinor: WORD;
    wSuiteMask: WORD;
    wProductType: BYTE;
    wReserved: BYTE;
  end;
  {$EXTERNALSYM _OSVERSIONINFOEXW}
  OSVERSIONINFOEXA = _OSVERSIONINFOEXA;
  OSVERSIONINFOEXW = _OSVERSIONINFOEXW;
  {$EXTERNALSYM OSVERSIONINFOEXA}
  {$EXTERNALSYM OSVERSIONINFOEXW}
  OSVERSIONINFOEX = OSVERSIONINFOEXA;
  {$EXTERNALSYM OSVERSIONINFOEX}
  TOSVersionInfoExA = OSVERSIONINFOEXA;
  TOSVersionInfoExW = OSVERSIONINFOEXW;

const
  NERR_Success = 0;
  SERVICE_MAX_COUNT = 10;
  VER_EQUAL = 1;

  VER_SERVER_NT = $80000000;
  {$EXTERNALSYM VER_SERVER_NT}
  VER_WORKSTATION_NT = $40000000;
  {$EXTERNALSYM VER_WORKSTATION_NT}

  VER_SUITE_SMALLBUSINESS = $00000001;
  {$EXTERNALSYM VER_SUITE_SMALLBUSINESS}
  VER_SUITE_ENTERPRISE = $00000002;
  {$EXTERNALSYM VER_SUITE_ENTERPRISE}
  VER_SUITE_BACKOFFICE = $00000004;
  {$EXTERNALSYM VER_SUITE_BACKOFFICE}
  VER_SUITE_COMMUNICATIONS = $00000008;
  {$EXTERNALSYM VER_SUITE_COMMUNICATIONS}
  VER_SUITE_TERMINAL = $00000010;
  {$EXTERNALSYM VER_SUITE_TERMINAL}
  VER_SUITE_SMALLBUSINESS_RESTRICTED = $00000020;
  {$EXTERNALSYM VER_SUITE_SMALLBUSINESS_RESTRICTED}
  VER_SUITE_EMBEDDEDNT = $00000040;
  {$EXTERNALSYM VER_SUITE_EMBEDDEDNT}
  VER_SUITE_DATACENTER = $00000080;
  {$EXTERNALSYM VER_SUITE_DATACENTER}
  VER_SUITE_SINGLEUSERTS = $00000100;
  {$EXTERNALSYM VER_SUITE_SINGLEUSERTS}
  VER_SUITE_PERSONAL = $00000200;
  {$EXTERNALSYM VER_SUITE_PERSONAL}
  VER_SUITE_BLADE = $00000400;
  {$EXTERNALSYM VER_SUITE_BLADE}
  VER_SUITE_EMBEDDED_RESTRICTED = $00000800;
  {$EXTERNALSYM VER_SUITE_EMBEDDED_RESTRICTED}
  VER_SUITE_SECURITY_APPLIANCE = $00001000;
  {$EXTERNALSYM VER_SUITE_SECURITY_APPLIANCE}

  VER_NT_WORKSTATION = $0000001;
  {$EXTERNALSYM VER_NT_WORKSTATION}
  VER_NT_DOMAIN_CONTROLLER = $0000002;
  {$EXTERNALSYM VER_NT_DOMAIN_CONTROLLER}
  VER_NT_SERVER = $0000003;
  {$EXTERNALSYM VER_NT_SERVER}

  //GetProductInfo  vista 이상에서만 사용
  PRODUCT_BUSINESS                      = $00000006;
  PRODUCT_BUSINESS_N                    = $00000010;
  PRODUCT_CLUSTER_SERVER                = $00000012;
  PRODUCT_DATACENTER_SERVER             = $00000008;
  PRODUCT_DATACENTER_SERVER_CORE        = $0000000C;
  PRODUCT_DATACENTER_SERVER_CORE_V      = $00000027;
  PRODUCT_DATACENTER_SERVER_V           = $00000025;
  PRODUCT_ENTERPRISE                    = $00000004;
  PRODUCT_ENTERPRISE_E                  = $00000046;
  PRODUCT_ENTERPRISE_N                  = $0000001B;
  PRODUCT_ENTERPRISE_SERVER             = $0000000A;
  PRODUCT_ENTERPRISE_SERVER_CORE        = $0000000E;
  PRODUCT_ENTERPRISE_SERVER_CORE_V      = $00000029;
  PRODUCT_ENTERPRISE_SERVER_IA64        = $0000000F;
  PRODUCT_ENTERPRISE_SERVER_V           = $00000026;
  PRODUCT_HOME_BASIC                    = $00000002;
  PRODUCT_HOME_BASIC_E                  = $00000043;
  PRODUCT_HOME_BASIC_N                  = $00000005;
  PRODUCT_HOME_PREMIUM                  = $00000003;
  PRODUCT_ULTIMATE                      = $00000001;
  PRODUCT_PROFESSIONAL                  = $00000030;
  PRODUCT_PROFESSIONAL_N                = $00000031;
  PRODUCT_STARTER                       = $0000000B;
  PRODUCT_SMALLBUSINESS_SERVER          = $00000009;
  PRODUCT_SMALLBUSINESS_SERVER_PREMIUM  = $00000019;
  PRODUCT_STANDARD_SERVER               = $00000007;
  PRODUCT_STANDARD_SERVER_CORE          = $0000000D;
  PRODUCT_WEB_SERVER                    = $00000011;

var
  MajorCache: DWORD;
  MinorCache: DWORD;
  ServicePackCache: DWORD;
  IsServerCache: Boolean;

function NetWkstaGetInfo(ServerName: LPWSTR; Level: DWORD; BufPtr: Pointer)
  : Longint; stdcall; external 'netapi32.dll' Name 'NetWkstaGetInfo';
function NetApiBufferFree(Buffer: Pointer): Longint; stdcall;
  external 'netapi32.dll' Name 'NetApiBufferFree';
{$IFDEF UNICODE}
function VerifyVersionInfo(var LPOSVERSIONINFOEX: OSVERSIONINFOEX;
  dwTypeMask: DWORD; dwlConditionMask: int64): BOOL; stdcall;
  external kernel32 name 'VerifyVersionInfoW';
{$ELSE}
function VerifyVersionInfo(var LPOSVERSIONINFOEX: OSVERSIONINFOEX;
  dwTypeMask: DWORD; dwlConditionMask: int64): BOOL; stdcall;
  external kernel32 name 'VerifyVersionInfoA';
{$ENDIF}
function VerSetConditionMask(dwlConditionMask: int64; dwTypeBitMask: DWORD;
  dwConditionMask: Byte): int64; stdcall; external kernel32;

function GetVersionEx2(var lpVersionInformation: TOSVersionInfoExA): BOOL; stdcall;
{$EXTERNALSYM GetVersionEx2}
function GetVersionEx2A(var lpVersionInformation: TOSVersionInfoExA): BOOL; stdcall;
{$EXTERNALSYM GetVersionEx2A}
function GetVersionEx2W(var lpVersionInformation: TOSVersionInfoExW): BOOL; stdcall;
{$EXTERNALSYM GetVersionEx2W}

function GetWindowsVersion(var AMajor, AMinor: DWORD): Boolean; overload;
function GetWindowsVersion(var AMajor, AMinor, AServicePack, ABuildNumber: DWORD;
  var AIsServer: Boolean): Boolean; overload;

function GetWinVersion: TWinVerInfo;

function ParseWinNT(p: TOSVersionInfoExW; IsEx: Boolean): TWinVerInfo; overload;
function ParseWinNT(p: TOSVersionInfoExA; IsEx: Boolean): TWinVerInfo; overload;
function ParseWin9x(p: TOSVersionInfo): TWinVerInfo;

implementation

uses
  System.Classes, Tocsg.Safe, Tocsg.Path, Tocsg.Strings, Tocsg.Registry,
  Tocsg.FileInfo;

function GetVersionEx2; external kernel32 name 'GetVersionExA';
function GetVersionEx2A; external kernel32 name 'GetVersionExA';
function GetVersionEx2W; external kernel32 name 'GetVersionExW';

function GetWinVersion: TWinVerInfo;
var
  rtnVerEx: TOSVersionInfoExA;
  rtnVer: TOSVersionInfo;
  rtnParse: TWinVerInfo;
begin
	rtnVer.dwOSVersionInfoSize := sizeof(OSVERSIONINFO); //Get 1st OS Info
  GetVersionEx(rtnVer);

  if rtnVer.dwPlatformId = VER_PLATFORM_WIN32_NT then
  begin
  	if rtnVer.dwMajorVersion < 5 then
    	rtnParse := ParseWinNT(rtnVerEx, False)
    else begin
  		rtnVerEx.dwOSVersionInfoSize := sizeof(TOSVersionInfoExA); //*WinNT/2000/XP/2003
  		GetVersionEx2(rtnVerEx);
    	rtnParse := ParseWinNT(rtnVerEx, True);
    end;
  end else
  	rtnParse := ParseWin9x(rtnVer);

  Result := rtnParse;
end;

function ParseWinNT(p: TOSVersionInfoExA; IsEx: Boolean): TWinVerInfo;

  function IsWin11: Boolean;
  var
    fi: TTgFileInfo;
    StrList: TStringList;
  begin
    Result := false;
    Guard(fi, TTgFileInfo.Create(GetSystemDir + 'kernel32.dll'));
    Guard(StrList, TStringList.Create);
    if SplitString(fi.Version, '.', StrList) > 3 then
    begin
      if StrToIntDef(StrList[2], -1) >= 22000 then
        Result := true;
    end;
  end;

var
  rtn: TWinVerInfo;

  resVer: TRegistry;
  szProduct: String;
  procGetProductInfo : TGetProductInfo;
  hModule : THandle;
  ProdType : DWORD;
  dwMajor, dwMinor, dwService, dwBuildNum : DWORD;
  bIsServer : Boolean;
begin
  GetWindowsVersion(dwMajor, dwMinor, dwService, dwBuildNum, bIsServer);

  p.dwMajorVersion := dwMajor;
  p.dwMinorVersion := dwMinor;
  p.dwBuildNumber := dwBuildNum;
  if not bIsServer then
  begin
    p.wProductType := VER_NT_WORKSTATION
  end;

// 윈도우 8.0이 추가 되면서 다시 개선함
  rtn.WinVer := 'Unknown';

  case p.dwMajorVersion of
    5 :
      case p.dwMinorVersion of
        0 : rtn.WinName := '2000';
        1 : rtn.WinName := 'XP';
        2 : rtn.WinName := '2003 Server Family';
      end;
    6 :
      case p.dwMinorVersion of
        0 :
          if p.wProductType = VER_NT_WORKSTATION then
            rtn.WinVer := 'Vista'
          else
            rtn.WinVer := 'Server 2008';
        1 :
          if p.wProductType = VER_NT_WORKSTATION then
            rtn.WinVer := '7'
          else
            rtn.WinVer := 'Server 2008 R2';
        2 : // 윈 8 정보 추가
          if p.wProductType = VER_NT_WORKSTATION then
            rtn.WinVer := '8'
          else
            rtn.WinVer := 'Server 2012';
        3 : // 윈 8 정보 추가
          if p.wProductType = VER_NT_WORKSTATION then
            rtn.WinVer := '8.1'
          else
            rtn.WinVer := 'Server 2012 R2';
      end;
   10 :
    begin
    // 윈도우 10
      rtn.WinVer := '10';
      case p.dwMinorVersion of
        0 : ; // .. 하위 정보 확인 필요
      end;

    // windows 11의 공식 메이저 버전은 10이다..
    // 11인걸 판단하기 위해 'kernel32.dll'의 빌드번호가 22000 이상인지 확인한다. 22_0517 10:05:21 kku
      if IsWin11 then
        rtn.WinVer := '11';
    end;
    0 :
    begin
    // 안전모드에서는 실패한다. 22_1116 13:11:54 kku
      rtn.WinVer := ExtrNumStr(GetRegValueAsString(HKEY_LOCAL_MACHINE,
              'SOFTWARE\Microsoft\Windows NT\CurrentVersion', 'ProductName'));
      if rtn.WinVer = '10' then
      begin
        if IsWin11 then
          rtn.WinVer := '11';
      end else
      if rtn.WinVer = '' then
        rtn.WinVer := '?';
    end;
  end;
  rtn.WinName := 'Windows ' + rtn.WinVer;

  //Get Product Substring
  if (IsEX) then begin
    case p.wProductType of
      VER_NT_WORKSTATION:
        begin
          if (p.dwMajorVersion = 4) then
            rtn.WinName := rtn.WinName + ' Workstation 4.0'
          else if (p.wSuiteMask and VER_SUITE_PERSONAL) = VER_SUITE_PERSONAL then
            rtn.WinName := rtn.WinName + ' Home Edition'
          else if p.dwMajorVersion = 6 then
          begin
            // 비스타의 경우.. 버전정보를 모르겠다.. 아직은..



          end
          else
            rtn.WinName := rtn.WinName + ' Professional';
        end;
      VER_NT_SERVER:
        begin
          if ((p.dwMajorVersion = 5) and (p.dwMinorVersion = 2)) then begin
            if (p.wSuiteMask and VER_SUITE_DATACENTER) = VER_SUITE_DATACENTER then
              rtn.WinName := rtn.WinName + ' Datacenter Edition'
            else if (p.wSuiteMask and VER_SUITE_ENTERPRISE) = VER_SUITE_ENTERPRISE then
              rtn.WinName := rtn.WinName + ' Enterprise Edition'
            else if (p.wSuiteMask and VER_SUITE_BLADE) = VER_SUITE_BLADE then
              rtn.WinName := rtn.WinName + ' Web Edition'
            else
              rtn.WinName := rtn.WinName + ' Standard Edition';
          end
          else if ((p.dwMajorVersion = 5) and (p.dwMinorVersion = 0)) then begin
            if (p.wSuiteMask and VER_SUITE_DATACENTER) = VER_SUITE_DATACENTER then
              rtn.WinName := rtn.WinName + ' Datacenter Server'
            else if (p.wSuiteMask and VER_SUITE_ENTERPRISE) = VER_SUITE_ENTERPRISE then
              rtn.WinName := rtn.WinName + ' Advanced Server'
            else
              rtn.WinName := rtn.WinName + ' Server';
          end
          else if (p.dwMajorVersion = 6) then

          else begin
            if (p.wSuiteMask and VER_SUITE_ENTERPRISE) = VER_SUITE_ENTERPRISE then
              rtn.WinName := rtn.WinName + ' Server 4.0 Enterprise Edition'
            else
              rtn.WinName := rtn.WinName + ' Server 4.0';
          end;
        end;
    end; {case of}

    if p.dwMajorVersion = 6 then
    begin
      hModule := GetModuleHandle('kernel32.dll');
      procGetProductInfo :=  GetProcAddress( hModule  ,'GetProductInfo');
      if Assigned(procGetProductInfo) then
      begin
        procGetProductInfo(p.dwMajorVersion, p.dwMinorVersion, 0, 0, @ProdType);

        case ProdType of
          PRODUCT_ULTIMATE : rtn.WinName := rtn.WinName + ' Ultimate';
          PRODUCT_PROFESSIONAL : rtn.WinName := rtn.WinName + ' Professional';
          PRODUCT_HOME_PREMIUM : rtn.WinName := rtn.WinName + ' Home Premium Edition';
          PRODUCT_HOME_BASIC : rtn.WinName := rtn.WinName + ' Home Basic Edition';
          PRODUCT_ENTERPRISE : rtn.WinName := rtn.WinName + ' Enterprise Edition';
          PRODUCT_BUSINESS : rtn.WinName := rtn.WinName + ' Business Edition';
          PRODUCT_STARTER : rtn.WinName := rtn.WinName + ' Starter Edition';
          PRODUCT_CLUSTER_SERVER : rtn.WinName := rtn.WinName + ' Cluster Server Edition';
          PRODUCT_DATACENTER_SERVER : rtn.WinName := rtn.WinName + ' Datacenter Edition';
          PRODUCT_DATACENTER_SERVER_CORE : rtn.WinName := rtn.WinName + ' Datacenter Edition (core installation)';
          PRODUCT_ENTERPRISE_SERVER : rtn.WinName := rtn.WinName + ' Enterprise Edition';
          PRODUCT_ENTERPRISE_SERVER_CORE : rtn.WinName := rtn.WinName + ' Enterprise Edition (core installation)';
          PRODUCT_ENTERPRISE_SERVER_IA64 : rtn.WinName := rtn.WinName + ' Enterprise Edition for Itanium-based Systems)';
          PRODUCT_SMALLBUSINESS_SERVER : rtn.WinName := rtn.WinName + ' Small Business Server';
          PRODUCT_SMALLBUSINESS_SERVER_PREMIUM : rtn.WinName := rtn.WinName + ' Small Business Server Premium Edition';
          PRODUCT_STANDARD_SERVER :  rtn.WinName := rtn.WinName + ' Standard Edition';
          PRODUCT_STANDARD_SERVER_CORE : rtn.WinName := rtn.WinName + ' Standard Edition (core installation)';
          PRODUCT_WEB_SERVER : rtn.WinName := rtn.WinName + ' Web Server Edition';
        end;

        FreeModule(hModule);

      end;
    end;


  end {If IsEx}
  else begin
    // Test for specific product on Windows NT 4.0 SP5 and earlier
    resVer := TRegistry.Create(KEY_READ);
    try
      resVer.RootKey := HKEY_LOCAL_MACHINE;
      resver.OpenKeyReadOnly('SYSTEM\\CurrentControlSet\\Control\\ProductOptions');

      szProduct := resVer.ReadString('ProductType');

      if (Strcomp('WINNT', PChar(szProduct)) = 0) then
        rtn.WinName := rtn.WinName + ' Workstation '
      else if (Strcomp('LANMANNT', PChar(szProduct)) = 0) then
        rtn.WinName := rtn.WinName + ' Server '
      else if (Strcomp('SERVERNT', PChar(szProduct)) = 0) then
        rtn.WinName := rtn.WinName + ' Advanced Server ';

      rtn.WinName := rtn.WinName + IntToStr(p.dwMajorVersion) + '.' + IntToStr(p.dwMinorVersion);
    finally
      resVer.Free;
    end;
  end;{else IsEx}

  //Get Version
  rtn.Version.Major := p.dwMajorVersion;
  rtn.Version.Minor := p.dwMinorVersion;
  rtn.Version.Build := p.dwBuildNumber;

  //Get Service Pack
  if ((p.dwMajorVersion = 4) and (lstrcmpiA(p.szCSDVersion, 'Service Pack 6' ) = 0)) then begin
    // Test for SP6 versus SP6a.
    resVer := TRegistry.Create(KEY_READ);
    try
      resVer.RootKey := HKEY_LOCAL_MACHINE;
      if resVer.OpenKeyReadOnly('SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Hotfix\\Q246009') then
        rtn.ServicePack := 'Service Pack 6a'
      else
        rtn.ServicePack := String(p.szCSDVersion);    //warring 제거
    finally
      resVer.Free;
    end;
  end
  else
    //Else Other All Version
    if Boolean(p.wServicePackMajor) or Boolean(p.wServicePackMinor) then begin
    	rtn.ServicePack := 'Service Pack ' + intToStr(p.wServicePackMajor);
      if Boolean(p.wServicePackMinor) then
      	rtn.ServicePack := rtn.ServicePack + '.' + IntToStr(p.wServicePackMinor);
    end
    else
    	rtn.ServicePack := String(p.szCSDVersion);

  //Create ID String and Return
  rtn.WinID := 'Microsoft ' + rtn.WinName + '(Version ' + IntToStr(rtn.Version.Major) + '.'  + IntToStr(rtn.Version.Minor)
               + ' Build ' + IntToStr(rtn.Version.Build) + ') ' + rtn.ServicePack;

  Result := rtn;
end;

function ParseWinNT(p: TOSVersionInfoExW; IsEx: Boolean): TWinVerInfo;
var
  rtn: TWinVerInfo;

  resVer: TRegistry;
  szProduct: String;
  procGetProductInfo : TGetProductInfo;
  hModule : THandle;
  ProdType : DWORD;
  dwMajor, dwMinor, dwService, dwBuildNum : DWORD;
  bIsServer : Boolean;
begin
  GetWindowsVersion(dwMajor, dwMinor, dwService, dwBuildNum, bIsServer);

  p.dwMajorVersion := dwMajor;
  p.dwMinorVersion := dwMinor;
  p.dwBuildNumber := dwBuildNum;
  if not bIsServer then
  begin
    p.wProductType := VER_NT_WORKSTATION
  end;

  //Get Windows Product
  if p.dwMajorVersion = 5 then begin
  	if	p.dwMinorVersion = 2 then
    	rtn.WinName := 'Windows 2003 Server Family'
  	else if p.dwMinorVersion = 1 then
    	rtn.WinName := 'Windows XP'
  	else if p.dwMinorVersion = 0 then
    	rtn.WinName := 'Windows 2000';
  end

  else if p.dwMajorVersion = 6 then
    if p.dwMinorVersion = 1 then
    begin
      if p.wProductType = VER_NT_WORKSTATION then
        rtn.WinName := 'Windows 7'
      else rtn.WinName := 'Windows Server 2008 R2';
    end  else if p.dwMinorVersion = 0 then
    begin
      if p.wProductType = VER_NT_WORKSTATION then
        rtn.WinName := 'Windows Vista'
      else rtn.WinName := 'Windows Server 2008';
    end
  else
    rtn.WinName := 'Windows NT';

  //Get Product Substring
  if (IsEX) then begin
    case p.wProductType of
      VER_NT_WORKSTATION:
        begin
          if (p.dwMajorVersion = 4) then
            rtn.WinName := rtn.WinName + ' Workstation 4.0'
          else if (p.wSuiteMask and VER_SUITE_PERSONAL) = VER_SUITE_PERSONAL then
            rtn.WinName := rtn.WinName + ' Home Edition'
          else if p.dwMajorVersion = 6 then
          begin
            // 비스타의 경우.. 버전정보를 모르겠다.. 아직은..
          end else
            rtn.WinName := rtn.WinName + ' Professional';
        end;
      VER_NT_SERVER:
        begin
          if ((p.dwMajorVersion = 5) and (p.dwMinorVersion = 2)) then begin
            if (p.wSuiteMask and VER_SUITE_DATACENTER) = VER_SUITE_DATACENTER then
              rtn.WinName := rtn.WinName + ' Datacenter Edition'
            else if (p.wSuiteMask and VER_SUITE_ENTERPRISE) = VER_SUITE_ENTERPRISE then
              rtn.WinName := rtn.WinName + ' Enterprise Edition'
            else if (p.wSuiteMask and VER_SUITE_BLADE) = VER_SUITE_BLADE then
              rtn.WinName := rtn.WinName + ' Web Edition'
            else
              rtn.WinName := rtn.WinName + ' Standard Edition';
          end
          else if ((p.dwMajorVersion = 5) and (p.dwMinorVersion = 0)) then begin
            if (p.wSuiteMask and VER_SUITE_DATACENTER) = VER_SUITE_DATACENTER then
              rtn.WinName := rtn.WinName + ' Datacenter Server'
            else if (p.wSuiteMask and VER_SUITE_ENTERPRISE) = VER_SUITE_ENTERPRISE then
              rtn.WinName := rtn.WinName + ' Advanced Server'
            else
              rtn.WinName := rtn.WinName + ' Server';
          end
          else if (p.dwMajorVersion = 6) then

          else begin
            if (p.wSuiteMask and VER_SUITE_ENTERPRISE) = VER_SUITE_ENTERPRISE then
              rtn.WinName := rtn.WinName + ' Server 4.0 Enterprise Edition'
            else
              rtn.WinName := rtn.WinName + ' Server 4.0';
          end;
        end;
    end; {case of}

    if p.dwMajorVersion = 6 then
    begin
      hModule := GetModuleHandle('kernel32.dll');
      procGetProductInfo :=  GetProcAddress( hModule  ,'GetProductInfo');
      if Assigned(procGetProductInfo) then
      begin
        procGetProductInfo(p.dwMajorVersion, p.dwMinorVersion, 0, 0, @ProdType);

        case ProdType of
          PRODUCT_ULTIMATE : rtn.WinName := rtn.WinName + ' Ultimate';
          PRODUCT_PROFESSIONAL : rtn.WinName := rtn.WinName + ' Professional';
          PRODUCT_HOME_PREMIUM : rtn.WinName := rtn.WinName + ' Home Premium Edition';
          PRODUCT_HOME_BASIC : rtn.WinName := rtn.WinName + ' Home Basic Edition';
          PRODUCT_ENTERPRISE : rtn.WinName := rtn.WinName + ' Enterprise Edition';
          PRODUCT_BUSINESS : rtn.WinName := rtn.WinName + ' Business Edition';
          PRODUCT_STARTER : rtn.WinName := rtn.WinName + ' Starter Edition';
          PRODUCT_CLUSTER_SERVER : rtn.WinName := rtn.WinName + ' Cluster Server Edition';
          PRODUCT_DATACENTER_SERVER : rtn.WinName := rtn.WinName + ' Datacenter Edition';
          PRODUCT_DATACENTER_SERVER_CORE : rtn.WinName := rtn.WinName + ' Datacenter Edition (core installation)';
          PRODUCT_ENTERPRISE_SERVER : rtn.WinName := rtn.WinName + ' Enterprise Edition';
          PRODUCT_ENTERPRISE_SERVER_CORE : rtn.WinName := rtn.WinName + ' Enterprise Edition (core installation)';
          PRODUCT_ENTERPRISE_SERVER_IA64 : rtn.WinName := rtn.WinName + ' Enterprise Edition for Itanium-based Systems)';
          PRODUCT_SMALLBUSINESS_SERVER : rtn.WinName := rtn.WinName + ' Small Business Server';
          PRODUCT_SMALLBUSINESS_SERVER_PREMIUM : rtn.WinName := rtn.WinName + ' Small Business Server Premium Edition';
          PRODUCT_STANDARD_SERVER :  rtn.WinName := rtn.WinName + ' Standard Edition';
          PRODUCT_STANDARD_SERVER_CORE : rtn.WinName := rtn.WinName + ' Standard Edition (core installation)';
          PRODUCT_WEB_SERVER : rtn.WinName := rtn.WinName + ' Web Server Edition';

        end;

        FreeModule(hModule);
      end;
    end;


  end {If IsEx}
  else begin
    // Test for specific product on Windows NT 4.0 SP5 and earlier
    resVer := TRegistry.Create(KEY_READ);
    try
      resVer.RootKey := HKEY_LOCAL_MACHINE;
      resver.OpenKeyReadOnly('SYSTEM\\CurrentControlSet\\Control\\ProductOptions');

      szProduct := resVer.ReadString('ProductType');

      if (Strcomp('WINNT', PChar(szProduct)) = 0) then
        rtn.WinName := rtn.WinName + ' Workstation '
      else if (Strcomp('LANMANNT', PChar(szProduct)) = 0) then
        rtn.WinName := rtn.WinName + ' Server '
      else if (Strcomp('SERVERNT', PChar(szProduct)) = 0) then
        rtn.WinName := rtn.WinName + ' Advanced Server ';

      rtn.WinName := rtn.WinName + IntToStr(p.dwMajorVersion) + '.' + IntToStr(p.dwMinorVersion);
    finally
      resVer.Free;
    end;
  end;{else IsEx}

  //Get Version
  rtn.Version.Major := p.dwMajorVersion;
  rtn.Version.Minor := p.dwMinorVersion;
  rtn.Version.Build := (p.dwBuildNumber and $FFFF);

  //Get Service Pack
  if ((p.dwMajorVersion = 4) and (lstrcmpW(p.szCSDVersion, 'Service Pack 6' ) = 0)) then begin
    // Test for SP6 versus SP6a.
    resVer := TRegistry.Create(KEY_READ);
    try
      resVer.RootKey := HKEY_LOCAL_MACHINE;
      if resVer.OpenKeyReadOnly('SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Hotfix\\Q246009') then
        rtn.ServicePack := 'Service Pack 6a'
      else
        rtn.ServicePack := p.szCSDVersion;
    finally
      resVer.Free;
    end;
  end
  else
    //Else Other All Version
    if Boolean(p.wServicePackMajor) or Boolean(p.wServicePackMinor) then begin
    	rtn.ServicePack := 'Service Pack ' + intToStr(p.wServicePackMajor);
      if Boolean(p.wServicePackMinor) then
      	rtn.ServicePack := rtn.ServicePack + '.' + IntToStr(p.wServicePackMinor);
    end
    else
    	rtn.ServicePack := p.szCSDVersion;

  //Create ID String and Return
  rtn.WinID := 'Microsoft ' + rtn.WinName + #10#13'(Version ' + IntToStr(rtn.Version.Major) + '.'  + IntToStr(rtn.Version.Minor)
               + ' Build ' + IntToStr(rtn.Version.Build) + ') ' + rtn.ServicePack;

  Result := rtn;
end;

function ParseWin9x(p: TOSVersionInfo): TWinVerInfo;
var
  rtn: TWinVerInfo;
begin
  if ((p.dwMajorVersion = 4) and (p.dwMinorVersion = 0)) then begin
    rtn.WinName := 'Windows 95';
    if ((p.szCSDVersion[1] = 'C') or (p.szCSDVersion[1] = 'B')) then
      rtn.WinName := rtn.WinName + ' OSR2';
  end
  else if ((p.dwMajorVersion = 4) and (p.dwMinorVersion = 10)) then begin
    rtn.WinName := 'Windows 98';
    if (p.szCSDVersion[1] = 'A') then
      rtn.WinName := rtn.WinName + ' SE';
  end
  else if ((p.dwMajorVersion = 4) and (p.dwMinorVersion = 90)) then
    rtn.WinName := 'Windows Millennium Edition';

  rtn.Version.Major := p.dwMajorVersion;
  rtn.Version.Minor := p.dwMinorVersion;
  rtn.Version.Build := (p.dwBuildNumber and $FFFF);

  rtn.ServicePack := p.szCSDVersion;
  if ((p.szCSDVersion[1] = 'C') or (p.szCSDVersion[1] = 'B') or (p.szCSDVersion[1] = 'A')) then
    rtn.ServicePack := RightStr(rtn.ServicePack, Length(rtn.ServicePack) - 2);
  
  //Create ID String and Return
  rtn.WinID := 'Microsoft ' + rtn.WinName + #10#13'(Version ' + IntToStr(rtn.Version.Major) + '.'  + IntToStr(rtn.Version.Minor)
               + ' Build ' + IntToStr(rtn.Version.Build) + ') ' + rtn.ServicePack;

  Result := rtn;
end;

function GetWindowsVersion(var AMajor, AMinor: DWORD): Boolean;
var
  Buf: LPWKSTA_INFO_100;
begin
  Result := False;
  if MajorCache <> 0 then
  begin
    AMajor := MajorCache;
    AMinor := MinorCache;
    Result := True;
    Exit;
  end;
  if NetWkstaGetInfo(nil, 100, @Buf) = NERR_Success then
  begin
    MajorCache := Buf.wki100_ver_major;
    AMajor := MajorCache;
    MinorCache := Buf.wki100_ver_minor;
    AMinor := MinorCache;
    NetApiBufferFree(Buf);
    Result := True;
  end;
end;

function GetWindowsVersion(var AMajor, AMinor, AServicePack, ABuildNumber: DWORD;
  var AIsServer: Boolean): Boolean; overload;
var
  I: Integer;
  osvi: OSVERSIONINFOEX;
  ConditionMask: LONGLONG;
begin
  Result := False;
  AMajor := 0;
  AMinor := 0;
  AServicePack := 0;
  ABuildNumber := 0;

  if not GetWindowsVersion(AMajor, AMinor) then
    exit;

  if ServicePackCache <> MAXDWORD then
  begin
    AServicePack := ServicePackCache;
    AIsServer := IsServerCache;
    Result := True;
    Exit;
  end;
  ZeroMemory(@osvi, SizeOf(OSVERSIONINFOEX));
  osvi.dwOSVersionInfoSize := SizeOf(OSVERSIONINFOEX);
  ConditionMask := 0;
  ConditionMask := VerSetConditionMask(ConditionMask, VER_SERVICEPACKMAJOR, VER_EQUAL);
  for I := 0 to SERVICE_MAX_COUNT - 1 do
  begin
    osvi.wServicePackMajor := I;
    if VerifyVersionInfo(osvi, VER_SERVICEPACKMAJOR, ConditionMask) then
    begin
      ServicePackCache := I;
      AServicePack := ServicePackCache;
      ZeroMemory(@osvi, SizeOf(OSVERSIONINFOEX));
      osvi.dwOSVersionInfoSize := SizeOf(OSVERSIONINFOEX);
      osvi.wProductType := VER_NT_SERVER;
      ConditionMask := 0;
      ConditionMask := VerSetConditionMask(ConditionMask, VER_PRODUCT_TYPE,
        VER_EQUAL);
      IsServerCache := VerifyVersionInfo(osvi, VER_PRODUCT_TYPE, ConditionMask);
      AIsServer := IsServerCache;
      Result := True;
      break;
    end;
  end;

  ConditionMask := 0;
  ConditionMask := VerSetConditionMask(ConditionMask, VER_BUILDNUMBER,
    VER_EQUAL);
  for I := 0 to 10000 do
  begin
    osvi.dwBuildNumber := I;
    if VerifyVersionInfo(osvi, VER_BUILDNUMBER, ConditionMask) then
    begin
      ABuildNumber := I;
      Result := True;
      Exit;
    end;
  end;
end;

initialization
  MajorCache := 0;
  MinorCache := 0;
  ServicePackCache := MAXDWORD;
  IsServerCache := False;

end.





