Repository: moby/moby (language: go)

Issue:
Wrong operating system reported by docker info on Windows
**Description**

For WCOW case, _Operating System_ reported by `docker info` is not right.

**Steps to reproduce the issue:**
1. _Switch to Windows containers_ on Docker Desktop
2. Open `cmd`
3. Type `docker info`

**Describe the results you received:**

```
> docker info
Client:
 Context:    default
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc., v0.7.1)
  compose: Docker Compose (Docker Inc., v2.2.3)
  hub: Docker Hub (Docker Inc., 346ece73cb-dirty)
  scan: Docker Scan (Docker Inc., v0.17.0)

Server:
 Containers: 0
  Running: 0
  Paused: 0
  Stopped: 0
 Images: 4
 Server Version: 20.10.12
 Storage Driver: windowsfilter
  Windows:
 Logging Driver: json-file
 Plugins:
  Volume: local
  Network: ics internal l2bridge l2tunnel nat null overlay private transparent
  Log: awslogs etwlogs fluentd gcplogs gelf json-file local logentries splunk syslog
 Swarm: inactive
 Default Isolation: hyperv
 Kernel Version: 10.0 22000 (22000.1.amd64fre.co_release.210604-1628)
 Operating System: Windows 10 Pro Version 2009 (OS Build 22000.527)
 OSType: windows
 Architecture: x86_64
 CPUs: 32
 Total Memory: 63.93GiB
 Name: pc-sff
 ID: BJ23:IQXR:IXL3:FAEE:TGKX:5XWD:7ZKB:6GXF:P5BL:LU3E:T45O:MULU
 Docker Root Dir: C:\ProgramData\Docker
 Debug Mode: false
 Username: ...
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Live Restore Enabled: false
 Product License: Community Engine
```

Here we have:

```
Operating System: Windows 10 Pro Version 2009 (OS Build 22000.527)
```

**Describe the results you expected:**

Expected:

```
Operating System: Windows 11 Pro Version 21H2 (OS Build 22000.527)
```

**Additional information you deem important (e.g. issue happens only occasionally):**

Looking at the source code, we rely on the HKLM hive in the registry: https://github.com/moby/moby/blob/dc8fb8f03ba30a80b036706ef89d4dd156729216/pkg/parsers/operatingsystem/operatingsystem_windows.go#L45

Here are the registry values for me:

```
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion]
"BaseBuildRevisionNumber"=dword:00000001
"BuildBranch"="co_release"
"BuildGUID"="ffffffff-ffff-ffff-ffff-ffffffffffff"
"BuildLab"="22000.co_release.210604-1628"
"BuildLabEx"="22000.1.amd64fre.co_release.210604-1628"
"CompositionEditionID"="Enterprise"
"CurrentBuild"="22000"
"CurrentBuildNumber"="22000"
"CurrentMajorVersionNumber"=dword:0000000a
"CurrentMinorVersionNumber"=dword:00000000
"CurrentType"="Multiprocessor Free"
"CurrentVersion"="6.3"
"DisplayVersion"="21H2"
"EditionID"="Enterprise"
"EditionSubManufacturer"=""
"EditionSubstring"=""
"EditionSubVersion"=""
"InstallationType"="Client"
"InstallDate"=dword:00000000
"ProductName"="Windows 10 Enterprise"
"ReleaseId"="2009"
"SoftwareType"="System"
"SystemRoot"="C:\\Windows"
"UBR"=dword:0000020f
"RegisteredOwner"="[redacted]"
"RegisteredOrganization"=""
"PathName"="C:\\Windows"
```

* `Windows 10 Pro`: As I can see `ProductName` is being used in the source code so should be `Windows 10 Enterprise` at least. Not sure where he found `Windows 10 Pro`.
* `2009`: Using `DisplayVersion` (`21H2`) should be more accurate with what is reported by sysinfo.

`winver` output:

