2021-04-06 02:53:09 +08:00
|
|
|
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]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-21 22:30:06 +08:00
|
|
|
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
|
|
|
|
}
|
2021-04-06 02:53:09 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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(®Buf[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)
|
|
|
|
}
|