Skip to content

Commit c89b5de

Browse files
committed
Restructure set
1 parent a2968b2 commit c89b5de

File tree

2 files changed

+137
-105
lines changed

2 files changed

+137
-105
lines changed

wmi-adapter/Tests/wmi.tests.ps1

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -59,27 +59,38 @@ Describe 'WMI adapter resource tests' {
5959
$res.results[1].result.actualState.result[4].properties.AdapterType | Should -BeLike "Ethernet*"
6060
}
6161

62-
It 'Set does not work with missing key property' -Skip:(!$IsWindows) {
62+
It 'Set does not work without input for resource' -Skip:(!$IsWindows) {
6363
$s = dsc resource set --resource root.cimv2/Win32_Environment --input '{}' 2>&1
6464
$LASTEXITCODE | Should -Be 1
65-
$s | Should -BeLike "*Key property 'Name' is required*"
65+
$s | Should -BeLike "*No valid properties found in the CIM class 'Win32_Environment' for the provided properties.*"
6666
}
6767

68-
It 'Set works on a WMI resource' -Skip:(!$IsWindows) {
68+
It 'Set does not work without a key property' -Skip:(!$IsWindows) {
6969
$i = @{
70-
UserName = ("{0}\{1}" -f $env:USERDOMAIN, $env:USERNAME) # Read-only property required
71-
Name = "TestVariable"
7270
VariableValue = "TestValue"
71+
UserName = ("{0}\{1}" -f $env:USERDOMAIN, $env:USERNAME) # Read-only property is key, but we need a real one
7372
} | ConvertTo-Json
7473

75-
$r = dsc -l trace resource set -r root.cimv2/Win32_Environment -i $i
74+
$s = dsc resource set -r root.cimv2/Win32_Environment -i $i 2>&1
75+
$LASTEXITCODE | Should -Be 1
76+
$s | Should -BeLike "*All key properties in the CIM class 'Win32_Environment' are read-only, which is not supported.*"
77+
}
7678

77-
$s = dsc resource set --resource root.cimv2/Win32_Environment --input '{"Name":"TestVariable","Value":"TestValue"}'
78-
$LASTEXITCODE | Should -Be 0
79-
$s | Should -BeLike "*Set operation completed successfully*"
79+
# It 'Set works on a WMI resource' -Skip:(!$IsWindows) {
80+
# $i = @{
81+
# UserName = ("{0}\{1}" -f $env:USERDOMAIN, $env:USERNAME) # Read-only property required
82+
# Name = 'test'
83+
# VariableValue = 'test'
84+
# } | ConvertTo-Json
85+
86+
# $r = dsc -l trace resource set -r root.cimv2/Win32_Environment -i $i
8087

81-
# Verify the variable was set
82-
$get = dsc resource get -r root.cimv2/Win32_Environment -i '{"Name":"TestVariable"}'
83-
$get | Should -BeLike "*TestValue*"
84-
}
88+
# $s = dsc resource set --resource root.cimv2/Win32_Environment --input '{"Name":"TestVariable","Value":"TestValue"}'
89+
# $LASTEXITCODE | Should -Be 0
90+
# $s | Should -BeLike "*Set operation completed successfully*"
91+
92+
# # Verify the variable was set
93+
# $get = dsc resource get -r root.cimv2/Win32_Environment -i '{"Name":"TestVariable"}'
94+
# $get | Should -BeLike "*TestValue*"
95+
# }
8596
}

wmi-adapter/wmiAdapter.psm1

Lines changed: 113 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -43,74 +43,108 @@ function GetValidCimProperties {
4343
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
4444
[Microsoft.Management.Infrastructure.CimClass]$CimClass,
4545

46+
[Parameter(Mandatory = $true)]
47+
$ClassName,
48+
4649
[Parameter()]
4750
[object]$Properties,
4851

4952
[Parameter()]
50-
[ValidateSet('Get', 'Set', 'Test')]
51-
[string]$Operation
53+
[switch] $SkipReadOnly,
54+
55+
[Parameter()]
56+
[switch] $ValidateKeyProperty
5257
)
5358

