// Copyright (c) 2022 Tailscale Inc & AUTHORS. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build windows package wingoes import ( "fmt" "sync" "golang.org/x/sys/windows" "golang.org/x/sys/windows/registry" ) var ( verOnce sync.Once verInfo osVersionInfo // must access via getVersionInfo() ) // osVersionInfo is more compact than windows.OsVersionInfoEx, which contains // extraneous information. type osVersionInfo struct { major uint32 minor uint32 build uint32 servicePack uint16 str string isDC bool isServer bool } const ( _VER_NT_WORKSTATION = 1 _VER_NT_DOMAIN_CONTROLLER = 2 _VER_NT_SERVER = 3 ) func getVersionInfo() *osVersionInfo { verOnce.Do(func() { osv := windows.RtlGetVersion() verInfo = osVersionInfo{ major: osv.MajorVersion, minor: osv.MinorVersion, build: osv.BuildNumber, servicePack: osv.ServicePackMajor, str: fmt.Sprintf("%d.%d.%d", osv.MajorVersion, osv.MinorVersion, osv.BuildNumber), isDC: osv.ProductType == _VER_NT_DOMAIN_CONTROLLER, // Domain Controllers are also implicitly servers. isServer: osv.ProductType == _VER_NT_DOMAIN_CONTROLLER || osv.ProductType == _VER_NT_SERVER, } // UBR is only available on Windows 10 and 11 (MajorVersion == 10). if osv.MajorVersion == 10 { if ubr, err := getUBR(); err == nil { verInfo.str = fmt.Sprintf("%s.%d", verInfo.str, ubr) } } }) return &verInfo } // getUBR returns the "update build revision," ie. the fourth component of the // version string found on Windows 10 and Windows 11 systems. func getUBR() (uint32, error) { key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE|registry.WOW64_64KEY) if err != nil { return 0, err } defer key.Close() val, valType, err := key.GetIntegerValue("UBR") if err != nil { return 0, err } if valType != registry.DWORD { return 0, registry.ErrUnexpectedType } return uint32(val), nil } // GetOSVersionString returns the Windows version of the current machine in // dotted-decimal form. The version string contains 3 components on Windows 7 // and 8.x, and 4 components on Windows 10 and 11. func GetOSVersionString() string { return getVersionInfo().String() } // IsWinServer returns true if and only if this computer's version of Windows is // a server edition. func IsWinServer() bool { return getVersionInfo().isServer } // IsWinDomainController returs true if this computer's version of Windows is // configured to act as a domain controller. func IsWinDomainController() bool { return getVersionInfo().isDC } // IsWin7SP1OrGreater returns true when running on Windows 7 SP1 or newer. func IsWin7SP1OrGreater() bool { if IsWin8OrGreater() { return true } vi := getVersionInfo() return vi.major == 6 && vi.minor == 1 && vi.servicePack > 0 } // IsWin8OrGreater returns true when running on Windows 8.0 or newer. func IsWin8OrGreater() bool { return getVersionInfo().isVersionOrGreater(6, 2, 0) } // IsWin8Point1OrGreater returns true when running on Windows 8.1 or newer. func IsWin8Point1OrGreater() bool { return getVersionInfo().isVersionOrGreater(6, 3, 0) } // IsWin10OrGreater returns true when running on any build of Windows 10 or newer. func IsWin10OrGreater() bool { return getVersionInfo().major >= 10 } // Win10BuildConstant encodes build numbers for the various editions of Windows 10, // for use with IsWin10BuildOrGreater. type Win10BuildConstant uint32 const ( Win10BuildRTM = Win10BuildConstant(10240) Win10Build1511 = Win10BuildConstant(10586) Win10Build1607 = Win10BuildConstant(14393) Win10BuildAnniversary = Win10Build1607 Win10Build1703 = Win10BuildConstant(15063) Win10BuildCreators = Win10Build1703 Win10Build1709 = Win10BuildConstant(16299) Win10BuildFallCreators = Win10Build1709 Win10Build1803 = Win10BuildConstant(17134) Win10Build1809 = Win10BuildConstant(17763) Win10Build1903 = Win10BuildConstant(18362) Win10Build1909 = Win10BuildConstant(18363) Win10Build2004 = Win10BuildConstant(19041) Win10Build20H2 = Win10BuildConstant(19042) Win10Build21H1 = Win10BuildConstant(19043) Win10Build21H2 = Win10BuildConstant(19044) Win10Build22H2 = Win10BuildConstant(19045) ) // IsWin10BuildOrGreater returns true when running on the specified Windows 10 // build, or newer. func IsWin10BuildOrGreater(build Win10BuildConstant) bool { return getVersionInfo().isWin10BuildOrGreater(uint32(build)) } // Win11BuildConstant encodes build numbers for the various editions of Windows 11, // for use with IsWin11BuildOrGreater. type Win11BuildConstant uint32 const ( Win11BuildRTM = Win11BuildConstant(22000) Win11Build22H2 = Win11BuildConstant(22621) Win11Build23H2 = Win11BuildConstant(22631) ) // IsWin11OrGreater returns true when running on any release of Windows 11, // or newer. func IsWin11OrGreater() bool { return IsWin11BuildOrGreater(Win11BuildRTM) } // IsWin11BuildOrGreater returns true when running on the specified Windows 11 // build, or newer. func IsWin11BuildOrGreater(build Win11BuildConstant) bool { // Under the hood, Windows 11 is just Windows 10 with a sufficiently advanced // build number. return getVersionInfo().isWin10BuildOrGreater(uint32(build)) } func (osv *osVersionInfo) String() string { return osv.str } func (osv *osVersionInfo) isWin10BuildOrGreater(build uint32) bool { return osv.isVersionOrGreater(10, 0, build) } func (osv *osVersionInfo) isVersionOrGreater(major, minor, build uint32) bool { return isVerGE(osv.major, major, osv.minor, minor, osv.build, build) } func isVerGE(lmajor, rmajor, lminor, rminor, lbuild, rbuild uint32) bool { return lmajor > rmajor || lmajor == rmajor && (lminor > rminor || lminor == rminor && lbuild >= rbuild) }