hostinfo_windows.go 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package hostinfo
  4. import (
  5. "fmt"
  6. "os"
  7. "path/filepath"
  8. "strings"
  9. "golang.org/x/sys/windows"
  10. "golang.org/x/sys/windows/registry"
  11. "tailscale.com/types/ptr"
  12. "tailscale.com/util/winutil"
  13. )
  14. func init() {
  15. osVersion = lazyOSVersion.Get
  16. packageType = lazyPackageType.Get
  17. }
  18. var (
  19. lazyOSVersion = &lazyAtomicValue[string]{f: ptr.To(osVersionWindows)}
  20. lazyPackageType = &lazyAtomicValue[string]{f: ptr.To(packageTypeWindows)}
  21. )
  22. func osVersionWindows() string {
  23. major, minor, build := windows.RtlGetNtVersionNumbers()
  24. s := fmt.Sprintf("%d.%d.%d", major, minor, build)
  25. // Windows 11 still uses 10 as its major number internally
  26. if major == 10 {
  27. if ubr, err := getUBR(); err == nil {
  28. s += fmt.Sprintf(".%d", ubr)
  29. }
  30. }
  31. return s // "10.0.19041.388", ideally
  32. }
  33. // getUBR obtains a fourth version field, the "Update Build Revision",
  34. // from the registry. This field is only available beginning with Windows 10.
  35. func getUBR() (uint32, error) {
  36. key, err := registry.OpenKey(registry.LOCAL_MACHINE,
  37. `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE|registry.WOW64_64KEY)
  38. if err != nil {
  39. return 0, err
  40. }
  41. defer key.Close()
  42. val, valType, err := key.GetIntegerValue("UBR")
  43. if err != nil {
  44. return 0, err
  45. }
  46. if valType != registry.DWORD {
  47. return 0, registry.ErrUnexpectedType
  48. }
  49. return uint32(val), nil
  50. }
  51. func packageTypeWindows() string {
  52. if _, err := os.Stat(`C:\ProgramData\chocolatey\lib\tailscale`); err == nil {
  53. return "choco"
  54. }
  55. msiSentinel, _ := winutil.GetRegInteger("MSI")
  56. if msiSentinel == 1 {
  57. return "msi"
  58. }
  59. exe, err := os.Executable()
  60. if err != nil {
  61. return ""
  62. }
  63. home, _ := os.UserHomeDir()
  64. if strings.HasPrefix(exe, filepath.Join(home, "scoop", "apps", "tailscale")) {
  65. return "scoop"
  66. }
  67. dir := filepath.Dir(exe)
  68. nsisUninstaller := filepath.Join(dir, "Uninstall-Tailscale.exe")
  69. _, err = os.Stat(nsisUninstaller)
  70. if err == nil {
  71. return "nsis"
  72. }
  73. // Atypical. Not worth trying to detect. Likely open
  74. // source tailscaled or a developer running by hand.
  75. return ""
  76. }