diff --git a/providers/os/detector/detector_win.go b/providers/os/detector/detector_win.go index 7deb4bd023..d184062910 100644 --- a/providers/os/detector/detector_win.go +++ b/providers/os/detector/detector_win.go @@ -13,7 +13,41 @@ import ( "go.mondoo.com/cnquery/v11/providers/os/registry" ) +// runtimeWindowsDetector uses powershell to gather information about the windows system func runtimeWindowsDetector(pf *inventory.Platform, conn shared.Connection) (bool, error) { + // most systems support wmi, but windows on arm does not ship with wmic, therefore we are trying to use windows + // builds from registry key first. If that fails, we try to use wmi + // see https://techcommunity.microsoft.com/t5/windows-it-pro-blog/wmi-command-line-wmic-utility-deprecation-next-steps/ba-p/4039242 + + if pf.Labels == nil { + pf.Labels = map[string]string{} + } + + // try to get build + ubr number (win 10+, 2019+) + current, err := win.GetWindowsOSBuild(conn) + if err == nil && current.UBR > 0 { + pf.Name = "windows" + pf.Title = current.ProductName + pf.Version = current.CurrentBuild + pf.Build = strconv.Itoa(current.UBR) + pf.Arch = current.Architecture + + var productType string + switch current.ProductType { + case "WinNT": + productType = "1" // Workstation + case "ServerNT": + productType = "3" // Server + case "LanmanNT": + productType = "2" // Domain Controller + } + + pf.Labels["windows.mondoo.com/product-type"] = productType + pf.Labels["windows.mondoo.com/display-version"] = current.DisplayVersion + return true, nil + } + + // fallback to wmi if the registry key is not available data, err := win.GetWmiInformation(conn) if err != nil { log.Debug().Err(err).Msg("could not gather wmi information") @@ -29,20 +63,8 @@ func runtimeWindowsDetector(pf *inventory.Platform, conn shared.Connection) (boo // FIXME: we need to ask wmic cpu get architecture pf.Arch = data.OSArchitecture - - if pf.Labels == nil { - pf.Labels = map[string]string{} - } pf.Labels["windows.mondoo.com/product-type"] = data.ProductType - // optional: try to get the ubr number (win 10 + 2019) - current, err := win.GetWindowsOSBuild(conn) - if err != nil { - log.Debug().Err(err).Msg("could not parse windows current version") - } else if current.UBR > 0 { - pf.Build = strconv.Itoa(current.UBR) - } - return true, nil } diff --git a/providers/os/detector/windows/build_version.go b/providers/os/detector/windows/build_version.go index 5f381de068..ccd453f079 100644 --- a/providers/os/detector/windows/build_version.go +++ b/providers/os/detector/windows/build_version.go @@ -58,11 +58,16 @@ func (b BuildVersion) OSBuild() string { } type WindowsCurrentVersion struct { - CurrentBuild string `json:"CurrentBuild"` - EditionID string `json:"EditionID"` - ReleaseId string `json:"ReleaseId"` + CurrentBuild string `json:"CurrentBuild"` + EditionID string `json:"EditionID"` + ReleaseId string `json:"ReleaseId"` + InstallationType string `json:"InstallationType"` + ProductName string `json:"ProductName"` + DisplayVersion string `json:"DisplayVersion"` // Update Build Revision - UBR int `json:"UBR"` + UBR int `json:"UBR"` + Architecture string `json:"Architecture"` + ProductType string `json:"ProductType"` } func ParseWinRegistryCurrentVersion(r io.Reader) (*WindowsCurrentVersion, error) { @@ -82,8 +87,15 @@ func ParseWinRegistryCurrentVersion(r io.Reader) (*WindowsCurrentVersion, error) // powershellGetWindowsOSBuild runs a powershell script to retrieve the current version from windows func powershellGetWindowsOSBuild(conn shared.Connection) (*WindowsCurrentVersion, error) { - pscommand := "Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion' -Name CurrentBuild, UBR, EditionID | ConvertTo-Json" - cmd, err := conn.RunCommand(powershell.Wrap(pscommand)) + pscommand := ` +$info = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\ProductOptions' -Name ProductType +$sysInfo = Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name CurrentBuild, UBR, InstallationType, EditionID, ProductName, DisplayVersion +$sysInfo | Add-Member -MemberType NoteProperty -Name Architecture -Value $env:PROCESSOR_ARCHITECTURE +$sysInfo | Add-Member -MemberType NoteProperty -Name ProductType -Value $info.ProductType +$sysInfo | Select-Object CurrentBuild, UBR, InstallationType, EditionID, ProductName, DisplayVersion, Architecture, ProductType | ConvertTo-Json +` + + cmd, err := conn.RunCommand(powershell.Encode(pscommand)) if err != nil { return nil, err } diff --git a/providers/os/detector/windows/build_version_test.go b/providers/os/detector/windows/build_version_test.go index 2c42cc9c6f..8b6ab25ef2 100644 --- a/providers/os/detector/windows/build_version_test.go +++ b/providers/os/detector/windows/build_version_test.go @@ -12,17 +12,37 @@ import ( // UBR - Update Build Revision func TestParseWinRegistryCurrentVersion(t *testing.T) { - data := `{ - "CurrentBuild": "17763", - "UBR": 720, - "EditionID": "ServerDatacenterEval", - "ReleaseId": "1809" - }` + t.Run("parse windows version", func(t *testing.T) { + data := `{ + "CurrentBuild": "17763", + "UBR": 720, + "EditionID": "ServerDatacenterEval", + "ReleaseId": "1809" + }` - m, err := ParseWinRegistryCurrentVersion(strings.NewReader(data)) - assert.Nil(t, err) + m, err := ParseWinRegistryCurrentVersion(strings.NewReader(data)) + assert.Nil(t, err) - assert.Equal(t, "17763", m.CurrentBuild, "buildnumber should be parsed properly") - assert.Equal(t, 720, m.UBR, "ubr should be parsed properly") + assert.Equal(t, "17763", m.CurrentBuild, "buildnumber should be parsed properly") + assert.Equal(t, 720, m.UBR, "ubr should be parsed properly") + }) + t.Run("parse windows version with architecture", func(t *testing.T) { + data := `{ + "CurrentBuild": "26100", + "UBR": 2033, + "InstallationType": "Client", + "EditionID": "Enterprise", + "ProductName": "Windows 10 Enterprise", + "DisplayVersion": "24H2", + "Architecture": "ARM64", + "ProductType": "WinNT" + }` + m, err := ParseWinRegistryCurrentVersion(strings.NewReader(data)) + assert.Nil(t, err) + + assert.Equal(t, "26100", m.CurrentBuild, "buildnumber should be parsed properly") + assert.Equal(t, 2033, m.UBR, "ubr should be parsed properly") + assert.Equal(t, "ARM64", m.Architecture, "architecture should be parsed properly") + }) } diff --git a/providers/os/resources/powershell/encode.go b/providers/os/resources/powershell/encode.go index acd3ecd6e6..445f10d167 100644 --- a/providers/os/resources/powershell/encode.go +++ b/providers/os/resources/powershell/encode.go @@ -31,7 +31,7 @@ func Encode(cmd string) string { return fmt.Sprintf("powershell.exe -NoProfile -EncodedCommand %s", encodedScript) } -// The Encode equivalent for running powershell script in unix systems +// EncodeUnix is equivalent to Encode for running powershell script on unix systems func EncodeUnix(cmd string) string { // avoid messages to stderr that are not required in our execution script := "$ProgressPreference='SilentlyContinue';" + cmd @@ -60,6 +60,9 @@ func ToBase64String(script string) (string, error) { return base64.StdEncoding.EncodeToString([]byte(encoded)), nil } +// Wrap runs a powershell script by calling powershell. Note that this is not encoded and therefore does not support +// multiline scripts or special characters. You should use Encode for that or ensure the script is a single line and +// does use semicolons to separate commands. func Wrap(cmd string) string { return fmt.Sprintf("powershell -c \"%s\"", cmd) }