osversion.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. // Copyright (c) 2022 Tailscale Inc & AUTHORS. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. //go:build windows
  5. package wingoes
  6. import (
  7. "fmt"
  8. "sync"
  9. "golang.org/x/sys/windows"
  10. "golang.org/x/sys/windows/registry"
  11. )
  12. var (
  13. verOnce sync.Once
  14. verInfo osVersionInfo // must access via getVersionInfo()
  15. )
  16. // osVersionInfo is more compact than windows.OsVersionInfoEx, which contains
  17. // extraneous information.
  18. type osVersionInfo struct {
  19. major uint32
  20. minor uint32
  21. build uint32
  22. servicePack uint16
  23. str string
  24. isDC bool
  25. isServer bool
  26. }
  27. const (
  28. _VER_NT_WORKSTATION = 1
  29. _VER_NT_DOMAIN_CONTROLLER = 2
  30. _VER_NT_SERVER = 3
  31. )
  32. func getVersionInfo() *osVersionInfo {
  33. verOnce.Do(func() {
  34. osv := windows.RtlGetVersion()
  35. verInfo = osVersionInfo{
  36. major: osv.MajorVersion,
  37. minor: osv.MinorVersion,
  38. build: osv.BuildNumber,
  39. servicePack: osv.ServicePackMajor,
  40. str: fmt.Sprintf("%d.%d.%d", osv.MajorVersion, osv.MinorVersion, osv.BuildNumber),
  41. isDC: osv.ProductType == _VER_NT_DOMAIN_CONTROLLER,
  42. // Domain Controllers are also implicitly servers.
  43. isServer: osv.ProductType == _VER_NT_DOMAIN_CONTROLLER || osv.ProductType == _VER_NT_SERVER,
  44. }
  45. // UBR is only available on Windows 10 and 11 (MajorVersion == 10).
  46. if osv.MajorVersion == 10 {
  47. if ubr, err := getUBR(); err == nil {
  48. verInfo.str = fmt.Sprintf("%s.%d", verInfo.str, ubr)
  49. }
  50. }
  51. })
  52. return &verInfo
  53. }
  54. // getUBR returns the "update build revision," ie. the fourth component of the
  55. // version string found on Windows 10 and Windows 11 systems.
  56. func getUBR() (uint32, error) {
  57. key, err := registry.OpenKey(registry.LOCAL_MACHINE,
  58. `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE|registry.WOW64_64KEY)
  59. if err != nil {
  60. return 0, err
  61. }
  62. defer key.Close()
  63. val, valType, err := key.GetIntegerValue("UBR")
  64. if err != nil {
  65. return 0, err
  66. }
  67. if valType != registry.DWORD {
  68. return 0, registry.ErrUnexpectedType
  69. }
  70. return uint32(val), nil
  71. }
  72. // GetOSVersionString returns the Windows version of the current machine in
  73. // dotted-decimal form. The version string contains 3 components on Windows 7
  74. // and 8.x, and 4 components on Windows 10 and 11.
  75. func GetOSVersionString() string {
  76. return getVersionInfo().String()
  77. }
  78. // IsWinServer returns true if and only if this computer's version of Windows is
  79. // a server edition.
  80. func IsWinServer() bool {
  81. return getVersionInfo().isServer
  82. }
  83. // IsWinDomainController returs true if this computer's version of Windows is
  84. // configured to act as a domain controller.
  85. func IsWinDomainController() bool {
  86. return getVersionInfo().isDC
  87. }
  88. // IsWin7SP1OrGreater returns true when running on Windows 7 SP1 or newer.
  89. func IsWin7SP1OrGreater() bool {
  90. if IsWin8OrGreater() {
  91. return true
  92. }
  93. vi := getVersionInfo()
  94. return vi.major == 6 && vi.minor == 1 && vi.servicePack > 0
  95. }
  96. // IsWin8OrGreater returns true when running on Windows 8.0 or newer.
  97. func IsWin8OrGreater() bool {
  98. return getVersionInfo().isVersionOrGreater(6, 2, 0)
  99. }
  100. // IsWin8Point1OrGreater returns true when running on Windows 8.1 or newer.
  101. func IsWin8Point1OrGreater() bool {
  102. return getVersionInfo().isVersionOrGreater(6, 3, 0)
  103. }
  104. // IsWin10OrGreater returns true when running on any build of Windows 10 or newer.
  105. func IsWin10OrGreater() bool {
  106. return getVersionInfo().major >= 10
  107. }
  108. // Win10BuildConstant encodes build numbers for the various editions of Windows 10,
  109. // for use with IsWin10BuildOrGreater.
  110. type Win10BuildConstant uint32
  111. const (
  112. Win10BuildNov2015 = Win10BuildConstant(10586)
  113. Win10BuildAnniversary = Win10BuildConstant(14393)
  114. Win10BuildCreators = Win10BuildConstant(15063)
  115. Win10BuildFallCreators = Win10BuildConstant(16299)
  116. Win10BuildApr2018 = Win10BuildConstant(17134)
  117. Win10BuildSep2018 = Win10BuildConstant(17763)
  118. Win10BuildMay2019 = Win10BuildConstant(18362)
  119. Win10BuildSep2019 = Win10BuildConstant(18363)
  120. Win10BuildApr2020 = Win10BuildConstant(19041)
  121. Win10Build20H2 = Win10BuildConstant(19042)
  122. Win10Build21H1 = Win10BuildConstant(19043)
  123. Win10Build21H2 = Win10BuildConstant(19044)
  124. )
  125. // IsWin10BuildOrGreater returns true when running on the specified Windows 10
  126. // build, or newer.
  127. func IsWin10BuildOrGreater(build Win10BuildConstant) bool {
  128. return getVersionInfo().isWin10BuildOrGreater(uint32(build))
  129. }
  130. // Win11BuildConstant encodes build numbers for the various editions of Windows 11,
  131. // for use with IsWin11BuildOrGreater.
  132. type Win11BuildConstant uint32
  133. const (
  134. Win11BuildRTM = Win11BuildConstant(22000)
  135. Win11Build22H2 = Win11BuildConstant(22621)
  136. )
  137. // IsWin11OrGreater returns true when running on any release of Windows 11,
  138. // or newer.
  139. func IsWin11OrGreater() bool {
  140. return IsWin11BuildOrGreater(Win11BuildRTM)
  141. }
  142. // IsWin11BuildOrGreater returns true when running on the specified Windows 11
  143. // build, or newer.
  144. func IsWin11BuildOrGreater(build Win11BuildConstant) bool {
  145. // Under the hood, Windows 11 is just Windows 10 with a sufficiently advanced
  146. // build number.
  147. return getVersionInfo().isWin10BuildOrGreater(uint32(build))
  148. }
  149. func (osv *osVersionInfo) String() string {
  150. return osv.str
  151. }
  152. func (osv *osVersionInfo) isWin10BuildOrGreater(build uint32) bool {
  153. return osv.isVersionOrGreater(10, 0, build)
  154. }
  155. func (osv *osVersionInfo) isVersionOrGreater(major, minor, build uint32) bool {
  156. return isVerGE(osv.major, major, osv.minor, minor, osv.build, build)
  157. }
  158. func isVerGE(lmajor, rmajor, lminor, rminor, lbuild, rbuild uint32) bool {
  159. return lmajor > rmajor ||
  160. lmajor == rmajor &&
  161. (lminor > rminor ||
  162. lminor == rminor && lbuild >= rbuild)
  163. }