mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
pkg/parsers: support Windows 11; drop ProductName
Microsoft has stopped updating the ProductName registry value in Windows 11; it reads as Windows 10. And Microsoft has made it very difficult to look up the real product name programmatically so that applications do not attempt to parse it. (Ever wonder why they skipped Windows 9?) The only documented and supported mechanisms require WMI or WinRT. The product name has no bearing on application compatibility so it is not worth doing any heroics to display the correct name. The build number and Update Build Revision is sufficient information to identify a specific build of Windows. Stop displaying the ProductName so as not to confuse users with incorrect information. Microsoft has frozen the ReleaseId registry value at 2009 when they switched to semi-annual releases and alpha-numeric versions. The release version as displayed by winver.exe and Settings -> System -> About on Windows 20H2 and newer can be found in the new DisplayVersion registry value. Replicate the way winver.exe displays the version by preferentially reporting the DisplayVersion if present and reporting if it is a Windows Server edition. Signed-off-by: Cory Snider <csnider@mirantis.com>
This commit is contained in:
parent
3e8bfcc9f2
commit
9aacaeb667
3 changed files with 159 additions and 35 deletions
|
@ -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)
|
||||
|
||||
buildNumber, _, err := key.GetStringValue("CurrentBuildNumber")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ubr, _, err := key.GetIntegerValue("UBR")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
os = fmt.Sprintf("%s (OS Build %s.%d)", os, buildNumber, ubr)
|
||||
|
||||
return
|
||||
})
|
||||
|
||||
if os == "" {
|
||||
// Default return value
|
||||
os = "Unknown Operating System"
|
||||
osversion := windows.RtlGetVersion() // Always succeeds.
|
||||
rel := windowsOSRelease{
|
||||
IsServer: osversion.ProductType != verNTWorkstation,
|
||||
Build: osversion.BuildNumber,
|
||||
}
|
||||
|
||||
return os, err
|
||||
// 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
|
||||
}
|
||||
if ubr, _, err := key.GetIntegerValue("UBR"); err == nil {
|
||||
rel.UBR = ubr
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
|
|
33
pkg/parsers/operatingsystem/windows_os_string.go
Normal file
33
pkg/parsers/operatingsystem/windows_os_string.go
Normal file
|
@ -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()
|
||||
}
|
89
pkg/parsers/operatingsystem/windows_os_string_test.go
Normal file
89
pkg/parsers/operatingsystem/windows_os_string_test.go
Normal file
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue