Skip to content

Commit 06f2b94

Browse files
authored
Merge pull request #897 from Gijsreyn/refactor-wmi-adapter
Refactor WMI adapter
2 parents 2ca8939 + 62ca73d commit 06f2b94

File tree

6 files changed

+312
-149
lines changed

6 files changed

+312
-149
lines changed

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@
2626
"vscode-nmake-tools.workspaceBuildDirectories": [
2727
"."
2828
],
29-
"azure-pipelines.1ESPipelineTemplatesSchemaFile": true
29+
"azure-pipelines.1ESPipelineTemplatesSchemaFile": true,
30+
"powershell.codeFormatting.preset": "OTBS"
3031
}

wmi-adapter/copy_files.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
wmi.resource.ps1
2-
wmi.dsc.resource.json
2+
wmi.dsc.resource.json
3+
wmiAdapter.psd1
4+
wmiAdapter.psm1

wmi-adapter/wmi.dsc.resource.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json",
33
"type": "Microsoft.Windows/WMI",
4-
"version": "0.1.0",
4+
"version": "1.0.0",
55
"kind": "adapter",
66
"description": "Resource adapter to WMI resources.",
77
"tags": [

wmi-adapter/wmi.resource.ps1

Lines changed: 86 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -1,170 +1,110 @@
11
# Copyright (c) Microsoft Corporation.
22
# Licensed under the MIT License.
3-
43
[CmdletBinding()]
54
param(
6-
[ValidateSet('List','Get','Set','Test','Validate')]
7-
$Operation = 'List',
8-
[Parameter(ValueFromPipeline)]
9-
$stdinput
5+
[Parameter(Mandatory = $true, Position = 0, HelpMessage = 'Operation to perform. Choose from List, Get, Set, Test, Validate.')]
6+
[ValidateSet('List', 'Get', 'Set', 'Test', 'Validate')]
7+
[string]$Operation,
8+
[Parameter(Mandatory = $false, Position = 1, ValueFromPipeline = $true, HelpMessage = 'Configuration or resource input in JSON format.')]
9+
[string]$jsonInput = '@{}'
1010
)
1111

12-
# catch any un-caught exception and write it to the error stream
13-
trap {
14-
Write-Trace -Level Error -message $_.Exception.Message
15-
exit 1
16-
}
17-
18-
$ProgressPreference = 'Ignore'
19-
$WarningPreference = 'Ignore'
20-
$VerbosePreference = 'Ignore'
21-
22-
function Write-Trace {
23-
param(
24-
[string]$message,
25-
[string]$level = 'Error'
26-
)
27-
28-
$trace = [pscustomobject]@{
29-
$level.ToLower() = $message
30-
} | ConvertTo-Json -Compress
31-
32-
$host.ui.WriteErrorLine($trace)
33-
}
34-
35-
if ($Operation -eq 'List')
36-
{
37-
$clases = Get-CimClass
38-
39-
foreach ($r in $clases)
40-
{
41-
$version_string = "";
42-
$author_string = "";
43-
44-
$propertyList = @()
45-
foreach ($p in $r.CimClassProperties)
46-
{
47-
if ($p.Name)
48-
{
49-
$propertyList += $p.Name
50-
}
51-
}
52-
53-
$namespace = $r.CimSystemProperties.Namespace.ToLower().Replace('/','.')
54-
$classname = $r.CimSystemProperties.ClassName
55-
$fullResourceTypeName = "$namespace/$classname"
56-
$requiresString = "Microsoft.Windows/WMI"
12+
# Import private functions
13+
$wmiAdapter = Import-Module "$PSScriptRoot/wmiAdapter.psm1" -Force -PassThru
5714

58-
$z = [pscustomobject]@{
59-
type = $fullResourceTypeName;
60-
kind = 'resource';
61-
version = $version_string;
62-
capabilities = @('get');
63-
path = "";
64-
directory = "";
65-
implementedAs = "";
66-
author = $author_string;
67-
properties = $propertyList;
68-
requireAdapter = $requiresString
69-
}
15+
if ('Validate' -ne $Operation) {
16+
# initialize OUTPUT as array
17+
$result = [System.Collections.Generic.List[Object]]::new()
7018

71-
$z | ConvertTo-Json -Compress
72-
}
19+
Write-DscTrace -Operation Debug -Message "jsonInput=$jsonInput"
7320
}
74-
elseif ($Operation -eq 'Get')
75-
{
76-
$inputobj_pscustomobj = $null
77-
if ($stdinput)
78-
{
79-
$inputobj_pscustomobj = $stdinput | ConvertFrom-Json
80-
}
8121

82-
$result = @()
22+
# Adding some debug info to STDERR
23+
'PSVersion=' + $PSVersionTable.PSVersion.ToString() | Write-DscTrace
24+
'PSPath=' + $PSHome | Write-DscTrace
25+
'PSModulePath=' + $env:PSModulePath | Write-DscTrace
8326

84-
foreach ($r in $inputobj_pscustomobj.resources)
85-
{
86-
$type_fields = $r.type -split "/"
87-
$wmi_namespace = $type_fields[0].Replace('.','\')
88-
$wmi_classname = $type_fields[1]
27+
switch ($Operation) {
28+
'List' {
29+
$clases = Get-CimClass
8930

90-
# TODO: identify key properties and add WHERE clause to the query
91-
if ($r.properties)
92-
{
93-
$query = "SELECT $($r.properties.psobject.properties.name -join ',') FROM $wmi_classname"
94-
$where = " WHERE "
95-
$useWhere = $false
96-
$first = $true
97-
foreach ($property in $r.properties.psobject.properties)
98-
{
99-
# TODO: validate property against the CIM class to give better error message
100-
if ($null -ne $property.value)
101-
{
102-
$useWhere = $true
103-
if ($first)
104-
{
105-
$first = $false
106-
}
107-
else
108-
{
109-
$where += " AND "
110-
}
31+
foreach ($r in $clases) {
32+
$version_string = ""
33+
$author_string = ""
34+
$description = ""
11135

112-
if ($property.TypeNameOfValue -eq "System.String")
113-
{
114-
$where += "$($property.Name) = '$($property.Value)'"
115-
}
116-
else
117-
{
118-
$where += "$($property.Name) = $($property.Value)"
119-
}
36+
$propertyList = @()
37+
foreach ($p in $r.CimClassProperties) {
38+
if ($p.Name) {
39+
$propertyList += $p.Name
12040
}
12141
}
122-
if ($useWhere)
123-
{
124-
$query += $where
125-
}
126-
Write-Trace -Level Trace -message "Query: $query"
127-
$wmi_instances = Get-CimInstance -Namespace $wmi_namespace -Query $query -ErrorAction Stop
42+
43+
$namespace = $r.CimSystemProperties.Namespace.ToLower().Replace('/', '.')
44+
$classname = $r.CimSystemProperties.ClassName
45+
$fullResourceTypeName = "$namespace/$classname"
46+
$requiresString = "Microsoft.Windows/WMI"
47+
48+
# OUTPUT dsc is expecting the following properties
49+
[resourceOutput]@{
50+
type = $fullResourceTypeName
51+
kind = 'resource'
52+
version = $version_string
53+
capabilities = @('get', 'set', 'test')
54+
path = ""
55+
directory = ""
56+
implementedAs = ""
57+
author = $author_string
58+
properties = $propertyList
59+
requireAdapter = $requiresString
60+
description = $description
61+
} | ConvertTo-Json -Compress
12862
}
129-
else
130-
{
131-
$wmi_instances = Get-CimInstance -Namespace $wmi_namespace -ClassName $wmi_classname -ErrorAction Stop
63+
}
64+
{ @('Get', 'Set', 'Test') -contains $_ } {
65+
$desiredState = $wmiAdapter.invoke( { param($jsonInput) Get-DscResourceObject -jsonInput $jsonInput }, $jsonInput )
66+
if ($null -eq $desiredState) {
67+
"Failed to create configuration object from provided input JSON." | Write-DscTrace -Operation Error
68+
exit 1
13269
}
13370

134-
if ($wmi_instances)
135-
{
136-
$instance_result = [ordered]@{}
137-
# TODO: for a `Get`, they key property must be provided so a specific instance is returned rather than just the first
138-
$wmi_instance = $wmi_instances[0] # for 'Get' we return just first matching instance; for 'export' we return all instances
139-
$wmi_instance.psobject.properties | %{
140-
if (($_.Name -ne "type") -and (-not $_.Name.StartsWith("Cim")))
141-
{
142-
if ($r.properties)
143-
{
144-
if ($r.properties.psobject.properties.name -contains $_.Name)
145-
{
146-
$instance_result[$_.Name] = $_.Value
147-
}
148-
}
149-
else
150-
{
151-
$instance_result[$_.Name] = $_.Value
152-
}
153-
}
71+
foreach ($ds in $desiredState) {
72+
# process the INPUT (desiredState) for each resource as dscresourceInfo and return the OUTPUT as actualState
73+
$actualstate = $wmiAdapter.Invoke( { param($op, $ds) Invoke-DscWmi -Operation $op -DesiredState $ds }, $Operation, $ds)
74+
if ($null -eq $actualState) {
75+
"Incomplete GET for resource $($ds.Type)" | Write-DscTrace -Operation Error
76+
exit 1
15477
}
15578

156-
$result += [pscustomobject]@{ name = $r.name; type = $r.type; properties = $instance_result }
79+
$result += $actualstate
15780
}
158-
}
15981

160-
@{result = $result } | ConvertTo-Json -Depth 10 -Compress
161-
}
162-
elseif ($Operation -eq 'Validate')
163-
{
164-
# TODO: this is placeholder
165-
@{ valid = $true } | ConvertTo-Json
82+
# OUTPUT json to stderr for debug, and to stdout
83+
"jsonOutput=$($result | ConvertTo-Json -Depth 10 -Compress)" | Write-DscTrace -Operation Debug
84+
return (@{ result = $result } | ConvertTo-Json -Depth 10 -Compress)
85+
}
86+
'Validate' {
87+
# TODO: VALIDATE not implemented
88+
89+
# OUTPUT
90+
@{ valid = $true } | ConvertTo-Json
91+
}
92+
Default {
93+
Write-DscTrace -Operation Error -Message 'Unsupported operation. Please use one of the following: List, Get, Set, Test, Export, Validate'
94+
}
16695
}
167-
else
168-
{
169-
Write-Trace "ERROR: Unsupported operation requested from wmigroup.resource.ps1"
96+
97+
# output format for resource list
98+
class resourceOutput {
99+
[string] $type
100+
[string] $kind
101+
[string] $version
102+
[string[]] $capabilities
103+
[string] $path
104+
[string] $directory
105+
[string] $implementedAs
106+
[string] $author
107+
[string[]] $properties
108+
[string] $requireAdapter
109+
[string] $description
170110
}

wmi-adapter/wmiAdapter.psd1

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
@{
5+
6+
# Script module or binary module file associated with this manifest.
7+
RootModule = 'wmiAdapter.psm1'
8+
9+
# Version number of this module.
10+
moduleVersion = '1.0.0'
11+
12+
# ID used to uniquely identify this module
13+
GUID = '420c66dc-d243-4bf8-8de0-66467328f4b7'
14+
15+
# Author of this module
16+
Author = 'Microsoft Corporation'
17+
18+
# Company or vendor of this module
19+
CompanyName = 'Microsoft Corporation'
20+
21+
# Copyright statement for this module
22+
Copyright = '(c) Microsoft Corporation. All rights reserved.'
23+
24+
# Description of the functionality provided by this module
25+
Description = 'PowerShell Desired State Configuration Module for DSC WMI Adapter'
26+
27+
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
28+
FunctionsToExport = @(
29+
'Invoke-DscWmi'
30+
)
31+
32+
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
33+
CmdletsToExport = @()
34+
35+
# Variables to export from this module
36+
VariablesToExport = @()
37+
38+
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
39+
AliasesToExport = @()
40+
41+
PrivateData = @{
42+
PSData = @{
43+
DscCapabilities = @(
44+
'get'
45+
'test'
46+
'set'
47+
)
48+
49+
ProjectUri = 'https://github.com/PowerShell/dsc'
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)