Getting the Windows version revisited

In my last blog post Getting the Windows version, I claimed that there is no way to get the actual version number of Windows 10 without reading the version information of the Kernel32.dll and interpreting it 1.

Since then I have been told that there is actually a Registry key that contains the Windows version:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion

It has several entries that can be used to get the same version info that the Winver tool displays to the user. On Windows 10 that’s:

  • ProductName
  • ReleaseId
  • CurrentBuildNumber
  • UBR

On my test installation they get me
“Windows 10 Pro” Version “1809” (OS Build “17762”.”437″)

(The quotes denote values read from the registry.)

By changing the ReleaseId value I verified that Winver (at least the on my Windows 10 installation) also reads these entries.

Some of these entries are available for older versions of Windows too, while some other entries apparently are only available in older versions. On the installations I have available for tests, it looks like this:

Entry Windows 10 Windows 8.1 Windows 7
ProductName x x x
ReleaseId x
CurrentBuildNumber x x x
UBR x x
CSDVersion x

There are some other interesting entries there, but I won’t go into that any further.

So I wrote another function that reads these entries and creates a version string from it. It returns the following results:

Windows Version Output
7 Windows 7 Professional (OS Build 7601.24411) Service Pack 1
8.1 Windows 8.1 Pro (OS Build 9600.19327)
10 Windows 10 Pro Version 1809 (OS Build 17763.437)

There is one catch though: Windows 64 bit versions by default return different view of the registry to 32 bit programs than they do to 64 bit programs. In order for a 32 bit program to read the “real” registry, it must open the registry specifying the KEY_WOW64_64KEY flag in addition to the desired access.

So here is the code:

function TryGetWindowsVersionFromRegistry(out _Values: TWinCurrentRec): Boolean;
var
  Reg: TRegistry;
begin
  // We need to read the real registry, not the 32 bit view, because some of the entries
  // don't exist there.
  Reg := TRegistry.Create(KEY_READ or KEY_WOW64_64KEY);
  try
    Reg.RootKey := HKEY_LOCAL_MACHINE;
    Result := Reg.OpenKeyReadOnly('SOFTWARE\Microsoft\Windows NT\CurrentVersion');
    if not Result then
      Exit; //==>

    _Values.BuildLab := TRegistry_ReadString(Reg, 'BuildLab');
    _Values.BuildLabEx := TRegistry_ReadString(Reg, 'BuildLabEx');
    _Values.CSDBuildNumber := TRegistry_ReadString(Reg, 'CSDBuildNumber');
    _Values.CSDVersion := TRegistry_ReadString(Reg, 'CSDVersion');
    _Values.CurrentBuildNumber := TRegistry_ReadString(Reg, 'CurrentBuildNumber');
    _Values.CurrentVersion := TRegistry_ReadString(Reg, 'CurrentVersion');
    _Values.EditionId := TRegistry_ReadString(Reg, 'EditionId');
    _Values.ProductName := TRegistry_ReadString(Reg, 'ProductName');
    _Values.ReleaseId := TRegistry_ReadString(Reg, 'ReleaseId');
    _Values.UBR := TRegistry_ReadInteger(Reg, 'UBR');
  finally
    FreeAndNil(Reg);
  end;
end;

function GetWindowsVersionStringFromRegistry: string;
var
  Values: TWinCurrentRec;
begin
  if not TryGetWindowsVersionFromRegistry(Values) then begin
    Result := _('Unknown Windows 10 version (registry key does not exist)');
    Exit; //==>
  end;

  if Values.ProductName = '' then begin
    Result := _('Unknown Windows version (ProductName entry cannot be read)');
    Exit; //==>
  end;
  Result := Values.ProductName;
  if Values.ReleaseId <> '' then
    Result := Result + ' Version ' + Values.ReleaseId;
  if Values.CurrentBuildNumber <> '' then begin
    Result := Result + ' (OS Build ' + Values.CurrentBuildNumber;
    if Values.UBR <> 0 then
      Result := Result + '.' + IntToStr(Values.UBR);
    Result := Result + ')';
  end;
  if Values.CSDVersion <> '' then
    Result := Result + ' ' + Values.CSDVersion;
end;

That code works even with Delphi 6, but you will have to declare the constant

KEY_WOW64_64KEY        = $0100;

there since it did not exist when Delphi 6 was released. It does exist in Delphi 2007. I haven’t tried other versions.

These functions are again also available in the unit u_dzOsUtils of my dzlib library.

If you would like to comment on this, go to this post in the international Delphi Praxis forum.

—–

1 Actually I did not want to claim that there is no way but that I could not find one.