Skip to content

Refactor WMI adapter #897

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@
"vscode-nmake-tools.workspaceBuildDirectories": [
"."
],
"azure-pipelines.1ESPipelineTemplatesSchemaFile": true
"azure-pipelines.1ESPipelineTemplatesSchemaFile": true,
"powershell.codeFormatting.preset": "OTBS"
}
4 changes: 3 additions & 1 deletion wmi-adapter/copy_files.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
wmi.resource.ps1
wmi.dsc.resource.json
wmi.dsc.resource.json
wmiAdapter.psd1
wmiAdapter.psm1
2 changes: 1 addition & 1 deletion wmi-adapter/wmi.dsc.resource.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json",
"type": "Microsoft.Windows/WMI",
"version": "0.1.0",
"version": "1.0.0",
"kind": "adapter",
"description": "Resource adapter to WMI resources.",
"tags": [
Expand Down
232 changes: 86 additions & 146 deletions wmi-adapter/wmi.resource.ps1
Original file line number Diff line number Diff line change
@@ -1,170 +1,110 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

[CmdletBinding()]
param(
[ValidateSet('List','Get','Set','Test','Validate')]
$Operation = 'List',
[Parameter(ValueFromPipeline)]
$stdinput
[Parameter(Mandatory = $true, Position = 0, HelpMessage = 'Operation to perform. Choose from List, Get, Set, Test, Validate.')]
[ValidateSet('List', 'Get', 'Set', 'Test', 'Validate')]
[string]$Operation,
[Parameter(Mandatory = $false, Position = 1, ValueFromPipeline = $true, HelpMessage = 'Configuration or resource input in JSON format.')]
[string]$jsonInput = '@{}'
)

# 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.ToLower() = $message
} | ConvertTo-Json -Compress

$host.ui.WriteErrorLine($trace)
}