54-
$validatedProperties = [System.Collections.Generic.List[Object]]::new()
55-
56-
switch ($Operation) {
57-
'Get' {
58-
# For 'Get', we don't need to validate properties, just return all properties
59-
$cimClass.CimClassProperties | ForEach-Object {
60-
$validatedProperties.Add([PSCustomObject]@{
61-
Name = $_.Name
62-
Type = $_.CimType.ToString()
63-
IsKey = $_.Flags -contains 'Key'
64-
IsReadOnly = $_.Flags -contains 'ReadOnly'
65-
})
66-
}
59+
$availableProperties = $CimClass.CimClassProperties | Where-Object -Property Name -in $Properties.psobject.Properties.name
60+
$validatedProperties = [System.Collections.Generic.List[Array]]::new()
61+
62+
$keyProperty = ($availableProperties.flags -like "Property, Key*")
63+
64+
if ($null -eq $availableProperties) {
65+
"No valid properties found in the CIM class '$ClassName' for the provided properties." | Write-DscTrace -Operation Error
66+
exit 1
67+
}
68+
69+
if ($null -eq $keyProperty) {
70+
"Key property not provided for CIM class '$ClassName'." | Write-DscTrace -Operation Error
71+
exit 1
72+
}
73+
74+
if ($ValidateKeyProperty.IsPresent) {
75+
# Check if any key property is also read-only
76+
$keyProps = $availableProperties | Where-Object { $_.Flags.ToString() -like "*Key*" }
77+
$readOnlyKeyProps = $keyProps | Where-Object { $_.Flags.ToString() -like "*ReadOnly*" }
78+
79+
if ($readOnlyKeyProps.Count -eq $keyProps.Count) {
80+
"All key properties in the CIM class '$ClassName' are read-only, which is not supported." | Write-DscTrace -Operation Error
81+
exit 1
6782
}
68-
'Set' {
69-
# For 'Set', we need to validate that the provided properties match the CIM class
70-
$availableProperties = $cimClass.CimClassProperties | ForEach-Object {
71-
[string[]]$flags = $_.Flags.ToString().Split(",").Trim()
72-
if ($flags -notcontains 'ReadOnly' -or $flags -contains 'Key') {
73-
@{
74-
Name = $_.Name
75-
Type = $_.CimType
76-
Flags = $flags
77-
IsKey = $flags -contains 'Key'
78-
IsReadOnly = $flags -contains 'ReadOnly' # This is to ensure we identify key read-only properties
79-
}
80-
}
83+
}
84+
85+
# Check if the provided properties match the available properties in the CIM class
86+
# If the count of provided properties does not match the available properties, we log a warning but continue
87+
if ($properties.psobject.Properties.name.count -ne $availableProperties.Count) {
88+
$inputPropertyNames = $properties.psobject.Properties.Name
89+
$availablePropertyNames = $availableProperties.Name
90+
91+
$missingProperties = $inputPropertyNames | Where-Object { $_ -notin $availablePropertyNames }
92+
if ($missingProperties) {
93+
foreach ($missing in $missingProperties) {
94+
"Property '$missing' was provided but not found in the CIM class '$($CimClass.ClassName)'." | Write-DscTrace -Operation Warn
8195
}
96+
}
97+
}
8298

83-
$validatedProperties = [System.Collections.Generic.List[Object]]::new()
84-
foreach ($property in $availableProperties) {
85-
$propName = $property.Name
86-
$isKey = $property.IsKey
87-
$isReadOnly = $property.IsReadOnly
88-
89-
if ($isKey) {
90-
if ($Properties.psobject.properties.name -notcontains $propName -or $null -eq $properties.$propName -or $Properties.$propName -eq '') {
91-
"Key property '$propName' is required but not provided or is empty." | Write-DscTrace -Operation Error
92-
exit 1
93-
} else {
94-
$validatedProperties.Add([PSCustomObject]@{
95-
Name = $propName
96-
Value = $Properties.$propName
97-
Type = $property.Type
98-
IsReadOnly = $isReadOnly
99-
})
100-
}
101-
} elseif ($Properties.psobject.Properties.name -contains $propName) {
102-
$validatedProperties.Add([PSCustomObject]@{
103-
Name = $propName
104-
Value = $Properties.$propName
105-
Type = $property.Type
106-
IsReadOnly = $isReadOnly
107-
})
108-
} else {
109-
"Property '$propName' is not provided in the resource object." | Write-DscTrace -Operation Trace
99+
$validatedProperties.Add($availableProperties)
100+
101+
if ($SkipReadOnly.IsPresent) {
102+
$availableProperties = foreach ($prop in $availableProperties) {
103+
[string[]]$flags = $prop.Flags.ToString().Split(",").Trim()
104+
if ($null -ne $properties.$($prop.Name)) {
105+
# Filter out read-only properties if SkipReadOnly is specified
106+
if ($flags -notcontains 'ReadOnly') {
107+
$prop
110108
}
109+
} else {
110+
# Return $prop as if there is an empty value provided as property, we are not going to a WHERE clause
111+
$prop
111112
}
112113
}
113-
}
114+
115+
return $availableProperties
116+
}
117+
118+
# if ($SkipReadOnly.IsPresent) {
119+
# # For 'Set', we need to validate that the provided properties match the CIM class
120+
# $availableProperties = $cimClass.CimClassProperties | ForEach-Object {
121+
# [string[]]$flags = $_.Flags.ToString().Split(",").Trim()
122+
# if ($flags -notcontains 'ReadOnly' -or $flags -contains 'Key') {
123+
# $_
124+
# }
125+
# }
126+
127+
# # Reset the validated properties list as we only want to capture non-readonly properties for 'Set'
128+
# $validatedProperties = [System.Collections.Generic.List[Array]]::new()
129+
# foreach ($property in $availableProperties) {
130+
# $propName = $property.Name
131+
# $isKey = $property.IsKey
132+
133+
# if ($isKey) {
134+
# # Still check here if the key property is passed as we continue
135+
# if ($Properties.psobject.properties.name -notcontains $propName -or $null -eq $properties.$propName -or $Properties.$propName -eq '') {
136+
# "Key property '$propName' is required but not provided or is empty." | Write-DscTrace -Operation Error
137+
# exit 1
138+
# } else {
139+
# $validatedProperties.Add($property)
140+
# }
141+
# } elseif ($Properties.psobject.Properties.name -contains $propName) {
142+
# $validatedProperties.Add($property)
143+
# } else {
144+
# "Property '$propName' is not provided in the resource object." | Write-DscTrace -Operation Trace
145+
# }
146+
# }
147+
# }
114148

115149
return $validatedProperties
116150
}
@@ -120,11 +154,7 @@ function GetWmiInstance {
120154
param
121155
(
122156
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
123-
[psobject]$DesiredState,
124-
125-
[Parameter()]
126-
[ValidateSet('Get', 'Set', 'Test')]
127-
[string]$Operation = 'Get'
157+
[psobject]$DesiredState
128158
)
129159

130160
$type_fields = $DesiredState.type -split "/"
@@ -134,36 +164,41 @@ function GetWmiInstance {
134164
$class = Get-CimClass -Namespace $wmi_namespace -ClassName $wmi_classname -ErrorAction Stop
135165

136166
if ($DesiredState.properties) {
137-
$properties = GetValidCimProperties -CimClass $class -Properties $DesiredState.properties -Operation $Operation
167+
$properties = GetValidCimProperties -CimClass $class -ClassName $wmi_classname -Properties $DesiredState.properties -SkipReadOnly
138168

139169
$query = "SELECT $($properties.Name -join ',') FROM $wmi_classname"
140170
$where = " WHERE "
141171
$useWhere = $false
142172
$first = $true
143173
foreach ($property in $properties) {
144174
# TODO: validate property against the CIM class to give better error message
145-
if ($null -ne $property.value) {
175+
if ($null -ne $DesiredState.properties.$($property.Name)) {
146176
$useWhere = $true
147177
if ($first) {
148178
$first = $false
149179
} else {
150180
$where += " AND "
151181
}
152182

153-
if ($property.Type -eq "String") {
154-
$where += "$($property.Name) = '$($property.Value)'"
183+
if ($property.CimType -eq "String") {
184+
$where += "$($property.Name) = '$($DesiredState.properties.$($property.Name))'"
155185
} else {
156-
$where += "$($property.Name) = $($property.Value)"
186+
$where += "$($property.Name) = $($DesiredState.properties.$($property.Name))"
157187
}
158188
}
159189
}
160190
if ($useWhere) {
161191
$query += $where
162192
}
163193
"Query: $query" | Write-DscTrace -Operation Debug
164-
$wmi_instances = Get-CimInstance -Namespace $wmi_namespace -Query $query -ErrorAction Stop
194+
$wmi_instances = Get-CimInstance -Namespace $wmi_namespace -Query $query -ErrorAction Ignore -ErrorVariable err
165195
} else {
166-
$wmi_instances = Get-CimInstance -Namespace $wmi_namespace -ClassName $wmi_classname -ErrorAction Stop
196+
$wmi_instances = Get-CimInstance -Namespace $wmi_namespace -ClassName $wmi_classname -ErrorAction Ignore -ErrorVariable Err
197+
}
198+
199+
if ($err) {
200+
"Error retrieving WMI instances: $($err.Exception.Message)" | Write-DscTrace -Operation Error
201+
exit 1
167202
}
168203

169204
return $wmi_instances
@@ -194,12 +229,10 @@ function GetCimSpace {
194229

195230
switch ($Operation) {
196231
'Get' {
197-
# TODO: identify key properties and add WHERE clause to the query
198232
$wmi_instances = GetWmiInstance -DesiredState $DesiredState
199233

200234
if ($wmi_instances) {
201235
$instance_result = [ordered]@{}
202-
# TODO: for a `Get`, they key property must be provided so a specific instance is returned rather than just the first
203236
$wmi_instance = $wmi_instances[0] # for 'Get' we return just first matching instance; for 'export' we return all instances
204237
$wmi_instance.psobject.properties | ForEach-Object {
205238
if (($_.Name -ne "type") -and (-not $_.Name.StartsWith("Cim"))) {
@@ -225,14 +258,14 @@ function GetCimSpace {
225258

226259
$wmi_instance.Properties | ForEach-Object {
227260
if ($r.properties.psobject.properties.name -contains $_.Name) {
228-
$properties[$_.Name] = $_.Value
261+
$properties[$_.Name] = $r.properties.$($_.Name)
229262
}
230263
}
231264

232-
$readOnlyProperties = $wmi_instance.Properties | Where-Object -Property IsReadOnly -eq $true
265+
$readOnlyProperties = $wmi_instance.Properties | Where-Object -Property Flags -like "*ReadOnly*"
233266

234267
if ($null -eq $wmi_instance.CimInstance) {
235-
New-CimInstance -Namespace $wmi_instance.Namespace -ClassName $wmi_instance.ClassName -Property $properties -ErrorAction Stop
268+
$instance = New-CimInstance -Namespace $wmi_instance.Namespace -ClassName $wmi_instance.ClassName -Property $properties -ErrorAction Ignore -ErrorVariable err
236269
} else {
237270
# When calling Set-CimInstance, the read-only properties needs to be filtered out
238271
if ($readOnlyProperties) {
@@ -242,7 +275,7 @@ function GetCimSpace {
242275
}
243276
}
244277
}
245-
$wmi_instance.CimInstance | Set-CimInstance -Property $properties -ErrorAction Stop
278+
$wmi_instance.CimInstance | Set-CimInstance -Property $properties -ErrorAction Ignore -ErrorVariable err | Out-Null
246279
}
247280

248281
$addToActualState = [dscResourceObject]@{
@@ -281,9 +314,9 @@ function ValidateCimMethodAndArguments {
281314
exit 1
282315
}
283316

284-
$validatedProperties = GetValidCimProperties -CimClass $cimClass -Properties $DesiredState.properties -Operation Set
317+
$validatedProperties = GetValidCimProperties -CimClass $cimClass -ClassName $className -Properties $DesiredState.properties -ValidateKeyProperty
285318

286-
$cimInstance = GetWmiInstance -DesiredState $DesiredState -Operation Set
319+
$cimInstance = GetWmiInstance -DesiredState $DesiredState
287320

288321
return @{
289322
CimInstance = $cimInstance
@@ -318,24 +351,12 @@ class dscResourceObject {
318351
[PSCustomObject] $properties
319352
}
320353

321-
322-
323-
# $out = [dscResourceObject]@{
324-
# name = "root.cimv2/Win32_Environment"
325-
# type = "root.cimv2/Win32_Environment"
326-
# properties = [PSCustomObject]@{
327-
# Name = "test"
328-
# VariableValue = "TestValue"
329-
# UserName = ("{0}\{1}" -f $env:USERDOMAIN, $env:USERNAME) # Read-only property required
330-
# }
331-
# }
332-
333354
$out = [dscResourceObject]@{
334-
name = "root.cimv2/Win32_Environment"
335-
type = "root.cimv2/Win32_Environment"
355+
name = 'root.cimv2/Win32_Environment'
356+
type = 'root.cimv2/Win32_Environment'
336357
properties = [PSCustomObject]@{
358+
UserName = "{0}\{1}" -f $env:USERDOMAIN, $env:USERNAME
359+
VariableValue = 'update'
337360
Name = 'test'
338-
VariableValue = 'TestValue'
339-
UserName = ("{0}\{1}" -f $env:USERDOMAIN, $env:USERNAME) # Read-only property required
340361
}
341362
}

0 commit comments

Comments
 (0)