rclone/lib/buildinfo/osversion_windows.go

139 lines
3.5 KiB
Go
Raw Normal View History

package buildinfo
import (
"fmt"
"regexp"
"strings"
"unsafe"
"github.com/shirou/gopsutil/v3/host"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
)
// GetOSVersion returns OS version, kernel and bitness
// On Windows it performs additional output enhancements.
func GetOSVersion() (osVersion, osKernel string) {
if platform, _, version, err := host.PlatformInformation(); err == nil && platform != "" {
osVersion = platform
if version != "" {
osVersion += " " + version
}
}
if version, err := host.KernelVersion(); err == nil && version != "" {
osKernel = version
// Prevent duplication of output on Windows
if strings.Contains(osVersion, osKernel) {
deduped := strings.TrimSpace(strings.Replace(osVersion, osKernel, "", 1))
if deduped != "" {
osVersion = deduped
}
}
// Simplify kernel output: `RELEASE.BUILD Build BUILD` -> `RELEASE.BUILD`
match := regexp.MustCompile(`^([\d\.]+?\.)(\d+) Build (\d+)$`).FindStringSubmatch(osKernel)
if len(match) == 4 && match[2] == match[3] {
osKernel = match[1] + match[2]
}
}
if osVersion != "" {
// Include the friendly-name of the version, which is typically what is referred to.
// Until Windows 10 version 2004 (May 2020) this can be found from registry entry
// ReleaseID, after that we must use entry DisplayVersion (ReleaseId is stuck at 2009).
// Source: https://ss64.com/nt/ver.html
friendlyName := getRegistryVersionString("DisplayVersion")
if friendlyName == "" {
friendlyName = getRegistryVersionString("ReleaseId")
}
if friendlyName != "" {
osVersion += " " + friendlyName
}
}
updateRevision := getRegistryVersionInt("UBR")
if osKernel != "" && updateRevision != 0 {
osKernel += fmt.Sprintf(".%d", updateRevision)
}
if arch, err := host.KernelArch(); err == nil && arch != "" {
if strings.HasSuffix(arch, "64") && osVersion != "" {
osVersion += " (64 bit)"
}
if osKernel != "" {
osKernel += " (" + arch + ")"
}
}
return
}
var regVersionKeyUTF16 = windows.StringToUTF16Ptr(`SOFTWARE\Microsoft\Windows NT\CurrentVersion`)
func getRegistryVersionString(name string) string {
var (
err error
handle windows.Handle
bufLen uint32
valType uint32
)
err = windows.RegOpenKeyEx(windows.HKEY_LOCAL_MACHINE, regVersionKeyUTF16, 0, windows.KEY_READ|windows.KEY_WOW64_64KEY, &handle)
if err != nil {
return ""
}
defer func() {
_ = windows.RegCloseKey(handle)
}()
nameUTF16 := windows.StringToUTF16Ptr(name)
err = windows.RegQueryValueEx(handle, nameUTF16, nil, &valType, nil, &bufLen)
if err != nil {
return ""
}
regBuf := make([]uint16, bufLen/2+1)
err = windows.RegQueryValueEx(handle, nameUTF16, nil, &valType, (*byte)(unsafe.Pointer(&regBuf[0])), &bufLen)
if err != nil {
return ""
}
return windows.UTF16ToString(regBuf[:])
}
func getRegistryVersionInt(name string) int {
var (
err error
handle windows.Handle
bufLen uint32
valType uint32
)
err = windows.RegOpenKeyEx(windows.HKEY_LOCAL_MACHINE, regVersionKeyUTF16, 0, windows.KEY_READ|windows.KEY_WOW64_64KEY, &handle)
if err != nil {
return 0
}
defer func() {
_ = windows.RegCloseKey(handle)
}()
nameUTF16 := windows.StringToUTF16Ptr(name)
err = windows.RegQueryValueEx(handle, nameUTF16, nil, &valType, nil, &bufLen)
if err != nil {
return 0
}
if valType != registry.DWORD || bufLen != 4 {
return 0
}
var val32 uint32
err = windows.RegQueryValueEx(handle, nameUTF16, nil, &valType, (*byte)(unsafe.Pointer(&val32)), &bufLen)
if err != nil {
return 0
}
return int(val32)
}