diff --git a/dsc/examples/wmi.dsc.yaml b/dsc/examples/wmi.dsc.yaml deleted file mode 100644 index c4907988..00000000 --- a/dsc/examples/wmi.dsc.yaml +++ /dev/null @@ -1,10 +0,0 @@ -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json -resources: -- name: WMI - type: Microsoft.Windows/WMI - properties: - resources: - - name: computer system - type: root.cimv2/Win32_ComputerSystem - - name: network adapter - type: root.cimv2/Win32_NetworkAdapter diff --git a/dsc/examples/wmi_inventory.dsc.yaml b/dsc/examples/wmi_inventory.dsc.yaml new file mode 100644 index 00000000..9f004d1c --- /dev/null +++ b/dsc/examples/wmi_inventory.dsc.yaml @@ -0,0 +1,42 @@ +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/config/document.json +resources: +- name: WMI + type: Microsoft.Windows/WMI + properties: + resources: + - name: computer system + type: root.cimv2/Win32_ComputerSystem + properties: + name: + domain: + totalphysicalmemory: + model: + manufacturer: + - name: operating system + type: root.cimv2/Win32_OperatingSystem + properties: + caption: + version: + osarchitecture: + oslanguage: + - name: system enclosure + type: root.cimv2/Win32_SystemEnclosure + properties: + manufacturer: + model: + serialnumber: + - name: bios + type: root.cimv2/Win32_BIOS + properties: + manufacturer: + version: + serialnumber: + - name: network adapter + type: root.cimv2/Win32_NetworkAdapter + properties: + name: + macaddress: + adaptertype: + netconnectionid: + serviceName: + netconnectionstatus: 2 diff --git a/wmi-adapter/Tests/test_wmi_config.dsc.yaml b/wmi-adapter/Tests/test_wmi_config.dsc.yaml index 225c87f6..7a18deae 100644 --- a/wmi-adapter/Tests/test_wmi_config.dsc.yaml +++ b/wmi-adapter/Tests/test_wmi_config.dsc.yaml @@ -7,6 +7,10 @@ resources: resources: - name: Get OS Info type: root.cimv2/Win32_OperatingSystem + properties: + caption: + version: + osarchitecture: - name: Get BIOS Info type: root.cimv2/Win32_BIOS - name: Get Processor Info diff --git a/wmi-adapter/Tests/wmi.tests.ps1 b/wmi-adapter/Tests/wmi.tests.ps1 index e5d2115c..2d2dd613 100644 --- a/wmi-adapter/Tests/wmi.tests.ps1 +++ b/wmi-adapter/Tests/wmi.tests.ps1 @@ -40,18 +40,22 @@ Describe 'WMI adapter resource tests' { $r = Get-Content -Raw $configPath | dsc config get $LASTEXITCODE | Should -Be 0 $res = $r | ConvertFrom-Json - $res.results[0].result.actualState[0].LastBootUpTime | Should -Not -BeNull - $res.results[0].result.actualState[1].BiosCharacteristics | Should -Not -BeNull - $res.results[0].result.actualState[2].NumberOfLogicalProcessors | Should -Not -BeNull + $res.results[0].result.actualState[0].LastBootUpTime | Should -BeNullOrEmpty + $res.results[0].result.actualState[0].Caption | Should -Not -BeNullOrEmpty + $res.results[0].result.actualState[0].Version | Should -Not -BeNullOrEmpty + $res.results[0].result.actualState[0].OSArchitecture | Should -Not -BeNullOrEmpty } It 'Example config works' -Skip:(!$IsWindows) { - $configPath = Join-Path $PSScriptRoot '..\..\dsc\examples\wmi.dsc.yaml' + $configPath = Join-Path $PSScriptRoot '..\..\dsc\examples\wmi_inventory.dsc.yaml' $r = dsc config get -p $configPath $LASTEXITCODE | Should -Be 0 $r | Should -Not -BeNullOrEmpty $res = $r | ConvertFrom-Json - $res.results[0].result.actualState[0].Model | Should -Not -BeNullOrEmpty - $res.results[0].result.actualState[1].Description | Should -Not -BeNullOrEmpty + $res.results[0].result.actualState[0].Name | Should -Not -BeNullOrEmpty + $res.results[0].result.actualState[0].BootupState | Should -BeNullOrEmpty + $res.results[0].result.actualState[1].Caption | Should -Not -BeNullOrEmpty + $res.results[0].result.actualState[1].BuildNumber | Should -BeNullOrEmpty + $res.results[0].result.actualState[4].AdapterType | Should -BeLike "Ethernet*" } } diff --git a/wmi-adapter/wmi.resource.ps1 b/wmi-adapter/wmi.resource.ps1 index 6eb2f88d..908e987c 100644 --- a/wmi-adapter/wmi.resource.ps1 +++ b/wmi-adapter/wmi.resource.ps1 @@ -9,10 +9,29 @@ param( $stdinput ) +# catch any un-caught exception and write it to the error stream +trap { + Write-Trace -Level Error -message $_.Exception.Message + exit 1 +} + $ProgressPreference = 'Ignore' $WarningPreference = 'Ignore' $VerbosePreference = 'Ignore' +function Write-Trace { + param( + [string]$message, + [string]$level = 'Error' + ) + + $trace = [pscustomobject]@{ + $level = $message + } | ConvertTo-Json -Compress + + $host.ui.WriteErrorLine($trace) +} + function IsConfiguration($obj) { if ($null -ne $obj.metadata -and $null -ne $obj.metadata.'Microsoft.DSC' -and $obj.metadata.'Microsoft.DSC'.context -eq 'Configuration') { return $true @@ -29,7 +48,6 @@ if ($Operation -eq 'List') { $version_string = ""; $author_string = ""; - $moduleName = ""; $propertyList = @() foreach ($p in $r.CimClassProperties) @@ -79,17 +97,69 @@ elseif ($Operation -eq 'Get') $wmi_namespace = $type_fields[0].Replace('.','\') $wmi_classname = $type_fields[1] - #TODO: add filtering based on supplied properties of $r - $wmi_instances = Get-CimInstance -Namespace $wmi_namespace -ClassName $wmi_classname + # TODO: identify key properties and add WHERE clause to the query + if ($r.properties) + { + $query = "SELECT $($r.properties.psobject.properties.name -join ',') FROM $wmi_classname" + $where = " WHERE " + $useWhere = $false + $first = $true + foreach ($property in $r.properties.psobject.properties) + { + # TODO: validate property against the CIM class to give better error message + if ($null -ne $property.value) + { + $useWhere = $true + if ($first) + { + $first = $false + } + else + { + $where += " AND " + } + + if ($property.TypeNameOfValue -eq "System.String") + { + $where += "$($property.Name) = '$($property.Value)'" + } + else + { + $where += "$($property.Name) = $($property.Value)" + } + } + } + if ($useWhere) + { + $query += $where + } + Write-Trace -Level Trace -message "Query: $query" + $wmi_instances = Get-CimInstance -Namespace $wmi_namespace -Query $query -ErrorAction Stop + } + else + { + $wmi_instances = Get-CimInstance -Namespace $wmi_namespace -ClassName $wmi_classname -ErrorAction Stop + } if ($wmi_instances) { $instance_result = @{} + # TODO: for a `Get`, they key property must be provided so a specific instance is returned rather than just the first $wmi_instance = $wmi_instances[0] # for 'Get' we return just first matching instance; for 'export' we return all instances $wmi_instance.psobject.properties | %{ if (($_.Name -ne "type") -and (-not $_.Name.StartsWith("Cim"))) { - $instance_result[$_.Name] = $_.Value + if ($r.properties) + { + if ($r.properties.psobject.properties.name -contains $_.Name) + { + $instance_result[$_.Name] = $_.Value + } + } + else + { + $instance_result[$_.Name] = $_.Value + } } } @@ -98,7 +168,7 @@ elseif ($Operation -eq 'Get') else { $errmsg = "Can not find type " + $r.type + "; please ensure that Get-CimInstance returns this resource type" - Write-Error $errmsg + Write-Trace $errmsg exit 1 } } @@ -110,11 +180,12 @@ elseif ($Operation -eq 'Get') $wmi_classname = $type_fields[1] #TODO: add filtering based on supplied properties of $inputobj_pscustomobj - $wmi_instances = Get-CimInstance -Namespace $wmi_namespace -ClassName $wmi_classname + $wmi_instances = Get-CimInstance -Namespace $wmi_namespace -ClassName $wmi_classname -ErrorAction Stop if ($wmi_instances) { - $wmi_instance = $wmi_instances[0] # for 'Get' we return just first matching instance; for 'export' we return all instances + # TODO: there's duplicate code here between configuration and non-configuration execution and should be refactored into a helper + $wmi_instance = $wmi_instances[0] $result = @{} $wmi_instance.psobject.properties | %{ if (($_.Name -ne "type") -and (-not $_.Name.StartsWith("Cim"))) @@ -126,7 +197,7 @@ elseif ($Operation -eq 'Get') else { $errmsg = "Can not find type " + $inputobj_pscustomobj.type + "; please ensure that Get-CimInstance returns this resource type" - Write-Error $errmsg + Write-Trace $errmsg exit 1 } } @@ -140,5 +211,5 @@ elseif ($Operation -eq 'Validate') } else { - Write-Error "ERROR: Unsupported operation requested from wmigroup.resource.ps1" -} \ No newline at end of file + Write-Trace "ERROR: Unsupported operation requested from wmigroup.resource.ps1" +}