if ($Operation -eq 'List')
{
$clases = Get-CimClass

foreach ($r in $clases)
{
$version_string = "";
$author_string = "";

$propertyList = @()
foreach ($p in $r.CimClassProperties)
{
if ($p.Name)
{
$propertyList += $p.Name
}
}

$namespace = $r.CimSystemProperties.Namespace.ToLower().Replace('/','.')
$classname = $r.CimSystemProperties.ClassName
$fullResourceTypeName = "$namespace/$classname"
$requiresString = "Microsoft.Windows/WMI"
# Import private functions
$wmiAdapter = Import-Module "$PSScriptRoot/wmiAdapter.psm1" -Force -PassThru

$z = [pscustomobject]@{
type = $fullResourceTypeName;
kind = 'resource';
version = $version_string;
capabilities = @('get');
path = "";
directory = "";
implementedAs = "";
author = $author_string;
properties = $propertyList;
requireAdapter = $requiresString
}
if ('Validate' -ne $Operation) {
# initialize OUTPUT as array
$result = [System.Collections.Generic.List[Object]]::new()

$z | ConvertTo-Json -Compress
}
Write-DscTrace -Operation Debug -Message "jsonInput=$jsonInput"
}
elseif ($Operation -eq 'Get')
{
$inputobj_pscustomobj = $null
if ($stdinput)
{
$inputobj_pscustomobj = $stdinput | ConvertFrom-Json
}

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

foreach ($r in $inputobj_pscustomobj.resources)
{
$type_fields = $r.type -split "/"
$wmi_namespace = $type_fields[0].Replace('.','\')
$wmi_classname = $type_fields[1]
switch ($Operation) {
'List' {
$clases = Get-CimClass

# 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 "
}
foreach ($r in $clases) {
$version_string = ""
$author_string = ""
$description = ""

if ($property.TypeNameOfValue -eq "System.String")
{
$where += "$($property.Name) = '$($property.Value)'"
}
else
{
$where += "$($property.Name) = $($property.Value)"
}
$propertyList = @()
foreach ($p in $r.CimClassProperties) {
if ($p.Name) {
$propertyList += $p.Name
}
}
if ($useWhere)
{
$query += $where
}
Write-Trace -Level Trace -message "Query: $query"
$wmi_instances = Get-CimInstance -Namespace $wmi_namespace -Query $query -ErrorAction Stop

$namespace = $r.CimSystemProperties.Namespace.ToLower().Replace('/', '.')
$classname = $r.CimSystemProperties.ClassName
$fullResourceTypeName = "$namespace/$classname"
$requiresString = "Microsoft.Windows/WMI"

# OUTPUT dsc is expecting the following properties
[resourceOutput]@{
type = $fullResourceTypeName
kind = 'resource'
version = $version_string
capabilities = @('get', 'set', 'test')
path = ""
directory = ""
implementedAs = ""
author = $author_string
properties = $propertyList
requireAdapter = $requiresString
description = $description
} | ConvertTo-Json -Compress
}
else
{
$wmi_instances = Get-CimInstance -Namespace $wmi_namespace -ClassName $wmi_classname -ErrorAction Stop
}
{ @('Get', 'Set', 'Test') -contains $_ } {
$desiredState = $wmiAdapter.invoke( { param($jsonInput) Get-DscResourceObject -jsonInput $jsonInput }, $jsonInput )
if ($null -eq $desiredState) {
"Failed to create configuration object from provided input JSON." | Write-DscTrace -Operation Error
exit 1
}

if ($wmi_instances)
{
$instance_result = [ordered]@{}
# 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")))
{
if ($r.properties)
{
if ($r.properties.psobject.properties.name -contains $_.Name)
{
$instance_result[$_.Name] = $_.Value
}
}
else
{
$instance_result[$_.Name] = $_.Value
}
}
foreach ($ds in $desiredState) {
# process the INPUT (desiredState) for each resource as dscresourceInfo and return the OUTPUT as actualState
$actualstate = $wmiAdapter.Invoke( { param($op, $ds) Invoke-DscWmi -Operation $op -DesiredState $ds }, $Operation, $ds)
if ($null -eq $actualState) {
"Incomplete GET for resource $($ds.Type)" | Write-DscTrace -Operation Error
exit 1
}

$result += [pscustomobject]@{ name = $r.name; type = $r.type; properties = $instance_result }
$result += $actualstate
}
}

@{result = $result } | ConvertTo-Json -Depth 10 -Compress
}
elseif ($Operation -eq 'Validate')
{
# TODO: this is placeholder
@{ valid = $true } | ConvertTo-Json
# OUTPUT json to stderr for debug, and to stdout
"jsonOutput=$($result | ConvertTo-Json -Depth 10 -Compress)" | Write-DscTrace -Operation Debug
return (@{ result = $result } | ConvertTo-Json -Depth 10 -Compress)
}
'Validate' {
# TODO: VALIDATE not implemented

# OUTPUT
@{ valid = $true } | ConvertTo-Json
}
Default {
Write-DscTrace -Operation Error -Message 'Unsupported operation. Please use one of the following: List, Get, Set, Test, Export, Validate'
}
}
else
{
Write-Trace "ERROR: Unsupported operation requested from wmigroup.resource.ps1"

# output format for resource list
class resourceOutput {
[string] $type
[string] $kind
[string] $version
[string[]] $capabilities
[string] $path
[string] $directory
[string] $implementedAs
[string] $author
[string[]] $properties
[string] $requireAdapter
[string] $description
}
52 changes: 52 additions & 0 deletions wmi-adapter/wmiAdapter.psd1
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

@{

# Script module or binary module file associated with this manifest.
RootModule = 'wmiAdapter.psm1'

# Version number of this module.
moduleVersion = '1.0.0'

# ID used to uniquely identify this module
GUID = '420c66dc-d243-4bf8-8de0-66467328f4b7'

# Author of this module
Author = 'Microsoft Corporation'

# Company or vendor of this module
CompanyName = 'Microsoft Corporation'

# Copyright statement for this module
Copyright = '(c) Microsoft Corporation. All rights reserved.'

# Description of the functionality provided by this module
Description = 'PowerShell Desired State Configuration Module for DSC WMI Adapter'

# 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.
FunctionsToExport = @(
'Invoke-DscWmi'
)

# 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.
CmdletsToExport = @()

# Variables to export from this module
VariablesToExport = @()

# 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.
AliasesToExport = @()

PrivateData = @{
PSData = @{
DscCapabilities = @(
'get'
'test'
'set'
)

ProjectUri = 'https://github.com/PowerShell/dsc'
}
}
}
Loading
Loading