Skip to content
Open
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,17 @@ changes since the last release, see the [diff on GitHub][unreleased].

### Changed

- **Breaking change:** PowerShell adapters now expose embedded resource schemas instead of an
always-successful `validate` operation. Resource input names must use the spelling reported by
`dsc resource schema -r '<moduleName>/<resourceName>'`, including camelCase key, mandatory, and
optional names. Unknown names and schema-invalid values are rejected before invocation.
Single-resource get, set, test, and export output uses camelCase names and omits unset optional
null properties unless explicitly nullable. Deprecated full-configuration wrapper output is
unchanged. Migrate existing configurations and output consumers to the generated schema's
property names and nullability.

Related PR: [#1775](https://github.com/PowerShell/DSC/pull/1775)

- Extensions that implement the `secret` capability must now define the `args` property for the
`secret` command with exactly one secret name input argument (`nameArg`) and at most one vault
input argument (`vaultArg`). Previously, an extension could omit the secret name input argument,
Expand Down
19 changes: 7 additions & 12 deletions adapters/powershell/PowerShell_adapter.dsc.resource.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@
"return": "state"
},
"export": {
"schema": {
"embedded": { "type": "object" }
},
"executable": "pwsh",
"args": [
"-NoLogo",
Expand All @@ -114,18 +117,10 @@
"input": "stdin",
"return": "state"
},
"validate": {
"executable": "pwsh",
"args": [
"-NoLogo",
"-NonInteractive",
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-Command",
"$Input | ./psDscAdapter/powershell.resource.ps1 Validate"
],
"input": "stdin"
"schema": {
"embedded": {
"type": "object"
}
},
"exitCodes": {
"0": "Success",
Expand Down
10 changes: 10 additions & 0 deletions adapters/powershell/Tests/PSAdapted.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ Describe 'Tests for PS adapted manifests' {
$out.actualState.Value | Should -Be 42
}

It 'Rejects input that does not match the adapted resource embedded schema' -ForEach @(
@{ Json = '{"name":42}' }
@{ Json = '{"name":"hello","value":"invalid"}' }
@{ Json = '{"name":"hello","unknown":true}' }
) {
$null = dsc resource get -r 'PSAdaptedTestClassResource/PSAdaptedTestClass' -i $Json 2> (Join-Path $TestDrive 'schema-error.log')
$LASTEXITCODE | Should -Not -Be 0
Get-Content -Raw (Join-Path $TestDrive 'schema-error.log') | Should -Match 'schema|properties'
}

It 'Get operation works if adapted resource is in a path with spaces' {
$resourcePath = Join-Path -Path $TestDrive -ChildPath 'Path with spaces'
New-Item -Path $resourcePath -ItemType Directory | Out-Null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,23 @@
"requireAdapter": "Microsoft.Adapter/PowerShell",
"path": "PSAdaptedTestClassResource.psd1",
"schema": {
"embedded": {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/resources/PSAdaptedTestClass/v0.1.0/schema.json",
"title": "PSAdaptedTestClass",
"description": "An adapted resource for testing.",
"type": "object",
"required": [],
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"title": "Name",
"description": "This is property one of the adapted resource."
},
"value": {
"type": "integer",
"title": "Value",
"description": "The value of the adapted resource instance."
}
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/resources/PSAdaptedTestClass/v0.1.0/schema.json",
"title": "PSAdaptedTestClass",
"description": "An adapted resource for testing.",
"type": "object",
"required": [],
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"title": "Name",
"description": "This is property one of the adapted resource."
},
"value": {
"type": "integer",
"title": "Value",
"description": "The value of the adapted resource instance."
}
}
}
Expand Down
21 changes: 10 additions & 11 deletions adapters/powershell/Tests/powershellgroup.config.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -269,10 +269,10 @@ Describe 'PowerShell adapter resource tests' {
directives:
$directive$adapter
properties:
Name: 'TestClassResource1'
HashTableProp:
name: 'TestClassResource1'
hashTableProp:
Name: 'DSCv3'
Prop1: foo
prop1: foo
"@
$out = dsc -l trace config $operation -i $yaml 2> $TestDrive/tracing.txt
$text = $out | Out-String
Expand Down Expand Up @@ -332,8 +332,8 @@ Describe 'PowerShell adapter resource tests' {
- name: Class-resource credential info
type: TestClassResource/TestClassResource
properties:
Name: 'TestClassResource'
Credential:
name: 'TestClassResource'
credential:
UserName: 'User'
OtherProperty: 'Password'
"@
Expand All @@ -350,8 +350,8 @@ Describe 'PowerShell adapter resource tests' {
- name: Class-resource Info
type: TestClassResource/TestClassResource
properties:
Name: 'TestClassResource'
Ensure: 'Present'
name: 'TestClassResource'
ensure: 'Present'
"@

$out = dsc config get -i $yaml | ConvertFrom-Json
Expand Down Expand Up @@ -385,9 +385,9 @@ Describe 'PowerShell adapter resource tests' {
- name: Class-resource Info
type: TestClassResource/TestClassResource
properties:
Name: EchoBack
Prop1: "[[this is a string literal]"
EnumProp: 'Expected'
name: EchoBack
prop1: "[[this is a string literal]"
enumProp: 'Expected'
"@
$out = dsc config get -i $yaml | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0
Expand All @@ -396,4 +396,3 @@ Describe 'PowerShell adapter resource tests' {
$out.results.result.actualState.EnumProp | Should -BeExactly 'Expected'
}
}

46 changes: 25 additions & 21 deletions adapters/powershell/Tests/powershellgroup.resource.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,15 @@ Describe 'PowerShell adapter resource tests' {
($resources | Where-Object { $_.Type -eq 'TestClassResource/TestClassResource' }).Count | Should -Be 1
($resources | Where-Object -Property type -EQ 'TestClassResource/TestClassResource').capabilities | Should -BeIn @('get', 'set', 'test', 'export')
($resources | Where-Object -Property type -EQ 'TestClassResource/NoExport').capabilities | Should -BeIn @('get', 'set', 'test')
$resourceSchema = ($resources | Where-Object -Property type -EQ 'TestClassResource/TestClassResource').schema
$resourceSchema.type | Should -BeExactly 'object'
$resourceSchema.properties.baseProperty.type | Should -BeExactly 'string'
$resourceSchema.properties.PSObject.Properties.Name | Should -Not -Contain 'NonDscProperty'
}

It 'Get works on class-based resource' {

$r = "{'Name':'TestClassResource1'}" | dsc resource get -r 'TestClassResource/TestClassResource' -f -
$r = "{'name':'TestClassResource1'}" | dsc resource get -r 'TestClassResource/TestClassResource' -f -
$LASTEXITCODE | Should -Be 0
$res = $r | ConvertFrom-Json
$res.actualState.Prop1 | Should -BeExactly 'ValueForProp1'
Expand All @@ -48,39 +52,39 @@ Describe 'PowerShell adapter resource tests' {

It 'Get uses enum names on class-based resource' {

$r = "{'Name':'TestClassResource1'}" | dsc resource get -r 'TestClassResource/TestClassResource' -f -
$r = "{'name':'TestClassResource1'}" | dsc resource get -r 'TestClassResource/TestClassResource' -f -
$LASTEXITCODE | Should -Be 0
$res = $r | ConvertFrom-Json
$res.actualState.EnumProp | Should -BeExactly 'Expected'
}

It 'Get should return the correct properties on class-based resource' {
$r = "{'Name':'TestClassResource1'}" | dsc resource get -r 'TestClassResource/TestClassResource' -f -
$r = "{'name':'TestClassResource1'}" | dsc resource get -r 'TestClassResource/TestClassResource' -f -
$LASTEXITCODE | Should -Be 0
$res = $r | ConvertFrom-Json -AsHashtable
$res.actualState.ContainsKey('Name') | Should -Be $True
$res.actualState.ContainsKey('Prop1') | Should -Be $True
$res.actualState.ContainsKey('HashTableProp') | Should -Be $True
$res.actualState.ContainsKey('EnumProp') | Should -Be $True
$res.actualState.ContainsKey('Credential') | Should -Be $True
$res.actualState.ContainsKey('Ensure') | Should -Be $True
$res.actualState.ContainsKey('BaseProperty') | Should -Be $True
$res.actualState.ContainsKey('HiddenDscProperty') | Should -Be $True
$res.actualState.Keys -ccontains 'name' | Should -BeTrue
$res.actualState.Keys -ccontains 'prop1' | Should -BeTrue
$res.actualState.Keys -ccontains 'enumProp' | Should -BeTrue
$res.actualState.Keys -ccontains 'ensure' | Should -BeTrue
$res.actualState.ContainsKey('hashTableProp') | Should -BeFalse
$res.actualState.ContainsKey('credential') | Should -BeFalse
$res.actualState.ContainsKey('baseProperty') | Should -BeFalse
$res.actualState.ContainsKey('hiddenDscProperty') | Should -BeFalse
$res.actualState.ContainsKey('NonDscProperty') | Should -Be $False
$res.actualState.ContainsKey('HiddenNonDscProperty') | Should -Be $False
}

It 'Test works on class-based resource' {

$r = "{'Name':'TestClassResource1','Prop1':'ValueForProp1'}" | dsc resource test -r 'TestClassResource/TestClassResource' -f -
$r = "{'name':'TestClassResource1','prop1':'ValueForProp1'}" | dsc resource test -r 'TestClassResource/TestClassResource' -f -
$LASTEXITCODE | Should -Be 0
$res = $r | ConvertFrom-Json
$res.InDesiredState | Should -Be $True -Because $r
}

It 'Set works on class-based resource' {

$r = "{'Name':'TestClassResource1','Prop1':'ValueForProp1'}" | dsc resource set -r 'TestClassResource/TestClassResource' -f -
$r = "{'name':'TestClassResource1','prop1':'ValueForProp1'}" | dsc resource set -r 'TestClassResource/TestClassResource' -f -
$LASTEXITCODE | Should -Be 0
$res = $r | ConvertFrom-Json
$res.afterState.Prop1 | Should -BeExactly 'ValueForProp1'
Expand Down Expand Up @@ -373,7 +377,7 @@ Describe 'PowerShell adapter resource tests' {
}

It 'Can process a key-value pair object' {
$r = '{"HashTableProp":{"Name":"DSCv3"},"Name":"TestClassResource1"}' | dsc resource get -r 'TestClassResource/TestClassResource' -f -
$r = '{"hashTableProp":{"Name":"DSCv3"},"name":"TestClassResource1"}' | dsc resource get -r 'TestClassResource/TestClassResource' -f -
$LASTEXITCODE | Should -Be 0
$res = $r | ConvertFrom-Json
$res.actualState.HashTableProp.Name | Should -Be 'DSCv3'
Expand All @@ -392,50 +396,50 @@ Describe 'PowerShell adapter resource tests' {
}

It 'Can process SecureString property' {
$r = '{"Name":"TestClassResource1","SecureStringProp":"MySecretValue"}' | dsc resource get -r 'TestClassResource/TestClassResource' -f -
$r = '{"name":"TestClassResource1","secureStringProp":"MySecretValue"}' | dsc resource get -r 'TestClassResource/TestClassResource' -f -
$LASTEXITCODE | Should -Be 0
$res = $r | ConvertFrom-Json
$res.actualState.SecureStringProp | Should -Not -BeNullOrEmpty
}

Context 'Tracing works' {
It 'Error messages come from Write-Error' {
$null = dsc -l error resource set -r TestClassResource/StreamResource -i '{"Name":"TestClassResource1"}' 2> $TestDrive/error.log
$null = dsc -l error resource set -r TestClassResource/StreamResource -i '{"name":"TestClassResource1"}' 2> $TestDrive/error.log
$logContent = Get-Content -Path $TestDrive/error.log -Raw
$LASTEXITCODE | Should -Be 0 -Because $logContent
$logContent | Should -Match 'ERROR .*? This is an Error message' -Because $logContent
}

It 'Warning messages come from Write-Warning' {
$null = "{'Name':'TestClassResource1','Prop1':'ValueForProp1'}" | dsc -l warn resource get -r 'TestClassResource/StreamResource' -f - 2> $TestDrive/warning.log
$null = "{'name':'TestClassResource1','prop1':'ValueForProp1'}" | dsc -l warn resource get -r 'TestClassResource/StreamResource' -f - 2> $TestDrive/warning.log
$logContent = Get-Content -Path $TestDrive/warning.log -Raw
$LASTEXITCODE | Should -Be 0 -Because $logContent
$logContent | Should -Match 'WARN .*? This is a Warning message' -Because $logContent
}

It 'Info messages come from Write-Host' {
$null = "{'Name':'TestClassResource1'}" | dsc -l info resource test -r 'TestClassResource/StreamResource' -f - 2> $TestDrive/verbose.log
$null = "{'name':'TestClassResource1'}" | dsc -l info resource test -r 'TestClassResource/StreamResource' -f - 2> $TestDrive/verbose.log
$logContent = Get-Content -Path $TestDrive/verbose.log -Raw
$LASTEXITCODE | Should -Be 0 -Because $logContent
$logContent | Should -Match 'INFO .*? This is a Host message' -Because $logContent
}

It 'Debug messages come from Write-Verbose' {
$null = "{'Name':'TestClassResource1'}" | dsc -l debug resource set -r 'TestClassResource/StreamResource' -f - 2> $TestDrive/debug.log
$null = "{'name':'TestClassResource1'}" | dsc -l debug resource set -r 'TestClassResource/StreamResource' -f - 2> $TestDrive/debug.log
$logContent = Get-Content -Path $TestDrive/debug.log -Raw
$LASTEXITCODE | Should -Be 0 -Because $logContent
$logContent | Should -Match 'DEBUG .*? This is a Verbose message' -Because $logContent
}

It 'Trace messages come from Write-Debug' {
$null = dsc -l trace resource set -r TestClassResource/StreamResource -i '{"Name":"TestClassResource1"}' 2> $TestDrive/trace.log
$null = dsc -l trace resource set -r TestClassResource/StreamResource -i '{"name":"TestClassResource1"}' 2> $TestDrive/trace.log
$logContent = Get-Content -Path $TestDrive/trace.log -Raw
$LASTEXITCODE | Should -Be 0 -Because $logContent
$logContent | Should -Match 'TRACE .*? This is a Debug message' -Because $logContent
}

It 'Trace messages come from Write-Information' {
$null = dsc -l trace resource test -r TestClassResource/StreamResource -i '{"Name":"TestClassResource1"}' 2> $TestDrive/trace_info.log
$null = dsc -l trace resource test -r TestClassResource/StreamResource -i '{"name":"TestClassResource1"}' 2> $TestDrive/trace_info.log
$logContent = Get-Content -Path $TestDrive/trace_info.log -Raw
$LASTEXITCODE | Should -Be 0 -Because $logContent
$logContent | Should -Match 'INFO .*? This is an Information message' -Because $logContent
Expand Down
Loading
Loading