google_guest_agent/osinfo/osinfo_windows.go (121 lines of code) (raw):

// Copyright 2023 Google LLC // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // https://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package osinfo import ( "errors" "fmt" "os" "path/filepath" "strings" "syscall" "unsafe" "github.com/GoogleCloudPlatform/guest-logging-go/logger" "golang.org/x/sys/windows" "golang.org/x/sys/windows/registry" ) var ( versionDLL = windows.NewLazySystemDLL("version.dll") // https://learn.microsoft.com/en-us/windows/win32/api/winver/nf-winver-getfileversioninfosizew procGetFileVersionInfoSizeW = versionDLL.NewProc("GetFileVersionInfoSizeW") // https://learn.microsoft.com/en-us/windows/win32/api/winver/nf-winver-getfileversioninfow procGetFileVersionInfoW = versionDLL.NewProc("GetFileVersionInfoW") // https://learn.microsoft.com/en-us/windows/win32/api/winver/nf-winver-verqueryvaluew procVerQueryValueW = versionDLL.NewProc("VerQueryValueW") ) // getTranslation returns the anguage and code page identifier from the provided // version-information block. func getTranslation(block []byte) (string, error) { var start uint var length uint blockStart := uintptr(unsafe.Pointer(&block[0])) if ret, _, _ := procVerQueryValueW.Call( blockStart, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(`\VarFileInfo\Translation`))), uintptr(unsafe.Pointer(&start)), uintptr(unsafe.Pointer(&length))); ret == 0 { return "", errors.New("zero return code from VerQueryValueW indicates failure") } begin := int(start) - int(blockStart) // For translation data length is bytes. trans := block[begin : begin+int(length)] // Each 'translation' is 4 bytes long (2 16-bit sections), we just want the // first one for simplicity. t := make([]byte, 4) // 16-bit language ID little endian // https://msdn.microsoft.com/en-us/library/windows/desktop/dd318693(v=vs.85).aspx t[0], t[1] = trans[1], trans[0] // 16-bit code page ID little endian // https://msdn.microsoft.com/en-us/library/windows/desktop/dd317756(v=vs.85).aspx t[2], t[3] = trans[3], trans[2] return fmt.Sprintf("%x", t), nil } // getStringFileInfo returns the string value file info name specific to the language and code page indicated. func getStringFileInfo(block []byte, langCodePage, name string) (string, error) { var start uint var length uint blockStart := uintptr(unsafe.Pointer(&block[0])) if ret, _, _ := procVerQueryValueW.Call( blockStart, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(fmt.Sprintf(`\StringFileInfo\%s\%s`, langCodePage, name)))), uintptr(unsafe.Pointer(&start)), uintptr(unsafe.Pointer(&length))); ret == 0 { return "", errors.New("zero return code from VerQueryValueW indicates failure") } begin := int(start) - int(blockStart) // For version information length is characters (UTF16). result := block[begin : begin+int(2*length)] // Result is UTF16LE. u16s := make([]uint16, length) for i := range u16s { u16s[i] = uint16(result[i*2+1])<<8 | uint16(result[i*2]) } return syscall.UTF16ToString(u16s), nil } func getVersion(block []byte, langCodePage string) (string, string, error) { ver, err := getStringFileInfo(block, langCodePage, "FileVersion") if err != nil { return "", "", err } rel, err := getStringFileInfo(block, langCodePage, "ProductVersion") return ver, rel, err } func getKernelInfo() (string, string, error) { root := os.Getenv("SystemRoot") if root == "" { root = `C:\Windows` } path := filepath.Join(root, "System32", "ntoskrnl.exe") if _, err := os.Stat(path); err != nil { return "", "", err } pPtr := unsafe.Pointer(syscall.StringToUTF16Ptr(path)) size, _, _ := procGetFileVersionInfoSizeW.Call( uintptr(pPtr)) if size <= 0 { return "", "", errors.New("GetFileVersionInfoSize call failed, data size can not be 0") } info := make([]byte, size) if ret, _, _ := procGetFileVersionInfoW.Call( uintptr(pPtr), 0, uintptr(len(info)), uintptr(unsafe.Pointer(&info[0]))); ret == 0 { return "", "", errors.New("zero return code from GetFileVersionInfoW indicates failure") } // This should be something like 040904b0 for US English UTF16LE. langCodePage, err := getTranslation(info) if err != nil { return "", "", fmt.Errorf("getTranslation() error: %v", err) } return getVersion(info, langCodePage) } // Get returns OSInfo on the running system. func Get() OSInfo { var osInfo OSInfo osInfo.OS = "windows" kVersion, kRelease, err := getKernelInfo() if err != nil { logger.Warningf("getKernelInfo() error: %v", err) return osInfo } osInfo.KernelVersion = kVersion osInfo.KernelRelease = kRelease // SOFTWARE\Microsoft\Windows NT\CurrentVersion\CurrentVersion is always 6.3 now, so we get os version from kernel release vs := strings.Split(kRelease, ".") if len(vs) == 4 { osInfo.VersionID = strings.Join(vs[:3], ".") } k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) if err != nil { logger.Warningf("registry.OpenKey error: %v", err) return osInfo } defer k.Close() productName, _, err := k.GetStringValue("ProductName") if err != nil { logger.Warningf("GetStringValue('ProductName') error: %v", err) return osInfo } osInfo.PrettyName = productName return osInfo }