![image](https://user-images.githubusercontent.com/1951866/155297027-b8454a86-5e81-413f-801b-d95b59f5ade0.png)

cc @thaJeztah 


Discussion / hints:
So, this information is provided by the `pkg/parsers/operatingsystem` package, which uses the Windows registry to get the details; https://github.com/moby/moby/blob/dc8fb8f03ba30a80b036706ef89d4dd156729216/pkg/parsers/operatingsystem/operatingsystem_windows.go#L11

I see that originally it did (e.g.) `syscall.StringToUTF16Ptr("ProductName")` (https://github.com/moby/moby/commit/f7ae2a372d6d845040cdf40b246f4eec62512398 (https://github.com/moby/moby/pull/13099)), but later this changed to using the registry (https://github.com/moby/moby/commit/6de9f90417f1295fcdc2f1c977178bff7a735f99 (https://github.com/moby/moby/pull/36451)) to get the ReleaseID (version) and UBR. Wondering if this would've been because Windows Desktop and Server would otherwise produce the wrong information?
WRT `ReleaseId`. MS no longer updates it. One instead needs to lookup `DisplayVersion` (and fallback to `ReleaseId` if `DisplayVersion` doesn't exist).
@thaJeztah May I take this issue? I have already sent a PR. 

Fix patch:
diff --git a/pkg/parsers/operatingsystem/operatingsystem_windows.go b/pkg/parsers/operatingsystem/operatingsystem_windows.go
index 562de75474feb..0b756141b6f4b 100644
--- a/pkg/parsers/operatingsystem/operatingsystem_windows.go
+++ b/pkg/parsers/operatingsystem/operatingsystem_windows.go
@@ -1,53 +1,55 @@
 package operatingsystem // import "github.com/docker/docker/pkg/parsers/operatingsystem"
 
 import (
-	"fmt"
+	"errors"
 
 	"github.com/Microsoft/hcsshim/osversion"
+	"golang.org/x/sys/windows"
 	"golang.org/x/sys/windows/registry"
 )
 
+// VER_NT_WORKSTATION, see https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoexa
+const verNTWorkstation = 0x00000001 // VER_NT_WORKSTATION
+
 // GetOperatingSystem gets the name of the current operating system.
 func GetOperatingSystem() (string, error) {
-	os, err := withCurrentVersionRegistryKey(func(key registry.Key) (os string, err error) {
-		if os, _, err = key.GetStringValue("ProductName"); err != nil {
-			return "", err
-		}
-
-		releaseId, _, err := key.GetStringValue("ReleaseId")
-		if err != nil {
-			return
-		}
-		os = fmt.Sprintf("%s Version %s", os, releaseId)
+	osversion := windows.RtlGetVersion() // Always succeeds.
+	rel := windowsOSRelease{
+		IsServer: osversion.ProductType != verNTWorkstation,
+		Build:    osversion.BuildNumber,
+	}
 
-		buildNumber, _, err := key.GetStringValue("CurrentBuildNumber")
-		if err != nil {
-			return
+	// Make a best-effort attempt to retrieve the display version and
+	// Update Build Revision by querying undocumented registry values.
+	key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
+	if err == nil {
+		defer key.Close()
+		if ver, err := getFirstStringValue(key,
+			"DisplayVersion", /* Windows 20H2 and above */
+			"ReleaseId",      /* Windows 2009 and below */
+		); err == nil {
+			rel.DisplayVersion = ver
 		}
-		ubr, _, err := key.GetIntegerValue("UBR")
-		if err != nil {
-			return
+		if ubr, _, err := key.GetIntegerValue("UBR"); err == nil {
+			rel.UBR = ubr
 		}
-		os = fmt.Sprintf("%s (OS Build %s.%d)", os, buildNumber, ubr)
-
-		return
-	})
-
-	if os == "" {
-		// Default return value
-		os = "Unknown Operating System"
 	}
 
-	return os, err
+	return rel.String(), nil
 }
 
-func withCurrentVersionRegistryKey(f func(registry.Key) (string, error)) (string, error) {
-	key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
-	if err != nil {
-		return "", err
+func getFirstStringValue(key registry.Key, names ...string) (string, error) {
+	for _, n := range names {
+		val, _, err := key.GetStringValue(n)
+		if err != nil {
+			if !errors.Is(err, registry.ErrNotExist) {
+				return "", err
+			}
+			continue
+		}
+		return val, nil
 	}
-	defer key.Close()
-	return f(key)
+	return "", registry.ErrNotExist
 }
 
 // GetOperatingSystemVersion gets the version of the current operating system, as a string.
diff --git a/pkg/parsers/operatingsystem/windows_os_string.go b/pkg/parsers/operatingsystem/windows_os_string.go
new file mode 100644
index 0000000000000..41240b46e6410
--- /dev/null
+++ b/pkg/parsers/operatingsystem/windows_os_string.go
@@ -0,0 +1,33 @@
+package operatingsystem // import "github.com/docker/docker/pkg/parsers/operatingsystem"
+
+import (
+	"fmt"
+	"strings"
+)
+
+type windowsOSRelease struct {
+	IsServer       bool
+	DisplayVersion string
+	Build          uint32
+	UBR            uint64
+}
+
+// String formats the OS release data similar to what is displayed by
+// winver.exe.
+func (r *windowsOSRelease) String() string {
+	var b strings.Builder
+	b.WriteString("Microsoft Windows")
+	if r.IsServer {
+		b.WriteString(" Server")
+	}
+	if r.DisplayVersion != "" {
+		b.WriteString(" Version ")
+		b.WriteString(r.DisplayVersion)
+	}
+	_, _ = fmt.Fprintf(&b, " (OS Build %d", r.Build)
+	if r.UBR > 0 {
+		_, _ = fmt.Fprintf(&b, ".%d", r.UBR)
+	}
+	b.WriteByte(')')
+	return b.String()
+}


Test patch:
diff --git a/pkg/parsers/operatingsystem/windows_os_string_test.go b/pkg/parsers/operatingsystem/windows_os_string_test.go
new file mode 100644
index 0000000000000..0a56eaff808c7
--- /dev/null
+++ b/pkg/parsers/operatingsystem/windows_os_string_test.go
@@ -0,0 +1,89 @@
+package operatingsystem
+
+import (
+	"testing"
+)
+
+func Test_windowsOSRelease_String(t *testing.T) {
+	tests := []struct {
+		name string
+		r    windowsOSRelease
+		want string
+	}{
+		{
+			name: "Flavor=client/DisplayVersion=yes/UBR=yes",
+			r: windowsOSRelease{
+				DisplayVersion: "1809",
+				Build:          17763,
+				UBR:            2628,
+			},
+			want: "Microsoft Windows Version 1809 (OS Build 17763.2628)",
+		},
+		{
+			name: "Flavor=client/DisplayVersion=yes/UBR=no",
+			r: windowsOSRelease{
+				DisplayVersion: "1809",
+				Build:          17763,
+			},
+			want: "Microsoft Windows Version 1809 (OS Build 17763)",
+		},
+		{
+			name: "Flavor=client/DisplayVersion=no/UBR=yes",
+			r: windowsOSRelease{
+				Build: 17763,
+				UBR:   1879,
+			},
+			want: "Microsoft Windows (OS Build 17763.1879)",
+		},
+		{
+			name: "Flavor=client/DisplayVersion=no/UBR=no",
+			r: windowsOSRelease{
+				Build: 10240,
+			},
+			want: "Microsoft Windows (OS Build 10240)",
+		},
+		{
+			name: "Flavor=server/DisplayVersion=yes/UBR=yes",
+			r: windowsOSRelease{
+				IsServer:       true,
+				DisplayVersion: "21H2",
+				Build:          20348,
+				UBR:            169,
+			},
+			want: "Microsoft Windows Server Version 21H2 (OS Build 20348.169)",
+		},
+		{
+			name: "Flavor=server/DisplayVersion=yes/UBR=no",
+			r: windowsOSRelease{
+				IsServer:       true,
+				DisplayVersion: "20H2",
+				Build:          19042,
+			},
+			want: "Microsoft Windows Server Version 20H2 (OS Build 19042)",
+		},
+		{
+			name: "Flavor=server/DisplayVersion=no/UBR=yes",
+			r: windowsOSRelease{
+				IsServer: true,
+				Build:    17763,
+				UBR:      107,
+			},
+			want: "Microsoft Windows Server (OS Build 17763.107)",
+		},
+		{
+			name: "Flavor=server/DisplayVersion=no/UBR=no",
+			r: windowsOSRelease{
+				IsServer: true,
+				Build:    17763,
+			},
+			want: "Microsoft Windows Server (OS Build 17763)",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := tt.r.String(); got != tt.want {
+				t.Errorf("windowsOSRelease.String() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}