Skip to content

Commit

Permalink
⭐️ support Windows 2025 on arm via SSH
Browse files Browse the repository at this point in the history
  • Loading branch information
chris-rock committed Nov 2, 2024
1 parent 0e24f42 commit 8d7b70c
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 37 deletions.
33 changes: 28 additions & 5 deletions providers/os/detector/detector_platform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ import (
"errors"
"testing"

"go.mondoo.com/cnquery/v11/providers-sdk/v1/inventory"

"github.com/stretchr/testify/assert"
"go.mondoo.com/cnquery/v11/providers-sdk/v1/inventory"
"go.mondoo.com/cnquery/v11/providers/os/connection/mock"
)

Expand Down Expand Up @@ -690,10 +689,34 @@ func TestWindows2019Detector(t *testing.T) {
assert.Nil(t, err, "was able to create the provider")

assert.Equal(t, "windows", di.Name, "os name should be identified")
assert.Equal(t, "Microsoft Windows Server 2019 Datacenter Evaluation", di.Title, "os title should be identified")
assert.Equal(t, "Windows Server 2019 Datacenter", di.Title, "os title should be identified")
assert.Equal(t, "17763", di.Version, "os version should be identified")
assert.Equal(t, "720", di.Build, "os build version should be identified")
assert.Equal(t, "64-bit", di.Arch, "os arch should be identified")
assert.Equal(t, "6414", di.Build, "os build version should be identified")
assert.Equal(t, "AMD64", di.Arch, "os arch should be identified")
assert.Equal(t, []string{"windows", "os"}, di.Family)
}

func TestWindows2022Detector(t *testing.T) {
di, err := detectPlatformFromMock("./testdata/detect-windows2022.toml")
assert.Nil(t, err, "was able to create the provider")

assert.Equal(t, "windows", di.Name, "os name should be identified")
assert.Equal(t, "Windows Server 2022 Datacenter", di.Title, "os title should be identified")
assert.Equal(t, "20348", di.Version, "os version should be identified")
assert.Equal(t, "2762", di.Build, "os build version should be identified")
assert.Equal(t, "AMD64", di.Arch, "os arch should be identified")
assert.Equal(t, []string{"windows", "os"}, di.Family)
}

func TestWindows2025Detector(t *testing.T) {
di, err := detectPlatformFromMock("./testdata/detect-windows2025.toml")
assert.Nil(t, err, "was able to create the provider")

assert.Equal(t, "windows", di.Name, "os name should be identified")
assert.Equal(t, "Windows Server 2025 Datacenter", di.Title, "os title should be identified")
assert.Equal(t, "26311", di.Version, "os version should be identified")
assert.Equal(t, "5000", di.Build, "os build version should be identified")
assert.Equal(t, "ARM64", di.Arch, "os arch should be identified")
assert.Equal(t, []string{"windows", "os"}, di.Family)
}

Expand Down
46 changes: 34 additions & 12 deletions providers/os/detector/detector_win.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
}

Expand Down
11 changes: 8 additions & 3 deletions providers/os/detector/testdata/detect-windows2019.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@ stdout = """Node,BootDevice,BuildNumber,BuildType,Caption,CodeSet,CountryCode,Cr
VAGRANT,\\Device\\HarddiskVolume1,17763,Multiprocessor Free,Microsoft Windows Server 2019 Datacenter Evaluation,1252,1,Win32_OperatingSystem,Win32_ComputerSystem,,VAGRANT,-420,TRUE,TRUE,TRUE,3,FALSE,,FALSE,256,2,721716,979372,1922780,20190906065515.000000-420,,20190908011749.580533-420,20190908042731.608000-420,0409,Microsoft Corporation,4294967295,137438953344,{en-US},Microsoft Windows Server 2019 Datacenter Evaluation|C:\\Windows|\\Device\\Harddisk0\\Partition2,0,69,1,80,Vagrant,64-bit,1033,400,18,,,,,FALSE,TRUE,3,,00431-20000-00000-AA838,0,0,1179648,OK,400,\\Device\\HarddiskVolume2,C:\\Windows\\system32,C:,,3276340,2096692,10.0.17763,C:\\Windows
"""

