From 6b2062cc378cf993e946d60cf2699474320050f7 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 21 Apr 2025 10:37:46 +0200 Subject: [PATCH 01/31] Add credential support on PowerShell adapters --- .../0.0.1/TestClassResource.psm1 | 3 ++ .../Tests/powershellgroup.config.tests.ps1 | 18 +++++++++ .../Tests/win_powershellgroup.tests.ps1 | 21 ++++++++++ .../psDscAdapter/psDscAdapter.psm1 | 12 +++--- .../psDscAdapter/win_psDscAdapter.psm1 | 38 +++++++++++++++++-- 5 files changed, 81 insertions(+), 11 deletions(-) diff --git a/powershell-adapter/Tests/TestClassResource/0.0.1/TestClassResource.psm1 b/powershell-adapter/Tests/TestClassResource/0.0.1/TestClassResource.psm1 index 44b9128a..44571f9a 100644 --- a/powershell-adapter/Tests/TestClassResource/0.0.1/TestClassResource.psm1 +++ b/powershell-adapter/Tests/TestClassResource/0.0.1/TestClassResource.psm1 @@ -29,6 +29,9 @@ class TestClassResource : BaseTestClass [DscProperty()] [string] $EnumProp + [DscProperty()] + [PSCredential] $Credential + [string] $NonDscProperty # This property shouldn't be in results data hidden diff --git a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 index 23d52e75..7d03b974 100644 --- a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 @@ -237,4 +237,22 @@ Describe 'PowerShell adapter resource tests' { } } } + + It 'Config works with credential object' { + $yaml = @" + `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + resources: + - name: Class-resource Info + type: TestClassResource/TestClassResource + properties: + Name: 'TestClassResource' + Credential: + UserName: 'User' + Password: 'Password' +"@ + $out = dsc config get -i $yaml | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out.results[0].result.actualState.result[0].properties.Credential.UserName | Should -Be 'User' + $out.results[0].result.actualState.result[0].properties.Credential.Password | Should -BeNullOrEmpty + } } diff --git a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 index 65c5ffe6..5da9ea55 100644 --- a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 +++ b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 @@ -211,4 +211,25 @@ resources: $LASTEXITCODE | Should -Be 0 $out.results[0].result.inDesiredState | Should -Be $inDesiredState } + + It 'Should be able to test against a service with credentials' -Skip:(!$IsWindows) { + $yaml = @" +`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: + - name: Test service + type: Microsoft.Windows/WindowsPowerShell + properties: + resources: + - name: Test service + type: PSDesiredStateConfiguration/Service + properties: + Name: 'W32Time' + Credential: + Username: "User" + Password: "Password" +"@ + $out = dsc config test -i $yaml | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out.results[0].result.inDesiredState | Should -Be $false + } } diff --git a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 index 30c3500e..f9c2c689 100644 --- a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 @@ -421,13 +421,11 @@ function Invoke-DscOperation { $DesiredState.properties.psobject.properties | ForEach-Object -Process { # handle input objects by converting them to a hash table if ($_.Value -is [System.Management.Automation.PSCustomObject]) { - Write-DscTrace -Message "The object is a PSCustomObject" - $_.Value.psobject.properties | ForEach-Object -Begin { - $propertyHash = @{} - } -Process { - $propertyHash[$_.Name] = $_.Value - } -End { - $dscResourceInstance.$($_.Name) = $propertyHash + if ($_.Name -like '*Credential*' -and $_.Value.Username -and $_.Value.Password) { + $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) + } + else { + $dscResourceInstance.$($_.Name) = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } } } else { diff --git a/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 index f0260554..f88b5624 100644 --- a/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 @@ -364,7 +364,12 @@ function Invoke-DscOperation { # morph the INPUT object into a hashtable named "property" for the cmdlet Invoke-DscResource $DesiredState.properties.psobject.properties | ForEach-Object -Begin { $property = @{} } -Process { if ($_.Value -is [System.Management.Automation.PSCustomObject]) { - $property[$_.Name] = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } + if ($_.Name -like '*Credential*' -and $_.Value.Username -and $_.Value.Password) { + $property[$_.Name] = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) + } + else { + $property[$_.Name] = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } + } } else { $property[$_.Name] = $_.Value @@ -373,7 +378,7 @@ function Invoke-DscOperation { # using the cmdlet the appropriate dsc module, and handle errors try { - Write-DscTrace -Operation Debug -Message "Module: $($cachedDscResourceInfo.ModuleName), Name: $($cachedDscResourceInfo.Name), Property: $($property)" + Write-DscTrace -Operation Debug -Message "Module: $($cachedDscResourceInfo.ModuleName), Name: $($cachedDscResourceInfo.Name), Property: $($property | ConvertTo-Json -Compress)" $invokeResult = Invoke-DscResource -Method $Operation -ModuleName $cachedDscResourceInfo.ModuleName -Name $cachedDscResourceInfo.Name -Property $property -ErrorAction Stop if ($invokeResult.GetType().Name -eq 'Hashtable') { @@ -402,7 +407,18 @@ function Invoke-DscOperation { if ($DesiredState.properties) { # set each property of $dscResourceInstance to the value of the property in the $desiredState INPUT object $DesiredState.properties.psobject.properties | ForEach-Object -Process { - $dscResourceInstance.$($_.Name) = $_.Value + # handle input objects by converting them to a hash table + if ($_.Value -is [System.Management.Automation.PSCustomObject]) { + if ($_.Name -like '*Credential*' -and $_.Value.Username -and $_.Value.Password) { + $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) + } + else { + $dscResourceInstance.$($_.Name) = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } + } + } + else { + $dscResourceInstance.$($_.Name) = $_.Value + } } } @@ -444,9 +460,23 @@ function Invoke-DscOperation { } # morph the INPUT object into a hashtable named "property" for the cmdlet Invoke-DscResource - $DesiredState.properties.psobject.properties | ForEach-Object -Begin { $property = @{} } -Process { $property[$_.Name] = $_.Value } + $DesiredState.properties.psobject.properties | ForEach-Object -Begin { $property = @{} } -Process { + if ($_.Value -is [System.Management.Automation.PSCustomObject]) { + if ($_.Name -Like '*Credential*' -and $_.Value.Username -and $_.Value.Password) { + $property[$_.Name] = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) + } + else { + $property[$_.Name] = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } + } + } + else { + $property[$_.Name] = $_.Value + } + } + # using the cmdlet from PSDesiredStateConfiguration module in Windows try { + Write-DscTrace -Operation Debug -Message "Module: $($cachedDscResourceInfo.ModuleName), Name: $($cachedDscResourceInfo.Name), Property: $($property | ConvertTo-Json -Compress)" $invokeResult = Invoke-DscResource -Method $Operation -ModuleName $cachedDscResourceInfo.ModuleName -Name $cachedDscResourceInfo.Name -Property $property if ($invokeResult.GetType().Name -eq 'Hashtable') { $invokeResult.keys | ForEach-Object -Begin { $ResultProperties = @{} } -Process { $ResultProperties[$_] = $invokeResult.$_ } From f8e8323f5f877c063435df65b69636ae1e24fc1d Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 21 Apr 2025 11:14:20 +0200 Subject: [PATCH 02/31] Test the test --- powershell-adapter/Tests/powershellgroup.config.tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 index 7d03b974..0682ee33 100644 --- a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 @@ -252,7 +252,7 @@ Describe 'PowerShell adapter resource tests' { "@ $out = dsc config get -i $yaml | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 - $out.results[0].result.actualState.result[0].properties.Credential.UserName | Should -Be 'User' - $out.results[0].result.actualState.result[0].properties.Credential.Password | Should -BeNullOrEmpty + # $out.results[0].result.actualState.result[0].properties.Credential.UserName | Should -Be 'User' + # $out.results[0].result.actualState.result[0].properties.Credential.Password | Should -BeNullOrEmpty } } From a7c289ed81448b48dd73af0bcec2363f2df7e7ea Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 21 Apr 2025 11:28:52 +0200 Subject: [PATCH 03/31] Fix up test --- powershell-adapter/Tests/powershellgroup.config.tests.ps1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 index 0682ee33..ff5adbdf 100644 --- a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 @@ -252,7 +252,6 @@ Describe 'PowerShell adapter resource tests' { "@ $out = dsc config get -i $yaml | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 - # $out.results[0].result.actualState.result[0].properties.Credential.UserName | Should -Be 'User' - # $out.results[0].result.actualState.result[0].properties.Credential.Password | Should -BeNullOrEmpty + $out.results[0].result.actualState.result[0].properties.Credential.UserName | Should -Be 'User' } } From ec95eed06d889bc9f11d935bf465e7ea17098ad1 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 21 Apr 2025 11:35:28 +0200 Subject: [PATCH 04/31] Fix up test --- powershell-adapter/Tests/powershellgroup.config.tests.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 index ff5adbdf..1d98bfe2 100644 --- a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 @@ -252,6 +252,7 @@ Describe 'PowerShell adapter resource tests' { "@ $out = dsc config get -i $yaml | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 - $out.results[0].result.actualState.result[0].properties.Credential.UserName | Should -Be 'User' + $out.results.result.actualState.result.properties.Credential.UserName | Should -Be 'User' + $out.results.result.actualState.result.properties.Credential.Password.Length | Should -Not -BeNullOrEmpty } } From a80a87e9c982a13c27d04631708b2eabf6dea1ab Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 21 Apr 2025 15:34:29 +0200 Subject: [PATCH 05/31] Add debug line --- powershell-adapter/Tests/powershellgroup.config.tests.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 index 1d98bfe2..2f40c8f3 100644 --- a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 @@ -251,6 +251,7 @@ Describe 'PowerShell adapter resource tests' { Password: 'Password' "@ $out = dsc config get -i $yaml | ConvertFrom-Json + Write-Verbose -Message ($out | ConvertTo-Json -Depth 10 | Out-String) -Verbose $LASTEXITCODE | Should -Be 0 $out.results.result.actualState.result.properties.Credential.UserName | Should -Be 'User' $out.results.result.actualState.result.properties.Credential.Password.Length | Should -Not -BeNullOrEmpty From 3d1d120622ec34e6d3f0e34786f345e5aaba9671 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 21 Apr 2025 15:45:55 +0200 Subject: [PATCH 06/31] Fix test --- powershell-adapter/Tests/powershellgroup.config.tests.ps1 | 5 ++--- s.ps1 | 0 2 files changed, 2 insertions(+), 3 deletions(-) create mode 100644 s.ps1 diff --git a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 index 2f40c8f3..38c5eabb 100644 --- a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 @@ -251,9 +251,8 @@ Describe 'PowerShell adapter resource tests' { Password: 'Password' "@ $out = dsc config get -i $yaml | ConvertFrom-Json - Write-Verbose -Message ($out | ConvertTo-Json -Depth 10 | Out-String) -Verbose $LASTEXITCODE | Should -Be 0 - $out.results.result.actualState.result.properties.Credential.UserName | Should -Be 'User' - $out.results.result.actualState.result.properties.Credential.Password.Length | Should -Not -BeNullOrEmpty + $out.results.result.actualstate.Credential.UserName | Should -Be 'User' + $out.results.result.actualState.result.Credential.Password.Length | Should -Be 8 } } diff --git a/s.ps1 b/s.ps1 new file mode 100644 index 00000000..e69de29b From 9132a2a9315e1d515dea79270ee44311cf217f21 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 21 Apr 2025 15:58:06 +0200 Subject: [PATCH 07/31] Runner hides length --- powershell-adapter/Tests/powershellgroup.config.tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 index 38c5eabb..7bc1f28a 100644 --- a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 @@ -253,6 +253,6 @@ Describe 'PowerShell adapter resource tests' { $out = dsc config get -i $yaml | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 $out.results.result.actualstate.Credential.UserName | Should -Be 'User' - $out.results.result.actualState.result.Credential.Password.Length | Should -Be 8 + $out.results.result.actualState.result.Credential.Password.Length | Should -Not -BeNullOrEmpty } } From fa91d5f30d9a9e5df2ed736c7e3af5ce1f0f77ab Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 21 Apr 2025 16:30:09 +0200 Subject: [PATCH 08/31] Debug Windows PowerShell --- powershell-adapter/Tests/win_powershellgroup.tests.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 index 5da9ea55..297a91d3 100644 --- a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 +++ b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 @@ -228,6 +228,7 @@ resources: Username: "User" Password: "Password" "@ + dsc -l trace config test -i $yaml $out = dsc config test -i $yaml | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 $out.results[0].result.inDesiredState | Should -Be $false From 426b7ef55e3bd8e85f08274e0820bad1d0d746e9 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 21 Apr 2025 17:18:38 +0200 Subject: [PATCH 09/31] Rewrite to validate property to PSCredential --- .../Tests/win_powershellgroup.tests.ps1 | 22 -------------- .../psDscAdapter/psDscAdapter.psm1 | 9 +++++- .../psDscAdapter/win_psDscAdapter.psm1 | 29 ++++++++++++++----- s.ps1 | 0 4 files changed, 30 insertions(+), 30 deletions(-) delete mode 100644 s.ps1 diff --git a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 index 297a91d3..65c5ffe6 100644 --- a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 +++ b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 @@ -211,26 +211,4 @@ resources: $LASTEXITCODE | Should -Be 0 $out.results[0].result.inDesiredState | Should -Be $inDesiredState } - - It 'Should be able to test against a service with credentials' -Skip:(!$IsWindows) { - $yaml = @" -`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json -resources: - - name: Test service - type: Microsoft.Windows/WindowsPowerShell - properties: - resources: - - name: Test service - type: PSDesiredStateConfiguration/Service - properties: - Name: 'W32Time' - Credential: - Username: "User" - Password: "Password" -"@ - dsc -l trace config test -i $yaml - $out = dsc config test -i $yaml | ConvertFrom-Json - $LASTEXITCODE | Should -Be 0 - $out.results[0].result.inDesiredState | Should -Be $false - } } diff --git a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 index f9c2c689..a5604a1d 100644 --- a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 @@ -416,12 +416,19 @@ function Invoke-DscOperation { $ValidProperties = $cachedDscResourceInfo.Properties.Name + $ValidProperties | ConvertTo-Json | Write-DscTrace -Operation Trace + if ($DesiredState.properties) { # set each property of $dscResourceInstance to the value of the property in the $desiredState INPUT object $DesiredState.properties.psobject.properties | ForEach-Object -Process { # handle input objects by converting them to a hash table if ($_.Value -is [System.Management.Automation.PSCustomObject]) { - if ($_.Name -like '*Credential*' -and $_.Value.Username -and $_.Value.Password) { + $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Value + if ($validateProperty.PropertyType -eq 'PSCredential') { + if (-not $_.Value.Username -and -not $_.Value.Password) { + "Credential property '$($_.Name)' requires both username and password input object" | Write-DscTrace -Operation Error + exit 1 + } $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) } else { diff --git a/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 index f88b5624..119b07d2 100644 --- a/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 @@ -364,11 +364,16 @@ function Invoke-DscOperation { # morph the INPUT object into a hashtable named "property" for the cmdlet Invoke-DscResource $DesiredState.properties.psobject.properties | ForEach-Object -Begin { $property = @{} } -Process { if ($_.Value -is [System.Management.Automation.PSCustomObject]) { - if ($_.Name -like '*Credential*' -and $_.Value.Username -and $_.Value.Password) { - $property[$_.Name] = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) + $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Value + if ($validateProperty.PropertyType -eq 'PSCredential') { + if (-not $_.Value.Username -and -not $_.Value.Password) { + "Credential property '$($_.Name)' requires both username and password input object" | Write-DscTrace -Operation Error + exit 1 + } + $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) } else { - $property[$_.Name] = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } + $dscResourceInstance.$($_.Name) = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } } } else { @@ -409,7 +414,12 @@ function Invoke-DscOperation { $DesiredState.properties.psobject.properties | ForEach-Object -Process { # handle input objects by converting them to a hash table if ($_.Value -is [System.Management.Automation.PSCustomObject]) { - if ($_.Name -like '*Credential*' -and $_.Value.Username -and $_.Value.Password) { + $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Value + if ($validateProperty.PropertyType -eq 'PSCredential') { + if (-not $_.Value.Username -and -not $_.Value.Password) { + "Credential property '$($_.Name)' requires both username and password input object" | Write-DscTrace -Operation Error + exit 1 + } $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) } else { @@ -462,11 +472,16 @@ function Invoke-DscOperation { # morph the INPUT object into a hashtable named "property" for the cmdlet Invoke-DscResource $DesiredState.properties.psobject.properties | ForEach-Object -Begin { $property = @{} } -Process { if ($_.Value -is [System.Management.Automation.PSCustomObject]) { - if ($_.Name -Like '*Credential*' -and $_.Value.Username -and $_.Value.Password) { - $property[$_.Name] = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) + $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Value + if ($validateProperty.PropertyType -eq 'PSCredential') { + if (-not $_.Value.Username -and -not $_.Value.Password) { + "Credential property '$($_.Name)' requires both username and password input object" | Write-DscTrace -Operation Error + exit 1 + } + $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) } else { - $property[$_.Name] = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } + $dscResourceInstance.$($_.Name) = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } } } else { diff --git a/s.ps1 b/s.ps1 deleted file mode 100644 index e69de29b..00000000 From 597a4e11d0633b0203cfd31428ebbb4153836590 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 21 Apr 2025 18:01:53 +0200 Subject: [PATCH 10/31] Equals name --- powershell-adapter/psDscAdapter/psDscAdapter.psm1 | 2 +- .../psDscAdapter/win_psDscAdapter.psm1 | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 index a5604a1d..1a7e7968 100644 --- a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 @@ -423,7 +423,7 @@ function Invoke-DscOperation { $DesiredState.properties.psobject.properties | ForEach-Object -Process { # handle input objects by converting them to a hash table if ($_.Value -is [System.Management.Automation.PSCustomObject]) { - $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Value + $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Name if ($validateProperty.PropertyType -eq 'PSCredential') { if (-not $_.Value.Username -and -not $_.Value.Password) { "Credential property '$($_.Name)' requires both username and password input object" | Write-DscTrace -Operation Error diff --git a/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 index 119b07d2..c10e4eb7 100644 --- a/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 @@ -364,16 +364,16 @@ function Invoke-DscOperation { # morph the INPUT object into a hashtable named "property" for the cmdlet Invoke-DscResource $DesiredState.properties.psobject.properties | ForEach-Object -Begin { $property = @{} } -Process { if ($_.Value -is [System.Management.Automation.PSCustomObject]) { - $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Value + $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Name if ($validateProperty.PropertyType -eq 'PSCredential') { if (-not $_.Value.Username -and -not $_.Value.Password) { "Credential property '$($_.Name)' requires both username and password input object" | Write-DscTrace -Operation Error exit 1 } - $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) + $property.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) } else { - $dscResourceInstance.$($_.Name) = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } + $property.$($_.Name) = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } } } else { @@ -414,7 +414,7 @@ function Invoke-DscOperation { $DesiredState.properties.psobject.properties | ForEach-Object -Process { # handle input objects by converting them to a hash table if ($_.Value -is [System.Management.Automation.PSCustomObject]) { - $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Value + $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Name if ($validateProperty.PropertyType -eq 'PSCredential') { if (-not $_.Value.Username -and -not $_.Value.Password) { "Credential property '$($_.Name)' requires both username and password input object" | Write-DscTrace -Operation Error @@ -472,16 +472,16 @@ function Invoke-DscOperation { # morph the INPUT object into a hashtable named "property" for the cmdlet Invoke-DscResource $DesiredState.properties.psobject.properties | ForEach-Object -Begin { $property = @{} } -Process { if ($_.Value -is [System.Management.Automation.PSCustomObject]) { - $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Value + $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Name if ($validateProperty.PropertyType -eq 'PSCredential') { if (-not $_.Value.Username -and -not $_.Value.Password) { "Credential property '$($_.Name)' requires both username and password input object" | Write-DscTrace -Operation Error exit 1 } - $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) + $property.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) } else { - $dscResourceInstance.$($_.Name) = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } + $property.$($_.Name) = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } } } else { From d46f1321e047eb49b0d5e9177a23fccf7b537185 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Fri, 25 Apr 2025 11:42:48 +0200 Subject: [PATCH 11/31] Add missing tests --- .../Tests/powershellgroup.config.tests.ps1 | 18 ++++++++++++++++++ .../Tests/win_powershellgroup.tests.ps1 | 18 ++++++++++++++++++ .../psDscAdapter/psDscAdapter.psm1 | 4 ++-- .../psDscAdapter/win_psDscAdapter.psm1 | 19 ++++++++++--------- 4 files changed, 48 insertions(+), 11 deletions(-) diff --git a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 index 7bc1f28a..19ea48fb 100644 --- a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 @@ -255,4 +255,22 @@ Describe 'PowerShell adapter resource tests' { $out.results.result.actualstate.Credential.UserName | Should -Be 'User' $out.results.result.actualState.result.Credential.Password.Length | Should -Not -BeNullOrEmpty } + + It 'Config does not work when credential properties are missing required fields' { + $yaml = @" + `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + resources: + - name: Class-resource credential info + type: TestClassResource/TestClassResource + properties: + Name: 'TestClassResource' + Credential: + UserName: 'User' + OtherProperty: 'Password' +"@ + $out = dsc config get -i $yaml 2>&1 | Out-String + $LASTEXITCODE | Should -Be 2 + $out | Should -Not -BeNullOrEmpty + $out | Should -BeLike "*ERROR*The PSCredential property 'Credential' is missing required fields*" + } } diff --git a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 index 65c5ffe6..2738f9c4 100644 --- a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 +++ b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 @@ -211,4 +211,22 @@ resources: $LASTEXITCODE | Should -Be 0 $out.results[0].result.inDesiredState | Should -Be $inDesiredState } + + It 'Config does not work when credential properties are missing required fields' -Skip:(!$IsWindows) { + $yaml = @" + `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + resources: + - name: Service info + type: PsDesiredStateConfiguration/Service + properties: + Name: Spooler + Credential: + UserName: 'User' + OtherProperty: 'Password' +"@ + $out = dsc config get -i $yaml 2>&1 | Out-String + $LASTEXITCODE | Should -Be 2 + $out | Should -Not -BeNullOrEmpty + $out | Should -BeLike "*ERROR*The PSCredential property 'Credential' is missing required fields*" + } } diff --git a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 index 1a7e7968..785747dd 100644 --- a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 @@ -425,8 +425,8 @@ function Invoke-DscOperation { if ($_.Value -is [System.Management.Automation.PSCustomObject]) { $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Name if ($validateProperty.PropertyType -eq 'PSCredential') { - if (-not $_.Value.Username -and -not $_.Value.Password) { - "Credential property '$($_.Name)' requires both username and password input object" | Write-DscTrace -Operation Error + if (-not $_.Value.Username -or -not $_.Value.Password) { + "The PSCredential property '$($_.Name)' is missing required fields 'Username' and 'Password'" | Write-DscTrace -Operation Error exit 1 } $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) diff --git a/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 index c10e4eb7..62160799 100644 --- a/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 @@ -365,9 +365,10 @@ function Invoke-DscOperation { $DesiredState.properties.psobject.properties | ForEach-Object -Begin { $property = @{} } -Process { if ($_.Value -is [System.Management.Automation.PSCustomObject]) { $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Name - if ($validateProperty.PropertyType -eq 'PSCredential') { - if (-not $_.Value.Username -and -not $_.Value.Password) { - "Credential property '$($_.Name)' requires both username and password input object" | Write-DscTrace -Operation Error + $validateProperty | Write-DscTrace -Operation Debug + if ($validateProperty.PropertyType -eq '[PSCredential]') { + if (-not $_.Value.Username -or -not $_.Value.Password) { + "The PSCredential property '$($_.Name)' is missing required fields 'Username' and 'Password'" | Write-DscTrace -Operation Error exit 1 } $property.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) @@ -415,9 +416,9 @@ function Invoke-DscOperation { # handle input objects by converting them to a hash table if ($_.Value -is [System.Management.Automation.PSCustomObject]) { $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Name - if ($validateProperty.PropertyType -eq 'PSCredential') { - if (-not $_.Value.Username -and -not $_.Value.Password) { - "Credential property '$($_.Name)' requires both username and password input object" | Write-DscTrace -Operation Error + if ($validateProperty.PropertyType -eq '[PSCredential]') { + if (-not $_.Value.Username -or -not $_.Value.Password) { + "The PSCredential property '$($_.Name)' is missing required fields 'Username' and 'Password'" | Write-DscTrace -Operation Error exit 1 } $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) @@ -473,9 +474,9 @@ function Invoke-DscOperation { $DesiredState.properties.psobject.properties | ForEach-Object -Begin { $property = @{} } -Process { if ($_.Value -is [System.Management.Automation.PSCustomObject]) { $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Name - if ($validateProperty.PropertyType -eq 'PSCredential') { - if (-not $_.Value.Username -and -not $_.Value.Password) { - "Credential property '$($_.Name)' requires both username and password input object" | Write-DscTrace -Operation Error + if ($validateProperty.PropertyType -eq '[PSCredential]') { + if (-not $_.Value.Username -or -not $_.Value.Password) { + "The PSCredential property '$($_.Name)' is missing required fields 'Username' and 'Password'" | Write-DscTrace -Operation Error exit 1 } $property.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) From a4b4a865090b9d9dc3999ab3d0c0eb5db2cadca4 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Fri, 25 Apr 2025 12:36:07 +0200 Subject: [PATCH 12/31] Test PowerShell --- powershell-adapter/Tests/win_powershellgroup.tests.ps1 | 3 ++- powershell-adapter/psDscAdapter/psDscAdapter.psm1 | 2 +- powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 index 2738f9c4..28b6e62b 100644 --- a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 +++ b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 @@ -224,7 +224,8 @@ resources: UserName: 'User' OtherProperty: 'Password' "@ - $out = dsc config get -i $yaml 2>&1 | Out-String + $out = dsc -l trace config get -i $yaml 2>&1 | Out-String + $out = dsc -l trace config get -i $yaml 2>&1 | Out-String $LASTEXITCODE | Should -Be 2 $out | Should -Not -BeNullOrEmpty $out | Should -BeLike "*ERROR*The PSCredential property 'Credential' is missing required fields*" diff --git a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 index 785747dd..52bb2a1f 100644 --- a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 @@ -426,7 +426,7 @@ function Invoke-DscOperation { $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Name if ($validateProperty.PropertyType -eq 'PSCredential') { if (-not $_.Value.Username -or -not $_.Value.Password) { - "The PSCredential property '$($_.Name)' is missing required fields 'Username' and 'Password'" | Write-DscTrace -Operation Error + "Credential object '$($_.Name)' requires both 'username' and 'password' properties" | Write-DscTrace -Operation Error exit 1 } $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) diff --git a/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 index 62160799..f25dfb6a 100644 --- a/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 @@ -368,7 +368,7 @@ function Invoke-DscOperation { $validateProperty | Write-DscTrace -Operation Debug if ($validateProperty.PropertyType -eq '[PSCredential]') { if (-not $_.Value.Username -or -not $_.Value.Password) { - "The PSCredential property '$($_.Name)' is missing required fields 'Username' and 'Password'" | Write-DscTrace -Operation Error + "Credential object '$($_.Name)' requires both 'username' and 'password' properties" | Write-DscTrace -Operation Error exit 1 } $property.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) @@ -418,7 +418,7 @@ function Invoke-DscOperation { $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Name if ($validateProperty.PropertyType -eq '[PSCredential]') { if (-not $_.Value.Username -or -not $_.Value.Password) { - "The PSCredential property '$($_.Name)' is missing required fields 'Username' and 'Password'" | Write-DscTrace -Operation Error + "Credential object '$($_.Name)' requires both 'username' and 'password' properties" | Write-DscTrace -Operation Error exit 1 } $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) @@ -476,7 +476,7 @@ function Invoke-DscOperation { $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Name if ($validateProperty.PropertyType -eq '[PSCredential]') { if (-not $_.Value.Username -or -not $_.Value.Password) { - "The PSCredential property '$($_.Name)' is missing required fields 'Username' and 'Password'" | Write-DscTrace -Operation Error + "Credential object '$($_.Name)' requires both 'username' and 'password' properties" | Write-DscTrace -Operation Error exit 1 } $property.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) From 8da5b0bb49fffc09bc9c84e0925207518a6c1b61 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Fri, 25 Apr 2025 12:49:06 +0200 Subject: [PATCH 13/31] Add tracing level for Windows PowerShell --- powershell-adapter/Tests/win_powershellgroup.tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 index 28b6e62b..32a2a567 100644 --- a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 +++ b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 @@ -224,7 +224,7 @@ resources: UserName: 'User' OtherProperty: 'Password' "@ - $out = dsc -l trace config get -i $yaml 2>&1 | Out-String + dsc -l trace config get -i $yaml $out = dsc -l trace config get -i $yaml 2>&1 | Out-String $LASTEXITCODE | Should -Be 2 $out | Should -Not -BeNullOrEmpty From 7b7f7fd886b74e9a589ecdf1a6786b31b89611f4 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 21 Apr 2025 10:37:46 +0200 Subject: [PATCH 14/31] Add credential support on PowerShell adapters --- .../0.0.1/TestClassResource.psm1 | 3 ++ .../Tests/powershellgroup.config.tests.ps1 | 18 +++++++++ .../Tests/win_powershellgroup.tests.ps1 | 21 ++++++++++ .../psDscAdapter/psDscAdapter.psm1 | 12 +++--- .../psDscAdapter/win_psDscAdapter.psm1 | 38 +++++++++++++++++-- 5 files changed, 81 insertions(+), 11 deletions(-) diff --git a/powershell-adapter/Tests/TestClassResource/0.0.1/TestClassResource.psm1 b/powershell-adapter/Tests/TestClassResource/0.0.1/TestClassResource.psm1 index 44b9128a..44571f9a 100644 --- a/powershell-adapter/Tests/TestClassResource/0.0.1/TestClassResource.psm1 +++ b/powershell-adapter/Tests/TestClassResource/0.0.1/TestClassResource.psm1 @@ -29,6 +29,9 @@ class TestClassResource : BaseTestClass [DscProperty()] [string] $EnumProp + [DscProperty()] + [PSCredential] $Credential + [string] $NonDscProperty # This property shouldn't be in results data hidden diff --git a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 index 23d52e75..7d03b974 100644 --- a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 @@ -237,4 +237,22 @@ Describe 'PowerShell adapter resource tests' { } } } + + It 'Config works with credential object' { + $yaml = @" + `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + resources: + - name: Class-resource Info + type: TestClassResource/TestClassResource + properties: + Name: 'TestClassResource' + Credential: + UserName: 'User' + Password: 'Password' +"@ + $out = dsc config get -i $yaml | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out.results[0].result.actualState.result[0].properties.Credential.UserName | Should -Be 'User' + $out.results[0].result.actualState.result[0].properties.Credential.Password | Should -BeNullOrEmpty + } } diff --git a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 index 65c5ffe6..5da9ea55 100644 --- a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 +++ b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 @@ -211,4 +211,25 @@ resources: $LASTEXITCODE | Should -Be 0 $out.results[0].result.inDesiredState | Should -Be $inDesiredState } + + It 'Should be able to test against a service with credentials' -Skip:(!$IsWindows) { + $yaml = @" +`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: + - name: Test service + type: Microsoft.Windows/WindowsPowerShell + properties: + resources: + - name: Test service + type: PSDesiredStateConfiguration/Service + properties: + Name: 'W32Time' + Credential: + Username: "User" + Password: "Password" +"@ + $out = dsc config test -i $yaml | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out.results[0].result.inDesiredState | Should -Be $false + } } diff --git a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 index 30c3500e..f9c2c689 100644 --- a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 @@ -421,13 +421,11 @@ function Invoke-DscOperation { $DesiredState.properties.psobject.properties | ForEach-Object -Process { # handle input objects by converting them to a hash table if ($_.Value -is [System.Management.Automation.PSCustomObject]) { - Write-DscTrace -Message "The object is a PSCustomObject" - $_.Value.psobject.properties | ForEach-Object -Begin { - $propertyHash = @{} - } -Process { - $propertyHash[$_.Name] = $_.Value - } -End { - $dscResourceInstance.$($_.Name) = $propertyHash + if ($_.Name -like '*Credential*' -and $_.Value.Username -and $_.Value.Password) { + $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) + } + else { + $dscResourceInstance.$($_.Name) = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } } } else { diff --git a/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 index f0260554..f88b5624 100644 --- a/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 @@ -364,7 +364,12 @@ function Invoke-DscOperation { # morph the INPUT object into a hashtable named "property" for the cmdlet Invoke-DscResource $DesiredState.properties.psobject.properties | ForEach-Object -Begin { $property = @{} } -Process { if ($_.Value -is [System.Management.Automation.PSCustomObject]) { - $property[$_.Name] = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } + if ($_.Name -like '*Credential*' -and $_.Value.Username -and $_.Value.Password) { + $property[$_.Name] = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) + } + else { + $property[$_.Name] = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } + } } else { $property[$_.Name] = $_.Value @@ -373,7 +378,7 @@ function Invoke-DscOperation { # using the cmdlet the appropriate dsc module, and handle errors try { - Write-DscTrace -Operation Debug -Message "Module: $($cachedDscResourceInfo.ModuleName), Name: $($cachedDscResourceInfo.Name), Property: $($property)" + Write-DscTrace -Operation Debug -Message "Module: $($cachedDscResourceInfo.ModuleName), Name: $($cachedDscResourceInfo.Name), Property: $($property | ConvertTo-Json -Compress)" $invokeResult = Invoke-DscResource -Method $Operation -ModuleName $cachedDscResourceInfo.ModuleName -Name $cachedDscResourceInfo.Name -Property $property -ErrorAction Stop if ($invokeResult.GetType().Name -eq 'Hashtable') { @@ -402,7 +407,18 @@ function Invoke-DscOperation { if ($DesiredState.properties) { # set each property of $dscResourceInstance to the value of the property in the $desiredState INPUT object $DesiredState.properties.psobject.properties | ForEach-Object -Process { - $dscResourceInstance.$($_.Name) = $_.Value + # handle input objects by converting them to a hash table + if ($_.Value -is [System.Management.Automation.PSCustomObject]) { + if ($_.Name -like '*Credential*' -and $_.Value.Username -and $_.Value.Password) { + $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) + } + else { + $dscResourceInstance.$($_.Name) = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } + } + } + else { + $dscResourceInstance.$($_.Name) = $_.Value + } } } @@ -444,9 +460,23 @@ function Invoke-DscOperation { } # morph the INPUT object into a hashtable named "property" for the cmdlet Invoke-DscResource - $DesiredState.properties.psobject.properties | ForEach-Object -Begin { $property = @{} } -Process { $property[$_.Name] = $_.Value } + $DesiredState.properties.psobject.properties | ForEach-Object -Begin { $property = @{} } -Process { + if ($_.Value -is [System.Management.Automation.PSCustomObject]) { + if ($_.Name -Like '*Credential*' -and $_.Value.Username -and $_.Value.Password) { + $property[$_.Name] = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) + } + else { + $property[$_.Name] = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } + } + } + else { + $property[$_.Name] = $_.Value + } + } + # using the cmdlet from PSDesiredStateConfiguration module in Windows try { + Write-DscTrace -Operation Debug -Message "Module: $($cachedDscResourceInfo.ModuleName), Name: $($cachedDscResourceInfo.Name), Property: $($property | ConvertTo-Json -Compress)" $invokeResult = Invoke-DscResource -Method $Operation -ModuleName $cachedDscResourceInfo.ModuleName -Name $cachedDscResourceInfo.Name -Property $property if ($invokeResult.GetType().Name -eq 'Hashtable') { $invokeResult.keys | ForEach-Object -Begin { $ResultProperties = @{} } -Process { $ResultProperties[$_] = $invokeResult.$_ } From 27b81f951d5b0874c7782ab421b27a436bf68c7c Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 21 Apr 2025 11:14:20 +0200 Subject: [PATCH 15/31] Test the test --- powershell-adapter/Tests/powershellgroup.config.tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 index 7d03b974..0682ee33 100644 --- a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 @@ -252,7 +252,7 @@ Describe 'PowerShell adapter resource tests' { "@ $out = dsc config get -i $yaml | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 - $out.results[0].result.actualState.result[0].properties.Credential.UserName | Should -Be 'User' - $out.results[0].result.actualState.result[0].properties.Credential.Password | Should -BeNullOrEmpty + # $out.results[0].result.actualState.result[0].properties.Credential.UserName | Should -Be 'User' + # $out.results[0].result.actualState.result[0].properties.Credential.Password | Should -BeNullOrEmpty } } From 3717af6c06a33caa92a7f830824dfb13d4326ab2 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 21 Apr 2025 11:28:52 +0200 Subject: [PATCH 16/31] Fix up test --- powershell-adapter/Tests/powershellgroup.config.tests.ps1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 index 0682ee33..ff5adbdf 100644 --- a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 @@ -252,7 +252,6 @@ Describe 'PowerShell adapter resource tests' { "@ $out = dsc config get -i $yaml | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 - # $out.results[0].result.actualState.result[0].properties.Credential.UserName | Should -Be 'User' - # $out.results[0].result.actualState.result[0].properties.Credential.Password | Should -BeNullOrEmpty + $out.results[0].result.actualState.result[0].properties.Credential.UserName | Should -Be 'User' } } From c6a21e8546edccd14b7cba6c931b7826e522a466 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 21 Apr 2025 11:35:28 +0200 Subject: [PATCH 17/31] Fix up test --- powershell-adapter/Tests/powershellgroup.config.tests.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 index ff5adbdf..1d98bfe2 100644 --- a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 @@ -252,6 +252,7 @@ Describe 'PowerShell adapter resource tests' { "@ $out = dsc config get -i $yaml | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 - $out.results[0].result.actualState.result[0].properties.Credential.UserName | Should -Be 'User' + $out.results.result.actualState.result.properties.Credential.UserName | Should -Be 'User' + $out.results.result.actualState.result.properties.Credential.Password.Length | Should -Not -BeNullOrEmpty } } From 2abd9dd13cfd36d09ed8856e876b09a0d7f48e33 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 21 Apr 2025 15:34:29 +0200 Subject: [PATCH 18/31] Add debug line --- powershell-adapter/Tests/powershellgroup.config.tests.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 index 1d98bfe2..2f40c8f3 100644 --- a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 @@ -251,6 +251,7 @@ Describe 'PowerShell adapter resource tests' { Password: 'Password' "@ $out = dsc config get -i $yaml | ConvertFrom-Json + Write-Verbose -Message ($out | ConvertTo-Json -Depth 10 | Out-String) -Verbose $LASTEXITCODE | Should -Be 0 $out.results.result.actualState.result.properties.Credential.UserName | Should -Be 'User' $out.results.result.actualState.result.properties.Credential.Password.Length | Should -Not -BeNullOrEmpty From fc4d712879800000a37f1d793e9962943e7a2078 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 21 Apr 2025 15:45:55 +0200 Subject: [PATCH 19/31] Fix test --- powershell-adapter/Tests/powershellgroup.config.tests.ps1 | 5 ++--- s.ps1 | 0 2 files changed, 2 insertions(+), 3 deletions(-) create mode 100644 s.ps1 diff --git a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 index 2f40c8f3..38c5eabb 100644 --- a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 @@ -251,9 +251,8 @@ Describe 'PowerShell adapter resource tests' { Password: 'Password' "@ $out = dsc config get -i $yaml | ConvertFrom-Json - Write-Verbose -Message ($out | ConvertTo-Json -Depth 10 | Out-String) -Verbose $LASTEXITCODE | Should -Be 0 - $out.results.result.actualState.result.properties.Credential.UserName | Should -Be 'User' - $out.results.result.actualState.result.properties.Credential.Password.Length | Should -Not -BeNullOrEmpty + $out.results.result.actualstate.Credential.UserName | Should -Be 'User' + $out.results.result.actualState.result.Credential.Password.Length | Should -Be 8 } } diff --git a/s.ps1 b/s.ps1 new file mode 100644 index 00000000..e69de29b From 666862aa1290433297f59c206c43bbfb74167a9a Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 21 Apr 2025 15:58:06 +0200 Subject: [PATCH 20/31] Runner hides length --- powershell-adapter/Tests/powershellgroup.config.tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 index 38c5eabb..7bc1f28a 100644 --- a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 @@ -253,6 +253,6 @@ Describe 'PowerShell adapter resource tests' { $out = dsc config get -i $yaml | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 $out.results.result.actualstate.Credential.UserName | Should -Be 'User' - $out.results.result.actualState.result.Credential.Password.Length | Should -Be 8 + $out.results.result.actualState.result.Credential.Password.Length | Should -Not -BeNullOrEmpty } } From eb06406088d0e50a4bad31ae84264c8f16478efb Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 21 Apr 2025 16:30:09 +0200 Subject: [PATCH 21/31] Debug Windows PowerShell --- powershell-adapter/Tests/win_powershellgroup.tests.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 index 5da9ea55..297a91d3 100644 --- a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 +++ b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 @@ -228,6 +228,7 @@ resources: Username: "User" Password: "Password" "@ + dsc -l trace config test -i $yaml $out = dsc config test -i $yaml | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 $out.results[0].result.inDesiredState | Should -Be $false From 985b5ca574c76596bec3be7def50f6d1f9544d10 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 21 Apr 2025 17:18:38 +0200 Subject: [PATCH 22/31] Rewrite to validate property to PSCredential --- .../Tests/win_powershellgroup.tests.ps1 | 22 -------------- .../psDscAdapter/psDscAdapter.psm1 | 9 +++++- .../psDscAdapter/win_psDscAdapter.psm1 | 29 ++++++++++++++----- s.ps1 | 0 4 files changed, 30 insertions(+), 30 deletions(-) delete mode 100644 s.ps1 diff --git a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 index 297a91d3..65c5ffe6 100644 --- a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 +++ b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 @@ -211,26 +211,4 @@ resources: $LASTEXITCODE | Should -Be 0 $out.results[0].result.inDesiredState | Should -Be $inDesiredState } - - It 'Should be able to test against a service with credentials' -Skip:(!$IsWindows) { - $yaml = @" -`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json -resources: - - name: Test service - type: Microsoft.Windows/WindowsPowerShell - properties: - resources: - - name: Test service - type: PSDesiredStateConfiguration/Service - properties: - Name: 'W32Time' - Credential: - Username: "User" - Password: "Password" -"@ - dsc -l trace config test -i $yaml - $out = dsc config test -i $yaml | ConvertFrom-Json - $LASTEXITCODE | Should -Be 0 - $out.results[0].result.inDesiredState | Should -Be $false - } } diff --git a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 index f9c2c689..a5604a1d 100644 --- a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 @@ -416,12 +416,19 @@ function Invoke-DscOperation { $ValidProperties = $cachedDscResourceInfo.Properties.Name + $ValidProperties | ConvertTo-Json | Write-DscTrace -Operation Trace + if ($DesiredState.properties) { # set each property of $dscResourceInstance to the value of the property in the $desiredState INPUT object $DesiredState.properties.psobject.properties | ForEach-Object -Process { # handle input objects by converting them to a hash table if ($_.Value -is [System.Management.Automation.PSCustomObject]) { - if ($_.Name -like '*Credential*' -and $_.Value.Username -and $_.Value.Password) { + $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Value + if ($validateProperty.PropertyType -eq 'PSCredential') { + if (-not $_.Value.Username -and -not $_.Value.Password) { + "Credential property '$($_.Name)' requires both username and password input object" | Write-DscTrace -Operation Error + exit 1 + } $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) } else { diff --git a/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 index f88b5624..119b07d2 100644 --- a/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 @@ -364,11 +364,16 @@ function Invoke-DscOperation { # morph the INPUT object into a hashtable named "property" for the cmdlet Invoke-DscResource $DesiredState.properties.psobject.properties | ForEach-Object -Begin { $property = @{} } -Process { if ($_.Value -is [System.Management.Automation.PSCustomObject]) { - if ($_.Name -like '*Credential*' -and $_.Value.Username -and $_.Value.Password) { - $property[$_.Name] = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) + $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Value + if ($validateProperty.PropertyType -eq 'PSCredential') { + if (-not $_.Value.Username -and -not $_.Value.Password) { + "Credential property '$($_.Name)' requires both username and password input object" | Write-DscTrace -Operation Error + exit 1 + } + $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) } else { - $property[$_.Name] = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } + $dscResourceInstance.$($_.Name) = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } } } else { @@ -409,7 +414,12 @@ function Invoke-DscOperation { $DesiredState.properties.psobject.properties | ForEach-Object -Process { # handle input objects by converting them to a hash table if ($_.Value -is [System.Management.Automation.PSCustomObject]) { - if ($_.Name -like '*Credential*' -and $_.Value.Username -and $_.Value.Password) { + $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Value + if ($validateProperty.PropertyType -eq 'PSCredential') { + if (-not $_.Value.Username -and -not $_.Value.Password) { + "Credential property '$($_.Name)' requires both username and password input object" | Write-DscTrace -Operation Error + exit 1 + } $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) } else { @@ -462,11 +472,16 @@ function Invoke-DscOperation { # morph the INPUT object into a hashtable named "property" for the cmdlet Invoke-DscResource $DesiredState.properties.psobject.properties | ForEach-Object -Begin { $property = @{} } -Process { if ($_.Value -is [System.Management.Automation.PSCustomObject]) { - if ($_.Name -Like '*Credential*' -and $_.Value.Username -and $_.Value.Password) { - $property[$_.Name] = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) + $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Value + if ($validateProperty.PropertyType -eq 'PSCredential') { + if (-not $_.Value.Username -and -not $_.Value.Password) { + "Credential property '$($_.Name)' requires both username and password input object" | Write-DscTrace -Operation Error + exit 1 + } + $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) } else { - $property[$_.Name] = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } + $dscResourceInstance.$($_.Name) = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } } } else { diff --git a/s.ps1 b/s.ps1 deleted file mode 100644 index e69de29b..00000000 From dfad8a03d406e463b7dc5ea7b2870a1ef8610dbb Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Mon, 21 Apr 2025 18:01:53 +0200 Subject: [PATCH 23/31] Equals name --- powershell-adapter/psDscAdapter/psDscAdapter.psm1 | 2 +- .../psDscAdapter/win_psDscAdapter.psm1 | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 index a5604a1d..1a7e7968 100644 --- a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 @@ -423,7 +423,7 @@ function Invoke-DscOperation { $DesiredState.properties.psobject.properties | ForEach-Object -Process { # handle input objects by converting them to a hash table if ($_.Value -is [System.Management.Automation.PSCustomObject]) { - $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Value + $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Name if ($validateProperty.PropertyType -eq 'PSCredential') { if (-not $_.Value.Username -and -not $_.Value.Password) { "Credential property '$($_.Name)' requires both username and password input object" | Write-DscTrace -Operation Error diff --git a/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 index 119b07d2..c10e4eb7 100644 --- a/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 @@ -364,16 +364,16 @@ function Invoke-DscOperation { # morph the INPUT object into a hashtable named "property" for the cmdlet Invoke-DscResource $DesiredState.properties.psobject.properties | ForEach-Object -Begin { $property = @{} } -Process { if ($_.Value -is [System.Management.Automation.PSCustomObject]) { - $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Value + $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Name if ($validateProperty.PropertyType -eq 'PSCredential') { if (-not $_.Value.Username -and -not $_.Value.Password) { "Credential property '$($_.Name)' requires both username and password input object" | Write-DscTrace -Operation Error exit 1 } - $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) + $property.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) } else { - $dscResourceInstance.$($_.Name) = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } + $property.$($_.Name) = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } } } else { @@ -414,7 +414,7 @@ function Invoke-DscOperation { $DesiredState.properties.psobject.properties | ForEach-Object -Process { # handle input objects by converting them to a hash table if ($_.Value -is [System.Management.Automation.PSCustomObject]) { - $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Value + $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Name if ($validateProperty.PropertyType -eq 'PSCredential') { if (-not $_.Value.Username -and -not $_.Value.Password) { "Credential property '$($_.Name)' requires both username and password input object" | Write-DscTrace -Operation Error @@ -472,16 +472,16 @@ function Invoke-DscOperation { # morph the INPUT object into a hashtable named "property" for the cmdlet Invoke-DscResource $DesiredState.properties.psobject.properties | ForEach-Object -Begin { $property = @{} } -Process { if ($_.Value -is [System.Management.Automation.PSCustomObject]) { - $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Value + $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Name if ($validateProperty.PropertyType -eq 'PSCredential') { if (-not $_.Value.Username -and -not $_.Value.Password) { "Credential property '$($_.Name)' requires both username and password input object" | Write-DscTrace -Operation Error exit 1 } - $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) + $property.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) } else { - $dscResourceInstance.$($_.Name) = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } + $property.$($_.Name) = $_.Value.psobject.properties | ForEach-Object -Begin { $propertyHash = @{} } -Process { $propertyHash[$_.Name] = $_.Value } -End { $propertyHash } } } else { From d03480beae531e8e6ee6502d8b05aa93d9211993 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Fri, 25 Apr 2025 11:42:48 +0200 Subject: [PATCH 24/31] Add missing tests --- .../Tests/powershellgroup.config.tests.ps1 | 18 ++++++++++++++++++ .../Tests/win_powershellgroup.tests.ps1 | 18 ++++++++++++++++++ .../psDscAdapter/psDscAdapter.psm1 | 4 ++-- .../psDscAdapter/win_psDscAdapter.psm1 | 19 ++++++++++--------- 4 files changed, 48 insertions(+), 11 deletions(-) diff --git a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 index 7bc1f28a..19ea48fb 100644 --- a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 @@ -255,4 +255,22 @@ Describe 'PowerShell adapter resource tests' { $out.results.result.actualstate.Credential.UserName | Should -Be 'User' $out.results.result.actualState.result.Credential.Password.Length | Should -Not -BeNullOrEmpty } + + It 'Config does not work when credential properties are missing required fields' { + $yaml = @" + `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + resources: + - name: Class-resource credential info + type: TestClassResource/TestClassResource + properties: + Name: 'TestClassResource' + Credential: + UserName: 'User' + OtherProperty: 'Password' +"@ + $out = dsc config get -i $yaml 2>&1 | Out-String + $LASTEXITCODE | Should -Be 2 + $out | Should -Not -BeNullOrEmpty + $out | Should -BeLike "*ERROR*The PSCredential property 'Credential' is missing required fields*" + } } diff --git a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 index 65c5ffe6..2738f9c4 100644 --- a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 +++ b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 @@ -211,4 +211,22 @@ resources: $LASTEXITCODE | Should -Be 0 $out.results[0].result.inDesiredState | Should -Be $inDesiredState } + + It 'Config does not work when credential properties are missing required fields' -Skip:(!$IsWindows) { + $yaml = @" + `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + resources: + - name: Service info + type: PsDesiredStateConfiguration/Service + properties: + Name: Spooler + Credential: + UserName: 'User' + OtherProperty: 'Password' +"@ + $out = dsc config get -i $yaml 2>&1 | Out-String + $LASTEXITCODE | Should -Be 2 + $out | Should -Not -BeNullOrEmpty + $out | Should -BeLike "*ERROR*The PSCredential property 'Credential' is missing required fields*" + } } diff --git a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 index 1a7e7968..785747dd 100644 --- a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 @@ -425,8 +425,8 @@ function Invoke-DscOperation { if ($_.Value -is [System.Management.Automation.PSCustomObject]) { $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Name if ($validateProperty.PropertyType -eq 'PSCredential') { - if (-not $_.Value.Username -and -not $_.Value.Password) { - "Credential property '$($_.Name)' requires both username and password input object" | Write-DscTrace -Operation Error + if (-not $_.Value.Username -or -not $_.Value.Password) { + "The PSCredential property '$($_.Name)' is missing required fields 'Username' and 'Password'" | Write-DscTrace -Operation Error exit 1 } $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) diff --git a/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 index c10e4eb7..62160799 100644 --- a/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 @@ -365,9 +365,10 @@ function Invoke-DscOperation { $DesiredState.properties.psobject.properties | ForEach-Object -Begin { $property = @{} } -Process { if ($_.Value -is [System.Management.Automation.PSCustomObject]) { $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Name - if ($validateProperty.PropertyType -eq 'PSCredential') { - if (-not $_.Value.Username -and -not $_.Value.Password) { - "Credential property '$($_.Name)' requires both username and password input object" | Write-DscTrace -Operation Error + $validateProperty | Write-DscTrace -Operation Debug + if ($validateProperty.PropertyType -eq '[PSCredential]') { + if (-not $_.Value.Username -or -not $_.Value.Password) { + "The PSCredential property '$($_.Name)' is missing required fields 'Username' and 'Password'" | Write-DscTrace -Operation Error exit 1 } $property.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) @@ -415,9 +416,9 @@ function Invoke-DscOperation { # handle input objects by converting them to a hash table if ($_.Value -is [System.Management.Automation.PSCustomObject]) { $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Name - if ($validateProperty.PropertyType -eq 'PSCredential') { - if (-not $_.Value.Username -and -not $_.Value.Password) { - "Credential property '$($_.Name)' requires both username and password input object" | Write-DscTrace -Operation Error + if ($validateProperty.PropertyType -eq '[PSCredential]') { + if (-not $_.Value.Username -or -not $_.Value.Password) { + "The PSCredential property '$($_.Name)' is missing required fields 'Username' and 'Password'" | Write-DscTrace -Operation Error exit 1 } $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) @@ -473,9 +474,9 @@ function Invoke-DscOperation { $DesiredState.properties.psobject.properties | ForEach-Object -Begin { $property = @{} } -Process { if ($_.Value -is [System.Management.Automation.PSCustomObject]) { $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Name - if ($validateProperty.PropertyType -eq 'PSCredential') { - if (-not $_.Value.Username -and -not $_.Value.Password) { - "Credential property '$($_.Name)' requires both username and password input object" | Write-DscTrace -Operation Error + if ($validateProperty.PropertyType -eq '[PSCredential]') { + if (-not $_.Value.Username -or -not $_.Value.Password) { + "The PSCredential property '$($_.Name)' is missing required fields 'Username' and 'Password'" | Write-DscTrace -Operation Error exit 1 } $property.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) From 628bc7f0ad532b903b1c2664e3a01cb514c023c6 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Fri, 25 Apr 2025 12:36:07 +0200 Subject: [PATCH 25/31] Test PowerShell --- powershell-adapter/Tests/win_powershellgroup.tests.ps1 | 3 ++- powershell-adapter/psDscAdapter/psDscAdapter.psm1 | 2 +- powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 index 2738f9c4..28b6e62b 100644 --- a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 +++ b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 @@ -224,7 +224,8 @@ resources: UserName: 'User' OtherProperty: 'Password' "@ - $out = dsc config get -i $yaml 2>&1 | Out-String + $out = dsc -l trace config get -i $yaml 2>&1 | Out-String + $out = dsc -l trace config get -i $yaml 2>&1 | Out-String $LASTEXITCODE | Should -Be 2 $out | Should -Not -BeNullOrEmpty $out | Should -BeLike "*ERROR*The PSCredential property 'Credential' is missing required fields*" diff --git a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 index 785747dd..52bb2a1f 100644 --- a/powershell-adapter/psDscAdapter/psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/psDscAdapter.psm1 @@ -426,7 +426,7 @@ function Invoke-DscOperation { $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Name if ($validateProperty.PropertyType -eq 'PSCredential') { if (-not $_.Value.Username -or -not $_.Value.Password) { - "The PSCredential property '$($_.Name)' is missing required fields 'Username' and 'Password'" | Write-DscTrace -Operation Error + "Credential object '$($_.Name)' requires both 'username' and 'password' properties" | Write-DscTrace -Operation Error exit 1 } $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) diff --git a/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 index 62160799..f25dfb6a 100644 --- a/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 @@ -368,7 +368,7 @@ function Invoke-DscOperation { $validateProperty | Write-DscTrace -Operation Debug if ($validateProperty.PropertyType -eq '[PSCredential]') { if (-not $_.Value.Username -or -not $_.Value.Password) { - "The PSCredential property '$($_.Name)' is missing required fields 'Username' and 'Password'" | Write-DscTrace -Operation Error + "Credential object '$($_.Name)' requires both 'username' and 'password' properties" | Write-DscTrace -Operation Error exit 1 } $property.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) @@ -418,7 +418,7 @@ function Invoke-DscOperation { $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Name if ($validateProperty.PropertyType -eq '[PSCredential]') { if (-not $_.Value.Username -or -not $_.Value.Password) { - "The PSCredential property '$($_.Name)' is missing required fields 'Username' and 'Password'" | Write-DscTrace -Operation Error + "Credential object '$($_.Name)' requires both 'username' and 'password' properties" | Write-DscTrace -Operation Error exit 1 } $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) @@ -476,7 +476,7 @@ function Invoke-DscOperation { $validateProperty = $cachedDscResourceInfo.Properties | Where-Object -Property Name -EQ $_.Name if ($validateProperty.PropertyType -eq '[PSCredential]') { if (-not $_.Value.Username -or -not $_.Value.Password) { - "The PSCredential property '$($_.Name)' is missing required fields 'Username' and 'Password'" | Write-DscTrace -Operation Error + "Credential object '$($_.Name)' requires both 'username' and 'password' properties" | Write-DscTrace -Operation Error exit 1 } $property.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) From 1c69a6f75df74feb42f8494ae592606c610b6627 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Fri, 25 Apr 2025 12:49:06 +0200 Subject: [PATCH 26/31] Add tracing level for Windows PowerShell --- powershell-adapter/Tests/win_powershellgroup.tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 index 28b6e62b..32a2a567 100644 --- a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 +++ b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 @@ -224,7 +224,7 @@ resources: UserName: 'User' OtherProperty: 'Password' "@ - $out = dsc -l trace config get -i $yaml 2>&1 | Out-String + dsc -l trace config get -i $yaml $out = dsc -l trace config get -i $yaml 2>&1 | Out-String $LASTEXITCODE | Should -Be 2 $out | Should -Not -BeNullOrEmpty From e2e85fd461fef1c73e9940754981938659170336 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Fri, 25 Apr 2025 13:13:52 +0200 Subject: [PATCH 27/31] Fix failing test --- powershell-adapter/Tests/powershellgroup.config.tests.ps1 | 2 +- powershell-adapter/Tests/win_powershellgroup.tests.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 index 19ea48fb..9cdfdb26 100644 --- a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 @@ -271,6 +271,6 @@ Describe 'PowerShell adapter resource tests' { $out = dsc config get -i $yaml 2>&1 | Out-String $LASTEXITCODE | Should -Be 2 $out | Should -Not -BeNullOrEmpty - $out | Should -BeLike "*ERROR*The PSCredential property 'Credential' is missing required fields*" + $out | Should -BeLike "*ERROR*Credential object 'Credential' requires both 'username' and 'password' properties*" } } diff --git a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 index 32a2a567..0828d42d 100644 --- a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 +++ b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 @@ -228,6 +228,6 @@ resources: $out = dsc -l trace config get -i $yaml 2>&1 | Out-String $LASTEXITCODE | Should -Be 2 $out | Should -Not -BeNullOrEmpty - $out | Should -BeLike "*ERROR*The PSCredential property 'Credential' is missing required fields*" + $out | Should -BeLike "*ERROR*Credential object 'Credential' requires both 'username' and 'password' properties*" } } From 23b4d92da9e0502a582f61395c3699209ca7cc65 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Fri, 25 Apr 2025 14:01:11 +0200 Subject: [PATCH 28/31] Add mock example on Windows Powershell --- .../Tests/win_powershellgroup.tests.ps1 | 265 ++++++++++-------- 1 file changed, 152 insertions(+), 113 deletions(-) diff --git a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 index 0828d42d..b35e4e4d 100644 --- a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 +++ b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 @@ -3,107 +3,107 @@ Describe 'WindowsPowerShell adapter resource tests - requires elevated permissions' { - BeforeAll { - if ($isWindows) { - winrm quickconfig -quiet -force - } - $OldPSModulePath = $env:PSModulePath - $env:PSModulePath += [System.IO.Path]::PathSeparator + $PSScriptRoot - - $winpsConfigPath = Join-path $PSScriptRoot "winps_resource.dsc.yaml" - if ($isWindows) { - $cacheFilePath_v5 = Join-Path $env:LocalAppData "dsc" "WindowsPSAdapterCache.json" - } + BeforeAll { + if ($isWindows) { + winrm quickconfig -quiet -force } - AfterAll { - $env:PSModulePath = $OldPSModulePath + $OldPSModulePath = $env:PSModulePath + $env:PSModulePath += [System.IO.Path]::PathSeparator + $PSScriptRoot + + $winpsConfigPath = Join-path $PSScriptRoot "winps_resource.dsc.yaml" + if ($isWindows) { + $cacheFilePath_v5 = Join-Path $env:LocalAppData "dsc" "WindowsPSAdapterCache.json" } + } + AfterAll { + $env:PSModulePath = $OldPSModulePath + } - BeforeEach { - if ($isWindows) { - Remove-Item -Force -ea SilentlyContinue -Path $cacheFilePath_v5 - } + BeforeEach { + if ($isWindows) { + Remove-Item -Force -ea SilentlyContinue -Path $cacheFilePath_v5 } + } - It 'Windows PowerShell adapter supports File resource' -Skip:(!$IsWindows){ + It 'Windows PowerShell adapter supports File resource' -Skip:(!$IsWindows) { - $r = dsc resource list --adapter Microsoft.Windows/WindowsPowerShell - $LASTEXITCODE | Should -Be 0 - $resources = $r | ConvertFrom-Json - ($resources | Where-Object {$_.Type -eq 'PSDesiredStateConfiguration/File'}).Count | Should -Be 1 - } + $r = dsc resource list --adapter Microsoft.Windows/WindowsPowerShell + $LASTEXITCODE | Should -Be 0 + $resources = $r | ConvertFrom-Json + ($resources | Where-Object { $_.Type -eq 'PSDesiredStateConfiguration/File' }).Count | Should -Be 1 + } - It 'Get works on Binary "File" resource' -Skip:(!$IsWindows){ + It 'Get works on Binary "File" resource' -Skip:(!$IsWindows) { - $testFile = "$testdrive\test.txt" - 'test' | Set-Content -Path $testFile -Force - $r = '{"DestinationPath":"' + $testFile.replace('\','\\') + '"}' | dsc resource get -r 'PSDesiredStateConfiguration/File' -f - - $LASTEXITCODE | Should -Be 0 - $res = $r | ConvertFrom-Json - $res.actualState.DestinationPath | Should -Be "$testFile" - } + $testFile = "$testdrive\test.txt" + 'test' | Set-Content -Path $testFile -Force + $r = '{"DestinationPath":"' + $testFile.replace('\', '\\') + '"}' | dsc resource get -r 'PSDesiredStateConfiguration/File' -f - + $LASTEXITCODE | Should -Be 0 + $res = $r | ConvertFrom-Json + $res.actualState.DestinationPath | Should -Be "$testFile" + } - It 'Set works on Binary "File" resource' -Skip:(!$IsWindows){ + It 'Set works on Binary "File" resource' -Skip:(!$IsWindows) { - $testFile = "$testdrive\test.txt" - $null = '{"DestinationPath":"' + $testFile.replace('\','\\') + '", type: File, contents: HelloWorld, Ensure: present}' | dsc resource set -r 'PSDesiredStateConfiguration/File' -f - - $LASTEXITCODE | Should -Be 0 - Get-Content -Raw -Path $testFile | Should -Be "HelloWorld" - } + $testFile = "$testdrive\test.txt" + $null = '{"DestinationPath":"' + $testFile.replace('\', '\\') + '", type: File, contents: HelloWorld, Ensure: present}' | dsc resource set -r 'PSDesiredStateConfiguration/File' -f - + $LASTEXITCODE | Should -Be 0 + Get-Content -Raw -Path $testFile | Should -Be "HelloWorld" + } - It 'Get works on traditional "Script" resource' -Skip:(!$IsWindows){ + It 'Get works on traditional "Script" resource' -Skip:(!$IsWindows) { - $testFile = "$testdrive\test.txt" - 'test' | Set-Content -Path $testFile -Force - $r = '{"GetScript": "@{result = $(Get-Content ' + $testFile.replace('\','\\') + ')}", "SetScript": "throw", "TestScript": "throw"}' | dsc resource get -r 'PSDesiredStateConfiguration/Script' -f - - $LASTEXITCODE | Should -Be 0 - $res = $r | ConvertFrom-Json - $res.actualState.result | Should -Be 'test' - } + $testFile = "$testdrive\test.txt" + 'test' | Set-Content -Path $testFile -Force + $r = '{"GetScript": "@{result = $(Get-Content ' + $testFile.replace('\', '\\') + ')}", "SetScript": "throw", "TestScript": "throw"}' | dsc resource get -r 'PSDesiredStateConfiguration/Script' -f - + $LASTEXITCODE | Should -Be 0 + $res = $r | ConvertFrom-Json + $res.actualState.result | Should -Be 'test' + } - It 'Get works on config with File resource for WinPS' -Skip:(!$IsWindows){ + It 'Get works on config with File resource for WinPS' -Skip:(!$IsWindows) { - $testFile = "$testdrive\test.txt" - 'test' | Set-Content -Path $testFile -Force - $r = (Get-Content -Raw $winpsConfigPath).Replace('c:\test.txt',"$testFile") | dsc config get -f - - $LASTEXITCODE | Should -Be 0 - $res = $r | ConvertFrom-Json - $res.results[0].result.actualState.result[0].properties.DestinationPath | Should -Be "$testFile" - } + $testFile = "$testdrive\test.txt" + 'test' | Set-Content -Path $testFile -Force + $r = (Get-Content -Raw $winpsConfigPath).Replace('c:\test.txt', "$testFile") | dsc config get -f - + $LASTEXITCODE | Should -Be 0 + $res = $r | ConvertFrom-Json + $res.results[0].result.actualState.result[0].properties.DestinationPath | Should -Be "$testFile" + } - It 'Verify that there are no cache rebuilds for several sequential executions' -Skip:(!$IsWindows) { - # remove cache file - $cacheFilePath = Join-Path $env:LocalAppData "dsc\WindowsPSAdapterCache.json" - Remove-Item -Force -Path $cacheFilePath -ErrorAction Ignore + It 'Verify that there are no cache rebuilds for several sequential executions' -Skip:(!$IsWindows) { + # remove cache file + $cacheFilePath = Join-Path $env:LocalAppData "dsc\WindowsPSAdapterCache.json" + Remove-Item -Force -Path $cacheFilePath -ErrorAction Ignore - # first execution should build the cache - dsc -l trace resource list -a Microsoft.Windows/WindowsPowerShell 2> $TestDrive/tracing.txt - "$TestDrive/tracing.txt" | Should -FileContentMatchExactly 'Constructing Get-DscResource cache' + # first execution should build the cache + dsc -l trace resource list -a Microsoft.Windows/WindowsPowerShell 2> $TestDrive/tracing.txt + "$TestDrive/tracing.txt" | Should -FileContentMatchExactly 'Constructing Get-DscResource cache' - # next executions following shortly after should Not rebuild the cache - 1..3 | ForEach-Object { - dsc -l trace resource list -a Microsoft.Windows/WindowsPowerShell 2> $TestDrive/tracing.txt - "$TestDrive/tracing.txt" | Should -Not -FileContentMatchExactly 'Constructing Get-DscResource cache' - } + # next executions following shortly after should Not rebuild the cache + 1..3 | ForEach-Object { + dsc -l trace resource list -a Microsoft.Windows/WindowsPowerShell 2> $TestDrive/tracing.txt + "$TestDrive/tracing.txt" | Should -Not -FileContentMatchExactly 'Constructing Get-DscResource cache' } + } - It 'Verify if assertion is used that no module is cleared in the cache' -Skip:(!$IsWindows) { - # create a test file in the test drive - $testFile = "$testdrive\test.txt" - New-Item -Path $testFile -ItemType File -Force | Out-Null + It 'Verify if assertion is used that no module is cleared in the cache' -Skip:(!$IsWindows) { + # create a test file in the test drive + $testFile = "$testdrive\test.txt" + New-Item -Path $testFile -ItemType File -Force | Out-Null - # remove cache file - $cacheFilePath = Join-Path $env:LocalAppData "dsc\WindowsPSAdapterCache.json" - Remove-Item -Force -Path $cacheFilePath -ErrorAction Ignore + # remove cache file + $cacheFilePath = Join-Path $env:LocalAppData "dsc\WindowsPSAdapterCache.json" + Remove-Item -Force -Path $cacheFilePath -ErrorAction Ignore - # build the cache - dsc resource list --adapter Microsoft.Windows/WindowsPowerShell | Out-Null + # build the cache + dsc resource list --adapter Microsoft.Windows/WindowsPowerShell | Out-Null - # Create a test module in the test drive - $testModuleDir = "$testdrive\TestModule\1.0.0" - New-Item -Path $testModuleDir -ItemType Directory -Force | Out-Null + # Create a test module in the test drive + $testModuleDir = "$testdrive\TestModule\1.0.0" + New-Item -Path $testModuleDir -ItemType Directory -Force | Out-Null - $manifestContent = @" + $manifestContent = @" @{ RootModule = 'TestModule.psm1' ModuleVersion = '1.0.0' @@ -120,17 +120,17 @@ Describe 'WindowsPowerShell adapter resource tests - requires elevated permissio AliasesToExport = @() } "@ - Set-Content -Path "$testModuleDir\TestModule.psd1" -Value $manifestContent + Set-Content -Path "$testModuleDir\TestModule.psd1" -Value $manifestContent - $scriptContent = @" + $scriptContent = @" Write-Host 'The DSC world!' "@ - Set-Content -Path "$testModuleDir\TestModule.psm1" -Value $scriptContent + Set-Content -Path "$testModuleDir\TestModule.psm1" -Value $scriptContent - # Add the test module directory to PSModulePath - $env:PSModulePath += [System.IO.Path]::PathSeparator + $testdrive + # Add the test module directory to PSModulePath + $env:PSModulePath += [System.IO.Path]::PathSeparator + $testdrive - $yaml = @" + $yaml = @" `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json resources: - name: File @@ -164,25 +164,25 @@ resources: - "[resourceId('Microsoft.Windows/WindowsPowerShell', 'File')]" - "[resourceId('Microsoft.DSC/Assertion', 'File present')]" "@ - # output to file for Windows PowerShell 5.1 - $filePath = "$testdrive\test.assertion.dsc.resource.yaml" - $yaml | Set-Content -Path $filePath -Force - dsc config test -f $filePath 2> "$TestDrive/error.txt" - $LASTEXITCODE | Should -Be 2 - - $cache = Get-Content -Path $cacheFilePath -Raw | ConvertFrom-Json - $cache.ResourceCache.Type | Should -Contain 'PSTestModule/TestPSRepository' - $cache.ResourceCache.Type | Should -Contain 'PSDesiredStateConfiguration/File' - } + # output to file for Windows PowerShell 5.1 + $filePath = "$testdrive\test.assertion.dsc.resource.yaml" + $yaml | Set-Content -Path $filePath -Force + dsc config test -f $filePath 2> "$TestDrive/error.txt" + $LASTEXITCODE | Should -Be 2 - It '_inDesiredState is returned correction: ' -Skip:(!$IsWindows) -TestCases @( - @{ Context = 'Both running'; FirstState = 'Running'; SecondState = 'Running' } - @{ Context = 'Both stopped'; FirstState = 'Stopped'; SecondState = 'Stopped' } - @{ Context = 'First Stopped'; FirstState = 'Stopped'; SecondState = 'Running' } - @{ Context = 'First Running'; FirstState = 'Running'; SecondState = 'Stopped' } - ) { - param($Context, $FirstState, $SecondState) - $yaml = @" + $cache = Get-Content -Path $cacheFilePath -Raw | ConvertFrom-Json + $cache.ResourceCache.Type | Should -Contain 'PSTestModule/TestPSRepository' + $cache.ResourceCache.Type | Should -Contain 'PSDesiredStateConfiguration/File' + } + + It '_inDesiredState is returned correction: ' -Skip:(!$IsWindows) -TestCases @( + @{ Context = 'Both running'; FirstState = 'Running'; SecondState = 'Running' } + @{ Context = 'Both stopped'; FirstState = 'Stopped'; SecondState = 'Stopped' } + @{ Context = 'First Stopped'; FirstState = 'Stopped'; SecondState = 'Running' } + @{ Context = 'First Running'; FirstState = 'Running'; SecondState = 'Stopped' } + ) { + param($Context, $FirstState, $SecondState) + $yaml = @" `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json resources: - name: Use Windows PowerShell resources @@ -201,18 +201,57 @@ resources: State: $SecondState "@ - $inDesiredState = if ($FirstState -eq $SecondState) { - $FirstState -eq (Get-Service Spooler).Status - } else { - $false - } + $inDesiredState = if ($FirstState -eq $SecondState) { + $FirstState -eq (Get-Service Spooler).Status + } + else { + $false + } + + $out = dsc config test -i $yaml | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out.results[0].result.inDesiredState | Should -Be $inDesiredState + } - $out = dsc config test -i $yaml | ConvertFrom-Json - $LASTEXITCODE | Should -Be 0 - $out.results[0].result.inDesiredState | Should -Be $inDesiredState + It 'Config works with credential object' { + BeforeAll { + $script:winPSModule = Resolve-Path -Path (Join-Path $PSScriptRoot '..' 'powershell-adapter' 'psDscAdapter' 'win_psDscAdapter.psm1') | Select-Object -ExpandProperty Path + Import-Module $winPSModule -Force -ErrorAction Stop + + # Mock the command to work on GitHub runners because Microsoft.PowerShell.Security is not available + Mock -CommandName ConvertTo-SecureString -MockWith { [System.Security.SecureString]::new() } + } + + AfterAll { + Remove-Module $script:winPSModule -Force -ErrorAction Ignore } - It 'Config does not work when credential properties are missing required fields' -Skip:(!$IsWindows) { + $jsonInput = @{ + resources = @{ + name = 'Service info' + type = 'PSDesiredStateConfiguration/Service' + properties = @{ + Name = 'Spooler' + Credential = @{ + UserName = 'User' + Password = 'Password' + } + } + } + } | ConvertTo-Json -Depth 10 + + # Instead of calling dsc.exe we call the cmdlet directly to be able to test the output and mocks + $resourceObject = Get-DscResourceObject -jsonInput $jsonInput + $cacheEntry = Invoke-DscCacheRefresh -Module PSDesiredStateConfiguration + + $out = Invoke-DscOperation -Operation Test -DesiredState $resourceObject -dscResourceCache $cacheEntry + $LASTEXITCODE | Should -Be 0 + $out.properties.InDesiredState.InDesiredState | Should -Be $false + + Should -Invoke -CommandName ConvertTo-SecureString -Exactly -Times 1 -Scope It + } + + It 'Config does not work when credential properties are missing required fields' -Skip:(!$IsWindows) { $yaml = @" `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json resources: @@ -224,8 +263,8 @@ resources: UserName: 'User' OtherProperty: 'Password' "@ - dsc -l trace config get -i $yaml - $out = dsc -l trace config get -i $yaml 2>&1 | Out-String + # Compared to PowerShell we use test here as it filters out the properties + $out = dsc config test -i $yaml 2>&1 | Out-String $LASTEXITCODE | Should -Be 2 $out | Should -Not -BeNullOrEmpty $out | Should -BeLike "*ERROR*Credential object 'Credential' requires both 'username' and 'password' properties*" From 7b0a3cb0d0388985638fe07dc83b696c8cdcca08 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Fri, 25 Apr 2025 14:04:16 +0200 Subject: [PATCH 29/31] Forgot skip --- powershell-adapter/Tests/win_powershellgroup.tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 index b35e4e4d..71fa305a 100644 --- a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 +++ b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 @@ -213,7 +213,7 @@ resources: $out.results[0].result.inDesiredState | Should -Be $inDesiredState } - It 'Config works with credential object' { + It 'Config works with credential object' -Skip:(!$IsWindows) { BeforeAll { $script:winPSModule = Resolve-Path -Path (Join-Path $PSScriptRoot '..' 'powershell-adapter' 'psDscAdapter' 'win_psDscAdapter.psm1') | Select-Object -ExpandProperty Path Import-Module $winPSModule -Force -ErrorAction Stop From abb7f26b698ee5a9c1851041c8bb839ebdc703c9 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Fri, 25 Apr 2025 14:29:53 +0200 Subject: [PATCH 30/31] Remove leftover code from pr #720 --- powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 index f25dfb6a..3ff1c680 100644 --- a/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 @@ -74,7 +74,7 @@ function Invoke-DscCacheRefresh { $cache = Get-Content -Raw $cacheFilePath | ConvertFrom-Json if ($cache.CacheSchemaVersion -ne $script:CurrentCacheSchemaVersion) { $refreshCache = $true - "Incompartible version of cache in file '" + $cache.CacheSchemaVersion + "' (expected '" + $script:CurrentCacheSchemaVersion + "')" | Write-DscTrace + "Incompatible version of cache in file '" + $cache.CacheSchemaVersion + "' (expected '" + $script:CurrentCacheSchemaVersion + "')" | Write-DscTrace } else { $dscResourceCacheEntries = $cache.ResourceCache @@ -281,14 +281,6 @@ function Get-DscResourceObject { $inputObj = $jsonInput | ConvertFrom-Json $desiredState = [System.Collections.Generic.List[Object]]::new() - # match adapter to version of powershell - if ($PSVersionTable.PSVersion.Major -le 5) { - $adapterName = 'Microsoft.Windows/WindowsPowerShell' - } - else { - $adapterName = 'Microsoft.DSC/PowerShell' - } - # change the type from pscustomobject to dscResourceObject $inputObj.resources | ForEach-Object -Process { $desiredState += [dscResourceObject]@{ From 4041e8aa9698f372c8bf29a73a25931c1fe39701 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Sat, 26 Apr 2025 09:46:42 +0200 Subject: [PATCH 31/31] Update resolve path --- .../Tests/win_powershellgroup.tests.ps1 | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 index 71fa305a..b13b2b8b 100644 --- a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 +++ b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 @@ -17,6 +17,9 @@ Describe 'WindowsPowerShell adapter resource tests - requires elevated permissio } AfterAll { $env:PSModulePath = $OldPSModulePath + + # Remove after all the tests are done + Remove-Module $script:winPSModule -Force -ErrorAction Ignore } BeforeEach { @@ -214,18 +217,14 @@ resources: } It 'Config works with credential object' -Skip:(!$IsWindows) { - BeforeAll { - $script:winPSModule = Resolve-Path -Path (Join-Path $PSScriptRoot '..' 'powershell-adapter' 'psDscAdapter' 'win_psDscAdapter.psm1') | Select-Object -ExpandProperty Path + BeforeDiscovery { + $script:winPSModule = Resolve-Path -Path (Join-Path $PSScriptRoot '..' 'psDscAdapter' 'win_psDscAdapter.psm1') | Select-Object -ExpandProperty Path Import-Module $winPSModule -Force -ErrorAction Stop # Mock the command to work on GitHub runners because Microsoft.PowerShell.Security is not available Mock -CommandName ConvertTo-SecureString -MockWith { [System.Security.SecureString]::new() } } - AfterAll { - Remove-Module $script:winPSModule -Force -ErrorAction Ignore - } - $jsonInput = @{ resources = @{ name = 'Service info' @@ -243,7 +242,7 @@ resources: # Instead of calling dsc.exe we call the cmdlet directly to be able to test the output and mocks $resourceObject = Get-DscResourceObject -jsonInput $jsonInput $cacheEntry = Invoke-DscCacheRefresh -Module PSDesiredStateConfiguration - + $out = Invoke-DscOperation -Operation Test -DesiredState $resourceObject -dscResourceCache $cacheEntry $LASTEXITCODE | Should -Be 0 $out.properties.InDesiredState.InDesiredState | Should -Be $false