2021-08-23 04:12:27 +00:00
|
|
|
// Copyright (c) 2020 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.
|
|
|
|
|
|
|
|
package hostinfo
|
|
|
|
|
|
|
|
import (
|
2021-11-22 18:45:19 +00:00
|
|
|
"fmt"
|
2022-02-22 18:01:36 +00:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
2021-08-23 04:12:27 +00:00
|
|
|
"sync/atomic"
|
2021-11-22 18:45:19 +00:00
|
|
|
|
|
|
|
"golang.org/x/sys/windows"
|
|
|
|
"golang.org/x/sys/windows/registry"
|
2021-08-23 04:12:27 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
osVersion = osVersionWindows
|
2022-02-22 18:01:36 +00:00
|
|
|
packageType = packageTypeWindows
|
2021-08-23 04:12:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var winVerCache atomic.Value // of string
|
|
|
|
|
|
|
|
func osVersionWindows() string {
|
|
|
|
if s, ok := winVerCache.Load().(string); ok {
|
|
|
|
return s
|
|
|
|
}
|
2021-11-22 18:45:19 +00:00
|
|
|
major, minor, build := windows.RtlGetNtVersionNumbers()
|
|
|
|
s := fmt.Sprintf("%d.%d.%d", major, minor, build)
|
|
|
|
// Windows 11 still uses 10 as its major number internally
|
|
|
|
if major == 10 {
|
|
|
|
if ubr, err := getUBR(); err == nil {
|
|
|
|
s += fmt.Sprintf(".%d", ubr)
|
|
|
|
}
|
2021-08-23 04:12:27 +00:00
|
|
|
}
|
|
|
|
if s != "" {
|
|
|
|
winVerCache.Store(s)
|
|
|
|
}
|
|
|
|
return s // "10.0.19041.388", ideally
|
|
|
|
}
|
2021-11-22 18:45:19 +00:00
|
|
|
|
|
|
|
// getUBR obtains a fourth version field, the "Update Build Revision",
|
|
|
|
// from the registry. This field is only available beginning with Windows 10.
|
|
|
|
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
|
|
|
|
}
|
2022-02-22 18:01:36 +00:00
|
|
|
|
|
|
|
func packageTypeWindows() string {
|
|
|
|
if _, err := os.Stat(`C:\ProgramData\chocolatey\lib\tailscale`); err == nil {
|
|
|
|
return "choco"
|
|
|
|
}
|
|
|
|
exe, err := os.Executable()
|
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
dir := filepath.Dir(exe)
|
|
|
|
if !strings.Contains(dir, "Program Files") {
|
|
|
|
// Atypical. Not worth trying to detect. Likely open
|
|
|
|
// source tailscaled or a developer running by hand.
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
nsisUninstaller := filepath.Join(dir, "Uninstall-Tailscale.exe")
|
|
|
|
_, err = os.Stat(nsisUninstaller)
|
|
|
|
if err == nil {
|
|
|
|
return "nsis"
|
|
|
|
}
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
_, cliErr := os.Stat(filepath.Join(dir, "tailscale.exe"))
|
|
|
|
_, daemonErr := os.Stat(filepath.Join(dir, "tailscaled.exe"))
|
|
|
|
if cliErr == nil && daemonErr == nil {
|
|
|
|
// Almost certainly MSI.
|
|
|
|
// We have tailscaled.exe and tailscale.exe
|
|
|
|
// next to each other in Program Files, but no
|
|
|
|
// uninstaller.
|
|
|
|
// TODO(bradfitz,dblohm7): tighter heuristic?
|
|
|
|
return "msi"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|