[commands."powershell -c \"Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion' -Name CurrentBuild, UBR, EditionID | ConvertTo-Json\""]
[commands."55dbc0e9b838caa11145eed07f6e73644bda27bf65a0c58a52291f9a18384481"]
stdout="""
{
"CurrentBuild": "17763",
"EditionID": "ServerDatacenterEval",
"UBR": 720
"UBR": 6414,
"InstallationType": "Server",
"EditionID": "ServerDatacenter",
"ProductName": "Windows Server 2019 Datacenter",
"DisplayVersion": null,
"Architecture": "AMD64",
"ProductType": "ServerNT"
}
"""
18 changes: 18 additions & 0 deletions providers/os/detector/testdata/detect-windows2022.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[commands."wmic os get * /format:csv"]
stdout = """NNode,BootDevice,BuildNumber,BuildType,Caption,CodeSet,CountryCode,CreationClassName,CSCreationClassName,CSDVersion,CSName,CurrentTimeZone,DataExecutionPrevention_32BitApplications,DataExecutionPrevention_Available,DataExecutionPrevention_Drivers,DataExecutionPrevention_SupportPolicy,Debug,Description,Distributed,EncryptionLevel,ForegroundApplicationBoost,FreePhysicalMemory,FreeSpaceInPagingFiles,FreeVirtualMemory,InstallDate,LargeSystemCache,LastBootUpTime,LocalDateTime,Locale,Manufacturer,MaxNumberOfProcesses,MaxProcessMemorySize,MUILanguages,Name,NumberOfLicensedUsers,NumberOfProcesses,NumberOfUsers,OperatingSystemSKU,Organization,OSArchitecture,OSLanguage,OSProductSuite,OSType,OtherTypeDescription,PAEEnabled,PlusProductID,PlusVersionNumber,PortableOperatingSystem,Primary,ProductType,RegisteredUser,SerialNumber,ServicePackMajorVersion,ServicePackMinorVersion,SizeStoredInPagingFiles,Status,SuiteMask,SystemDevice,SystemDirectory,SystemDrive,TotalSwapSpaceSize,TotalVirtualMemorySize,TotalVisibleMemorySize,Version,WindowsDirectory
EC2AMAZ-EBJBV88,\\Device\\HarddiskVolume1,20348,Multiprocessor Free,Microsoft Windows Server 2022 Datacenter,1252,1,Win32_OperatingSystem,Win32_ComputerSystem,,EC2AMAZ-EBJBV88,0,TRUE,TRUE,TRUE,3,FALSE,,FALSE,256,2,2675412,1249808,4264104,20241030064719.000000+000,,20241030111529.500000+000,20241102105726.901000+000,0409,Microsoft Corporation,4294967295,137438953344,{en-US},Microsoft Windows Server 2022 Datacenter|C:\\Windows|\\Device\\Harddisk0\\Partition1,0,79,2,8,Amazon.com,64-bit,1033,400,18,,,,,FALSE,TRUE,3,EC2,00454-60000-00001-AA631,0,0,1441792,OK,400,\\Device\\HarddiskVolume1,C:\\Windows\\system32,C:,,5635696,4193904,10.0.20348,C:\\Windows
"""

[commands."55dbc0e9b838caa11145eed07f6e73644bda27bf65a0c58a52291f9a18384481"]
stdout="""
{
"CurrentBuild": "20348",
"UBR": 2762,
"InstallationType": "Server",
"EditionID": "ServerDatacenter",
"ProductName": "Windows Server 2022 Datacenter",
"DisplayVersion": "21H2",
"Architecture": "AMD64",
"ProductType": "ServerNT"
}
"""
13 changes: 13 additions & 0 deletions providers/os/detector/testdata/detect-windows2025.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[commands."55dbc0e9b838caa11145eed07f6e73644bda27bf65a0c58a52291f9a18384481"]
stdout="""
{
"CurrentBuild": "26311",
"UBR": 5000,
"InstallationType": "Server",
"EditionID": "ServerDatacenter",
"ProductName": "Windows Server 2025 Datacenter",
"DisplayVersion": "24H2",
"Architecture": "ARM64",
"ProductType": "ServerNT"
}
"""
24 changes: 18 additions & 6 deletions providers/os/detector/windows/build_version.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
}
Expand Down
40 changes: 30 additions & 10 deletions providers/os/detector/windows/build_version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
})
}
5 changes: 4 additions & 1 deletion providers/os/resources/powershell/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}

0 comments on commit 8d7b70c

Please sign in to comment.