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(®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) }