From 5cb3f0f8364ac896380ebb3ae2a5f0d6d728144f Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Tue, 29 Jul 2025 13:19:02 -0700 Subject: [PATCH 01/11] Skip winps tests if not Windows or not elevated, redirect native output to $null --- .../Tests/win_powershellgroup.tests.ps1 | 70 ++++++++++--------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 index 077170815..43d310795 100644 --- a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 +++ b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 @@ -1,27 +1,31 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -Describe 'WindowsPowerShell adapter resource tests - requires elevated permissions' { +BeforeDiscovery { + if ($IsWindows) { + $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent() + $principal = [System.Security.Principal.WindowsPrincipal]::new($identity) + $isElevated = $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) + } +} + +Describe 'WindowsPowerShell adapter resource tests - requires elevated permissions' -Skip:(!$IsWindows -or !$isElevated) { BeforeAll { - if ($isWindows) { - winrm quickconfig -quiet -force - $OldPSModulePath = $env:PSModulePath - $env:PSModulePath += [System.IO.Path]::PathSeparator + $PSScriptRoot + $null = winrm quickconfig -quiet -force 2>&1 + $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" - } + $winpsConfigPath = Join-path $PSScriptRoot "winps_resource.dsc.yaml" + if ($isWindows) { + $cacheFilePath_v5 = Join-Path $env:LocalAppData "dsc" "WindowsPSAdapterCache.json" } } AfterAll { - if ($isWindows) { - $env:PSModulePath = $OldPSModulePath + $env:PSModulePath = $OldPSModulePath - # Remove after all the tests are done - Remove-Module $script:winPSModule -Force -ErrorAction Ignore - } + # Remove after all the tests are done + Remove-Module $script:winPSModule -Force -ErrorAction Ignore } BeforeEach { @@ -30,7 +34,7 @@ Describe 'WindowsPowerShell adapter resource tests - requires elevated permissio } } - It 'Windows PowerShell adapter supports File resource' -Skip:(!$IsWindows) { + It 'Windows PowerShell adapter supports File resource' { $r = dsc resource list --adapter Microsoft.Windows/WindowsPowerShell $LASTEXITCODE | Should -Be 0 @@ -38,7 +42,7 @@ Describe 'WindowsPowerShell adapter resource tests - requires elevated permissio ($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' { $testFile = "$testdrive\test.txt" 'test' | Set-Content -Path $testFile -Force @@ -48,7 +52,7 @@ Describe 'WindowsPowerShell adapter resource tests - requires elevated permissio $res.actualState.DestinationPath | Should -Be "$testFile" } - It 'Set works on Binary "File" resource' -Skip:(!$IsWindows) { + It 'Set works on Binary "File" resource' { $testFile = "$testdrive\test.txt" $null = '{"DestinationPath":"' + $testFile.replace('\', '\\') + '", type: File, contents: HelloWorld, Ensure: present}' | dsc resource set -r 'PSDesiredStateConfiguration/File' -f - @@ -56,7 +60,7 @@ Describe 'WindowsPowerShell adapter resource tests - requires elevated permissio Get-Content -Raw -Path $testFile | Should -Be "HelloWorld" } - It 'Get works on traditional "Script" resource' -Skip:(!$IsWindows) { + It 'Get works on traditional "Script" resource' { $testFile = "$testdrive\test.txt" 'test' | Set-Content -Path $testFile -Force @@ -66,7 +70,7 @@ Describe 'WindowsPowerShell adapter resource tests - requires elevated permissio $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' { $testFile = "$testdrive\test.txt" 'test' | Set-Content -Path $testFile -Force @@ -76,7 +80,7 @@ Describe 'WindowsPowerShell adapter resource tests - requires elevated permissio $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) { + It 'Verify that there are no cache rebuilds for several sequential executions' { # remove cache file $cacheFilePath = Join-Path $env:LocalAppData "dsc\WindowsPSAdapterCache.json" Remove-Item -Force -Path $cacheFilePath -ErrorAction Ignore @@ -92,7 +96,7 @@ Describe 'WindowsPowerShell adapter resource tests - requires elevated permissio } } - It 'Verify if assertion is used that no module is cleared in the cache' -Skip:(!$IsWindows) { + It 'Verify if assertion is used that no module is cleared in the cache' { # create a test file in the test drive $testFile = "$testdrive\test.txt" New-Item -Path $testFile -ItemType File -Force | Out-Null @@ -180,7 +184,7 @@ resources: $cache.ResourceCache.Type | Should -Contain 'PSDesiredStateConfiguration/File' } - It '_inDesiredState is returned correction: ' -Skip:(!$IsWindows) -TestCases @( + It '_inDesiredState is returned correction: ' -TestCases @( @{ Context = 'Both running'; FirstState = 'Running'; SecondState = 'Running' } @{ Context = 'Both stopped'; FirstState = 'Stopped'; SecondState = 'Stopped' } @{ Context = 'First Stopped'; FirstState = 'Stopped'; SecondState = 'Running' } @@ -218,14 +222,12 @@ resources: $out.results[0].result.inDesiredState | Should -Be $inDesiredState } - It 'Config works with credential object' -Skip:(!$IsWindows) { - BeforeDiscovery { - $script:winPSModule = Resolve-Path -Path (Join-Path $PSScriptRoot '..' 'psDscAdapter' 'win_psDscAdapter.psm1') | Select-Object -ExpandProperty Path - Import-Module $winPSModule -Force -ErrorAction Stop + It 'Config works with credential object' { + $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() } - } + # Mock the command to work on GitHub runners because Microsoft.PowerShell.Security is not available + Mock -CommandName ConvertTo-SecureString -MockWith { [System.Security.SecureString]::new() } $jsonInput = @{ resources = @{ @@ -252,7 +254,7 @@ resources: Should -Invoke -CommandName ConvertTo-SecureString -Exactly -Times 1 -Scope It } - It 'Config does not work when credential properties are missing required fields' -Skip:(!$IsWindows) { + 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: @@ -271,7 +273,7 @@ resources: $out | Should -BeLike "*ERROR*Credential object 'Credential' requires both 'username' and 'password' properties*" } - It 'List works with class-based PS DSC resources' -Skip:(!$IsWindows) { + It 'List works with class-based PS DSC resources' { BeforeDiscovery { $windowsPowerShellPath = Join-Path $testDrive 'WindowsPowerShell' 'Modules' $env:PSModulePath += [System.IO.Path]::PathSeparator + $windowsPowerShellPath @@ -376,7 +378,7 @@ class PSClassResource { ($out | Where-Object -Property type -EQ 'PSClassResource/PSClassResource').capabilities | Should -BeIn @('get', 'test', 'set', 'export') } - It 'Get works with class-based PS DSC resources' -Skip:(!$IsWindows) { + It 'Get works with class-based PS DSC resources' { $out = dsc resource get -r PSClassResource/PSClassResource --input (@{Name = 'TestName' } | ConvertTo-Json) | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 @@ -385,14 +387,14 @@ class PSClassResource { $propCount.Count | Should -Be 1 # Only the DscProperty should be returned } - It 'Set works with class-based PS DSC resources' -Skip:(!$IsWindows) { + It 'Set works with class-based PS DSC resources' { $out = dsc resource set -r PSClassResource/PSClassResource --input (@{Name = 'TestName' } | ConvertTo-Json) | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 $out.afterstate.InDesiredState | Should -Be $true } - It 'Export works with class-based PS DSC resources' -Skip:(!$IsWindows) { + It 'Export works with class-based PS DSC resources' { $out = dsc resource export -r PSClassResource/PSClassResource | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 From c4ffac4c2e7e90aa2a01c3ce13f3d54cb195778e Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Tue, 29 Jul 2025 15:51:13 -0700 Subject: [PATCH 02/11] fix tests --- dsc/tests/dsc_extension_discover.tests.ps1 | 11 +++++++---- extensions/appx/appx.tests.ps1 | 2 +- reboot_pending/tests/reboot_pending.tests.ps1 | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/dsc/tests/dsc_extension_discover.tests.ps1 b/dsc/tests/dsc_extension_discover.tests.ps1 index b935ffeea..dacbb844f 100644 --- a/dsc/tests/dsc_extension_discover.tests.ps1 +++ b/dsc/tests/dsc_extension_discover.tests.ps1 @@ -15,15 +15,18 @@ Describe 'Discover extension tests' { It 'Discover extensions' { $out = dsc extension list | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 - $out.Count | Should -Be 2 -Because ($out | Out-String) + $out.Count | Should -Be 3 -Because ($out | Out-String) $out[0].type | Should -Be 'Microsoft.DSC.Extension/Bicep' $out[0].version | Should -Be '0.1.0' $out[0].capabilities | Should -BeExactly @('import') $out[0].manifest | Should -Not -BeNullOrEmpty - $out[1].type | Should -BeExactly 'Test/Discover' - $out[1].version | Should -BeExactly '0.1.0' + $out[1].type | Should -Be 'Microsoft.Windows.Appx/Discover' + $out[1].version | Should -Be '0.1.0' $out[1].capabilities | Should -BeExactly @('discover') - $out[1].manifest | Should -Not -BeNullOrEmpty + $out[2].type | Should -BeExactly 'Test/Discover' + $out[2].version | Should -BeExactly '0.1.0' + $out[2].capabilities | Should -BeExactly @('discover') + $out[2].manifest | Should -Not -BeNullOrEmpty } It 'Filtering works for extension discovered resources' { diff --git a/extensions/appx/appx.tests.ps1 b/extensions/appx/appx.tests.ps1 index 7e3f0bd51..244e54693 100644 --- a/extensions/appx/appx.tests.ps1 +++ b/extensions/appx/appx.tests.ps1 @@ -7,7 +7,7 @@ Describe 'Tests for Appx resource discovery' -Skip:(!$IsWindows){ $LASTEXITCODE | Should -Be 0 $found = $false foreach ($resource in $out) { - if ($resource.directory.StartsWith("$env:ProgramFiles\WindowsApps\Microsoft.DesiredStateConfiguration-Private")) { + if ($resource.directory.StartsWith("$env:ProgramFiles\WindowsApps")) { $found = $true break } diff --git a/reboot_pending/tests/reboot_pending.tests.ps1 b/reboot_pending/tests/reboot_pending.tests.ps1 index d6bf5a80c..a0f08b3c6 100644 --- a/reboot_pending/tests/reboot_pending.tests.ps1 +++ b/reboot_pending/tests/reboot_pending.tests.ps1 @@ -25,7 +25,7 @@ Describe 'reboot_pending resource tests' { $out | dsc resource get -r Microsoft.Windows/RebootPending | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 - $out.actualState.reason | Should -Not -BeNullOrEmpty + $out.actualState.reason.count | Should -BeGreaterThan 0 } finally { Remove-ItemProperty -Path $keyPath -Name $keyName -ErrorAction Ignore } From 2578344082f81629ce7eccedfd2d992c3518662d Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Tue, 29 Jul 2025 16:06:32 -0700 Subject: [PATCH 03/11] fix test --- dsc/tests/dsc_extension_discover.tests.ps1 | 37 +++++++++++++++------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/dsc/tests/dsc_extension_discover.tests.ps1 b/dsc/tests/dsc_extension_discover.tests.ps1 index dacbb844f..bb9480b97 100644 --- a/dsc/tests/dsc_extension_discover.tests.ps1 +++ b/dsc/tests/dsc_extension_discover.tests.ps1 @@ -15,18 +15,31 @@ Describe 'Discover extension tests' { It 'Discover extensions' { $out = dsc extension list | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 - $out.Count | Should -Be 3 -Because ($out | Out-String) - $out[0].type | Should -Be 'Microsoft.DSC.Extension/Bicep' - $out[0].version | Should -Be '0.1.0' - $out[0].capabilities | Should -BeExactly @('import') - $out[0].manifest | Should -Not -BeNullOrEmpty - $out[1].type | Should -Be 'Microsoft.Windows.Appx/Discover' - $out[1].version | Should -Be '0.1.0' - $out[1].capabilities | Should -BeExactly @('discover') - $out[2].type | Should -BeExactly 'Test/Discover' - $out[2].version | Should -BeExactly '0.1.0' - $out[2].capabilities | Should -BeExactly @('discover') - $out[2].manifest | Should -Not -BeNullOrEmpty + if ($IsWindows) { + $out.Count | Should -Be 3 -Because ($out | Out-String) + $out[0].type | Should -Be 'Microsoft.DSC.Extension/Bicep' + $out[0].version | Should -Be '0.1.0' + $out[0].capabilities | Should -BeExactly @('import') + $out[0].manifest | Should -Not -BeNullOrEmpty + $out[1].type | Should -Be 'Microsoft.Windows.Appx/Discover' + $out[1].version | Should -Be '0.1.0' + $out[1].capabilities | Should -BeExactly @('discover') + $out[1].manifest | Should -Not -BeNullOrEmpty + $out[2].type | Should -BeExactly 'Test/Discover' + $out[2].version | Should -BeExactly '0.1.0' + $out[2].capabilities | Should -BeExactly @('discover') + $out[2].manifest | Should -Not -BeNullOrEmpty + } else { + $out.Count | Should -Be 2 -Because ($out | Out-String) + $out[0].type | Should -Be 'Microsoft.DSC.Extension/Bicep' + $out[0].version | Should -Be '0.1.0' + $out[0].capabilities | Should -BeExactly @('import') + $out[0].manifest | Should -Not -BeNullOrEmpty + $out[1].type | Should -BeExactly 'Test/Discover' + $out[1].version | Should -BeExactly '0.1.0' + $out[1].capabilities | Should -BeExactly @('discover') + $out[1].manifest | Should -Not -BeNullOrEmpty + } } It 'Filtering works for extension discovered resources' { From 7862a8612bb32c04aa544d10453cba05e6b37172 Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Tue, 29 Jul 2025 17:07:55 -0700 Subject: [PATCH 04/11] fix tests --- dsc/tests/dsc_extension_discover.tests.ps1 | 12 ++++++++++-- dsc/tests/dsc_resource_list.tests.ps1 | 12 ++++++++++-- extensions/appx/appx.tests.ps1 | 6 +++++- extensions/bicep/bicep.tests.ps1 | 4 +++- reboot_pending/tests/reboot_pending.tests.ps1 | 14 +++++++++++--- 5 files changed, 39 insertions(+), 9 deletions(-) diff --git a/dsc/tests/dsc_extension_discover.tests.ps1 b/dsc/tests/dsc_extension_discover.tests.ps1 index bb9480b97..d37455465 100644 --- a/dsc/tests/dsc_extension_discover.tests.ps1 +++ b/dsc/tests/dsc_extension_discover.tests.ps1 @@ -1,6 +1,14 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. +BeforeDiscovery { + try { + $windowWidth = [Console]::WindowWidth + } catch { + $consoleUnavailable = $true + } +} + Describe 'Discover extension tests' { BeforeAll { $oldPath = $env:PATH @@ -110,12 +118,12 @@ Describe 'Discover extension tests' { } } - It 'Table can be not truncated' { + It 'Table can be not truncated' -Skip:($consoleUnavailable) { $output = dsc extension list --output-format table-no-truncate $LASTEXITCODE | Should -Be 0 $foundWideLine = $false foreach ($line in $output) { - if ($line.Length -gt [Console]::WindowWidth) { + if ($line.Length -gt $windowWidth) { $foundWideLine = $true } } diff --git a/dsc/tests/dsc_resource_list.tests.ps1 b/dsc/tests/dsc_resource_list.tests.ps1 index db90fdc45..8ee75845a 100644 --- a/dsc/tests/dsc_resource_list.tests.ps1 +++ b/dsc/tests/dsc_resource_list.tests.ps1 @@ -1,6 +1,14 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. +BeforeDiscovery { + try { + $windowWidth = [Console]::WindowWidth + } catch { + $consoleUnavailable = $true + } +} + Describe 'Tests for listing resources' { It 'dsc resource list' { $resources = dsc resource list | ConvertFrom-Json -Depth 10 @@ -89,12 +97,12 @@ Describe 'Tests for listing resources' { $out | Should -BeLike "*ERROR*Adapter not found: foo`*" } - It 'Table is not truncated' { + It 'Table is not truncated' -Skip:($consoleUnavailable) { $output = dsc resource list --output-format table-no-truncate $LASTEXITCODE | Should -Be 0 $foundWideLine = $false foreach ($line in $output) { - if ($line.Length -gt [Console]::WindowWidth) { + if ($line.Length -gt $windowWidth) { $foundWideLine = $true break } diff --git a/extensions/appx/appx.tests.ps1 b/extensions/appx/appx.tests.ps1 index 244e54693..5d505a8a3 100644 --- a/extensions/appx/appx.tests.ps1 +++ b/extensions/appx/appx.tests.ps1 @@ -1,7 +1,11 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -Describe 'Tests for Appx resource discovery' -Skip:(!$IsWindows){ +BeforeDiscovery { + $runningInCI = $null -ne $env:GITHUB_RUN_ID +} + +Describe 'Tests for Appx resource discovery' -Skip:(!$IsWindows -or $runningInCI) { It 'Should find DSC appx resources' { $out = dsc resource list | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 diff --git a/extensions/bicep/bicep.tests.ps1 b/extensions/bicep/bicep.tests.ps1 index a60e65cd7..b6ccd6d79 100644 --- a/extensions/bicep/bicep.tests.ps1 +++ b/extensions/bicep/bicep.tests.ps1 @@ -15,6 +15,7 @@ Describe 'Bicep extension tests' -Skip:(!$foundBicep) { $out = dsc -l trace config get -f $bicepFile 2>$TestDrive/error.log | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) $out.results[0].result.actualState.output | Should -BeExactly 'Hello, world!' + $bicepFile = $bicepFile.ToString().Replace('\', '\\') (Get-Content -Path $TestDrive/error.log -Raw) | Should -Match "Importing file '$bicepFile' with extension 'Microsoft.DSC.Extension/Bicep'" } @@ -28,9 +29,10 @@ resource invalid 'Microsoft.DSC.Extension/Bicep:1.0' = { properties: { output: 'This is invalid' "@ - $out = dsc -l trace config get -f $bicepFile 2>$TestDrive/error.log | ConvertFrom-Json + dsc -l trace config get -f $bicepFile 2>$TestDrive/error.log | ConvertFrom-Json $LASTEXITCODE | Should -Be 4 -Because (Get-Content -Path $TestDrive/error.log -Raw | Out-String) $content = (Get-Content -Path $TestDrive/error.log -Raw) + $bicepFile = $bicepFile.ToString().Replace('\', '\\') $content | Should -Match "Importing file '$bicepFile' with extension 'Microsoft.DSC.Extension/Bicep'" $content | Should -Match "BCP033" } diff --git a/reboot_pending/tests/reboot_pending.tests.ps1 b/reboot_pending/tests/reboot_pending.tests.ps1 index a0f08b3c6..df9897e15 100644 --- a/reboot_pending/tests/reboot_pending.tests.ps1 +++ b/reboot_pending/tests/reboot_pending.tests.ps1 @@ -1,6 +1,14 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. +BeforeDiscovery { + if ($IsWindows) { + $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent() + $principal = [System.Security.Principal.WindowsPrincipal]::new($identity) + $isElevated = $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) + } +} + Describe 'reboot_pending resource tests' { It 'should get reboot_pending' -Skip:(!$IsWindows) { $out = dsc resource get -r Microsoft.Windows/RebootPending | ConvertFrom-Json @@ -15,7 +23,7 @@ Describe 'reboot_pending resource tests' { $out.results.result.actualState.rebootPending | Should -Not -BeNullOrEmpty } - It 'reboot_pending should have a reason' -Skip:(!$IsWindows) { + It 'reboot_pending should have a reason' -Skip:(!$IsWindows -or !$isElevated) { $keyPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update" $keyName = "RebootRequired" try { @@ -23,9 +31,9 @@ Describe 'reboot_pending resource tests' { New-ItemProperty -Path $keyPath -Name $keyName -Value 1 -PropertyType DWord -Force | Out-Null } - $out | dsc resource get -r Microsoft.Windows/RebootPending | ConvertFrom-Json + $out = dsc resource get -r Microsoft.Windows/RebootPending | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 - $out.actualState.reason.count | Should -BeGreaterThan 0 + $out.actualState.reason.count | Should -BeGreaterThan 0 -Because ($out | Out-String) } finally { Remove-ItemProperty -Path $keyPath -Name $keyName -ErrorAction Ignore } From fb583abce005d79422c6e8cd97bd22e226b618f2 Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Tue, 29 Jul 2025 18:59:17 -0700 Subject: [PATCH 05/11] fix reboot pending test --- reboot_pending/tests/reboot_pending.tests.ps1 | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/reboot_pending/tests/reboot_pending.tests.ps1 b/reboot_pending/tests/reboot_pending.tests.ps1 index df9897e15..97cb307bf 100644 --- a/reboot_pending/tests/reboot_pending.tests.ps1 +++ b/reboot_pending/tests/reboot_pending.tests.ps1 @@ -9,33 +9,35 @@ BeforeDiscovery { } } -Describe 'reboot_pending resource tests' { - It 'should get reboot_pending' -Skip:(!$IsWindows) { +Describe 'reboot_pending resource tests' -Skip:(!$IsWindows -or !$isElevated) { + BeforeAll { + $keyPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update" + $keyName = "RebootRequired" + if (-not (Get-ItemProperty "$keyPath\$keyName" -ErrorAction SilentlyContinue)) { + New-ItemProperty -Path $keyPath -Name $keyName -Value 1 -PropertyType DWord -Force | Out-Null + } + } + + AfterAll { + Remove-ItemProperty -Path $keyPath -Name $keyName -ErrorAction Ignore + } + + It 'should get reboot_pending' { $out = dsc resource get -r Microsoft.Windows/RebootPending | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 $out.actualState.rebootPending | Should -Not -BeNullOrEmpty } - It 'reboot_pending works in a config' -Skip:(!$IsWindows) { + It 'reboot_pending works in a config' { $ConfigPath = Resolve-Path "$PSScriptRoot/reboot_pending.dsc.yaml" $out = dsc config get --file $ConfigPath | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 $out.results.result.actualState.rebootPending | Should -Not -BeNullOrEmpty } - It 'reboot_pending should have a reason' -Skip:(!$IsWindows -or !$isElevated) { - $keyPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update" - $keyName = "RebootRequired" - try { - if (-not (Get-ItemProperty "$keyPath\$keyName" -ErrorAction SilentlyContinue)) { - New-ItemProperty -Path $keyPath -Name $keyName -Value 1 -PropertyType DWord -Force | Out-Null - } - - $out = dsc resource get -r Microsoft.Windows/RebootPending | ConvertFrom-Json - $LASTEXITCODE | Should -Be 0 - $out.actualState.reason.count | Should -BeGreaterThan 0 -Because ($out | Out-String) - } finally { - Remove-ItemProperty -Path $keyPath -Name $keyName -ErrorAction Ignore - } + It 'reboot_pending should have a reason' { + $out = dsc resource get -r Microsoft.Windows/RebootPending | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out.actualState.reason.count | Should -BeGreaterThan 0 -Because ($out | Out-String) } } From f04a2561f6e588770d877e9bf5e0ad1c5bab0461 Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Mon, 4 Aug 2025 17:19:04 -0700 Subject: [PATCH 06/11] fix tets --- build.ps1 | 11 +- dsc_lib/locales/en-us.toml | 1 + dsc_lib/src/discovery/command_discovery.rs | 4 +- dsc_lib/src/dscresources/command_resource.rs | 3 + .../Tests/powershellgroup.config.tests.ps1 | 3 +- .../Tests/powershellgroup.resource.tests.ps1 | 5 +- .../Tests/win_powershell_cache.tests.ps1 | 183 ++++++++ .../Tests/win_powershellgroup.tests.ps1 | 409 +++++------------- .../psDscAdapter/powershell.resource.ps1 | 4 + .../psDscAdapter/win_psDscAdapter.psm1 | 15 +- reboot_pending/tests/reboot_pending.tests.ps1 | 2 +- 11 files changed, 336 insertions(+), 304 deletions(-) create mode 100644 powershell-adapter/Tests/win_powershell_cache.tests.ps1 diff --git a/build.ps1 b/build.ps1 index 0afe46305..5cb6b3922 100755 --- a/build.ps1 +++ b/build.ps1 @@ -26,6 +26,11 @@ param( $env:RUSTC_LOG=$null $env:RUSTFLAGS='-Dwarnings' +trap { + Write-Error "An error occurred: $($_ | Out-String)" + exit 1 +} + if ($Verbose) { $env:RUSTC_LOG='rustc_codegen_ssa::back::link=info' } @@ -569,11 +574,7 @@ if ($Test) { (Get-Module -Name Pester -ListAvailable).Path } - try { - Invoke-Pester -ErrorAction Stop - } catch { - throw "Pester had unexpected error: $($_.Exception.Message)" - } + Invoke-Pester -Output Detailed -ErrorAction Stop } function Find-MakeAppx() { diff --git a/dsc_lib/locales/en-us.toml b/dsc_lib/locales/en-us.toml index 956b74047..6a757c2ee 100644 --- a/dsc_lib/locales/en-us.toml +++ b/dsc_lib/locales/en-us.toml @@ -135,6 +135,7 @@ processChildExit = "Process '%{executable}' id %{id} exited with code %{code}" processChildTerminated = "Process '%{executable}' id %{id} terminated by signal" processTerminated = "Process terminated by signal" commandInvoke = "Invoking command '%{executable}' with args %{args}" +commandCwd = "Current working directory: %{cwd}" noArgs = "No args to process" parseAsEnvVars = "Parsing input as environment variables" parseAsStdin = "Parsing input as stdin" diff --git a/dsc_lib/src/discovery/command_discovery.rs b/dsc_lib/src/discovery/command_discovery.rs index 51ec01069..83d3f2ff4 100644 --- a/dsc_lib/src/discovery/command_discovery.rs +++ b/dsc_lib/src/discovery/command_discovery.rs @@ -200,8 +200,8 @@ impl ResourceDiscovery for CommandDiscovery { fn discover(&mut self, kind: &DiscoveryKind, filter: &str) -> Result<(), DscError> { info!("{}", t!("discovery.commandDiscovery.discoverResources", kind = kind : {:?}, filter = filter)); - // if kind is DscResource, we need to discover extensions first - if *kind == DiscoveryKind::Resource { + // if kind is DscResource and DSC_RESOURCE_PATH is not defined, we need to discover extensions first + if *kind == DiscoveryKind::Resource && env::var("DSC_RESOURCE_PATH").is_err() { self.discover(&DiscoveryKind::Extension, "*")?; } diff --git a/dsc_lib/src/dscresources/command_resource.rs b/dsc_lib/src/dscresources/command_resource.rs index 41b69e15b..cf4d57156 100644 --- a/dsc_lib/src/dscresources/command_resource.rs +++ b/dsc_lib/src/dscresources/command_resource.rs @@ -697,6 +697,9 @@ async fn run_process_async(executable: &str, args: Option>, input: O #[allow(clippy::implicit_hasher)] pub fn invoke_command(executable: &str, args: Option>, input: Option<&str>, cwd: Option<&str>, env: Option>, exit_codes: Option<&HashMap>) -> Result<(i32, String, String), DscError> { debug!("{}", t!("dscresources.commandResource.commandInvoke", executable = executable, args = args : {:?})); + if let Some(cwd) = cwd { + debug!("{}", t!("dscresources.commandResource.commandCwd", cwd = cwd)); + } tokio::runtime::Builder::new_multi_thread() .enable_all() diff --git a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 index 568789549..cc01c4460 100644 --- a/powershell-adapter/Tests/powershellgroup.config.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.config.tests.ps1 @@ -15,12 +15,13 @@ Describe 'PowerShell adapter resource tests' { $cacheFilePath = Join-Path $env:LocalAppData "dsc" "PSAdapterCache.json" } } + AfterAll { $env:PSModulePath = $OldPSModulePath } BeforeEach { - Remove-Item -Force -ea SilentlyContinue -Path $cacheFilePath + Remove-Item -Force -ErrorAction Ignore -Path $cacheFilePath } It 'Get works on config with class-based resources' { diff --git a/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 b/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 index ffe97c7e3..da0d20f0c 100644 --- a/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 +++ b/powershell-adapter/Tests/powershellgroup.resource.tests.ps1 @@ -14,12 +14,13 @@ Describe 'PowerShell adapter resource tests' { $cacheFilePath = Join-Path $env:LocalAppData "dsc" "PSAdapterCache.json" } } + AfterAll { $env:PSModulePath = $OldPSModulePath } BeforeEach { - Remove-Item -Force -ea SilentlyContinue -Path $cacheFilePath + Remove-Item -Force -ErrorAction Ignore -Path $cacheFilePath } It 'Discovery includes class-based resources' { @@ -173,7 +174,7 @@ Describe 'PowerShell adapter resource tests' { # remove the module files Remove-Item -Recurse -Force -Path "$TestDrive/TestClassResource" # verify that cache rebuid happened - dsc -l trace resource list '*' -a Microsoft.DSC/PowerShell 2> $TestDrive/tracing.txt + $null = dsc -l trace resource list '*' -a Microsoft.DSC/PowerShell 2> $TestDrive/tracing.txt $LASTEXITCODE | Should -Be 0 "$TestDrive/tracing.txt" | Should -FileContentMatchExactly 'Detected non-existent cache entry' diff --git a/powershell-adapter/Tests/win_powershell_cache.tests.ps1 b/powershell-adapter/Tests/win_powershell_cache.tests.ps1 new file mode 100644 index 000000000..8f2ca4b58 --- /dev/null +++ b/powershell-adapter/Tests/win_powershell_cache.tests.ps1 @@ -0,0 +1,183 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +BeforeDiscovery { + if ($IsWindows) { + $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent() + $principal = [System.Security.Principal.WindowsPrincipal]::new($identity) + $isElevated = $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) + } +} + +Describe 'WindowsPowerShell adapter resource tests - requires elevated permissions' -Skip:(!$IsWindows -or !$isElevated) { + + BeforeAll { + $OldPSModulePath = $env:PSModulePath + $dscHome = Split-Path (Get-Command dsc -ErrorAction Stop).Source -Parent + $psexeHome = Split-Path (Get-Command powershell -ErrorAction Stop).Source -Parent + $ps7exeHome = Split-Path (Get-Command pwsh -ErrorAction Stop).Source -Parent + $env:DSC_RESOURCE_PATH = $dscHome + [System.IO.Path]::PathSeparator + $psexeHome + [System.IO.Path]::PathSeparator + $ps7exeHome + $null = winrm quickconfig -quiet -force 2>&1 + $env:PSModulePath = $PSScriptRoot + [System.IO.Path]::PathSeparator + $env:PSModulePath + + $winpsConfigPath = Join-path $PSScriptRoot "winps_resource.dsc.yaml" + $cacheFilePath_v5 = Join-Path $env:LocalAppData "dsc" "WindowsPSAdapterCache.json" + + $script:winPSModule = Resolve-Path -Path (Join-Path $PSScriptRoot '..' 'psDscAdapter' 'win_psDscAdapter.psm1') | Select-Object -ExpandProperty Path + Import-Module $winPSModule -Force -ErrorAction Stop + } + + AfterAll { + $env:PSModulePath = $OldPSModulePath + $env:DSC_RESOURCE_PATH = $null + + # Remove after all the tests are done + Remove-Module $script:winPSModule -Force -ErrorAction Ignore + } + + BeforeEach { + Remove-Item -Force -ea SilentlyContinue -Path $cacheFilePath_v5 + } + + It 'Windows PowerShell adapter supports File resource' { + + $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' { + + $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' { + + $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' { + + $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' { + + $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' { + # first execution should build the cache + $null = 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 { + $null = 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' { + # create a test file in the test drive + $testFile = "$testdrive\test.txt" + New-Item -Path $testFile -ItemType File -Force | 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 + + $manifestContent = @" + @{ + RootModule = 'TestModule.psm1' + ModuleVersion = '1.0.0' + GUID = '$([guid]::NewGuid().Guid)' + Author = 'Microsoft Corporation' + CompanyName = 'Microsoft Corporation' + Copyright = '(c) Microsoft Corporation. All rights reserved.' + Description = 'Test module for DSC tests' + PowerShellVersion = '5.1' + DscResourcesToExport = @() + FunctionsToExport = @() + CmdletsToExport = @() + VariablesToExport = @() + AliasesToExport = @() + } +"@ + Set-Content -Path "$testModuleDir\TestModule.psd1" -Value $manifestContent + + $scriptContent = @" +Write-Host 'The DSC world!' +"@ + Set-Content -Path "$testModuleDir\TestModule.psm1" -Value $scriptContent + + # Add the test module directory to PSModulePath + $env:PSModulePath = $testdrive + [System.IO.Path]::PathSeparator + $env:PSModulePath + + $yaml = @" +`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: + - name: File + type: Microsoft.Windows/WindowsPowerShell + properties: + resources: + - name: File + type: PSDesiredStateConfiguration/File + properties: + DestinationPath: $testfile + - name: File present + type: Microsoft.DSC/Assertion + properties: + `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + resources: + - name: Use powershell adapter + type: Microsoft.Windows/WindowsPowerShell + properties: + resources: + - name: File present + type: PSDesiredStateConfiguration/File + properties: + DestinationPath: $testFile + dependsOn: + - "[resourceId('Microsoft.Windows/WindowsPowerShell', 'File')]" + - name: TestPSRepository + type: PSTestModule/TestPSRepository + properties: + Name: NuGet + dependsOn: + - "[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_v5 -Raw | ConvertFrom-Json + $cache.ResourceCache.Type | Should -Contain 'PSTestModule/TestPSRepository' + $cache.ResourceCache.Type | Should -Contain 'PSDesiredStateConfiguration/File' + } +} diff --git a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 index 43d310795..26cb02cd2 100644 --- a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 +++ b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 @@ -12,273 +12,13 @@ BeforeDiscovery { Describe 'WindowsPowerShell adapter resource tests - requires elevated permissions' -Skip:(!$IsWindows -or !$isElevated) { BeforeAll { - $null = winrm quickconfig -quiet -force 2>&1 $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 - - # Remove after all the tests are done - Remove-Module $script:winPSModule -Force -ErrorAction Ignore - } - - BeforeEach { - if ($isWindows) { - Remove-Item -Force -ea SilentlyContinue -Path $cacheFilePath_v5 - } - } - - It 'Windows PowerShell adapter supports File resource' { - - $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' { - - $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' { - - $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' { - - $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' { - - $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' { - # 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' - - # 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' { - # 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 - - # 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 - - $manifestContent = @" - @{ - RootModule = 'TestModule.psm1' - ModuleVersion = '1.0.0' - GUID = $([guid]::NewGuid().Guid) - Author = 'Microsoft Corporation' - CompanyName = 'Microsoft Corporation' - Copyright = '(c) Microsoft Corporation. All rights reserved.' - Description = 'Test module for DSC tests' - PowerShellVersion = '5.1' - DscResourcesToExport = @() - FunctionsToExport = @() - CmdletsToExport = @() - VariablesToExport = @() - AliasesToExport = @() - } -"@ - Set-Content -Path "$testModuleDir\TestModule.psd1" -Value $manifestContent - - $scriptContent = @" -Write-Host 'The DSC world!' -"@ - Set-Content -Path "$testModuleDir\TestModule.psm1" -Value $scriptContent - - # Add the test module directory to PSModulePath - $env:PSModulePath += [System.IO.Path]::PathSeparator + $testdrive - - $yaml = @" -`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json -resources: - - name: File - type: Microsoft.Windows/WindowsPowerShell - properties: - resources: - - name: File - type: PSDesiredStateConfiguration/File - properties: - DestinationPath: $testfile - - name: File present - type: Microsoft.DSC/Assertion - properties: - `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json - resources: - - name: Use powershell adapter - type: Microsoft.Windows/WindowsPowerShell - properties: - resources: - - name: File present - type: PSDesiredStateConfiguration/File - properties: - DestinationPath: $testFile - dependsOn: - - "[resourceId('Microsoft.Windows/WindowsPowerShell', 'File')]" - - name: TestPSRepository - type: PSTestModule/TestPSRepository - properties: - Name: NuGet - dependsOn: - - "[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' - } - - It '_inDesiredState is returned correction: ' -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 - type: Microsoft.Windows/WindowsPowerShell - properties: - resources: - - name: Check Spooler service 1 - type: PsDesiredStateConfiguration/Service - properties: - Name: Spooler - State: $FirstState - - name: Check Spooler service 2 - type: PsDesiredStateConfiguration/Service - properties: - Name: Spooler - State: $SecondState -"@ - - $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 - } - - It 'Config works with credential object' { - $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() } - - $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' { - $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' -"@ - # 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*" - } - - It 'List works with class-based PS DSC resources' { - BeforeDiscovery { - $windowsPowerShellPath = Join-Path $testDrive 'WindowsPowerShell' 'Modules' - $env:PSModulePath += [System.IO.Path]::PathSeparator + $windowsPowerShellPath - - $moduleFile = @" + $dscHome = Split-Path (Get-Command dsc -ErrorAction Stop).Source -Parent + $psexeHome = Split-Path (Get-Command powershell -ErrorAction Stop).Source -Parent + $ps7exeHome = Split-Path (Get-Command pwsh -ErrorAction Stop).Source -Parent + $env:DSC_RESOURCE_PATH = $dscHome + [System.IO.Path]::PathSeparator + $psexeHome + [System.IO.Path]::PathSeparator + $ps7exeHome + $windowsPowerShellPath = Join-Path $testDrive 'WindowsPowerShell' 'Modules' + $moduleFile = @" @{ RootModule = 'PSClassResource.psm1' ModuleVersion = '0.1.0' @@ -306,13 +46,12 @@ resources: } } "@ - $moduleFilePath = Join-Path $windowsPowerShellPath 'PSClassResource' '0.1.0' 'PSClassResource.psd1' - if (-not (Test-Path -Path $moduleFilePath)) { - New-Item -Path $moduleFilePath -ItemType File -Value $moduleFile -Force | Out-Null - } - + $moduleFilePath = Join-Path $windowsPowerShellPath 'PSClassResource' '0.1.0' 'PSClassResource.psd1' + if (-not (Test-Path -Path $moduleFilePath)) { + New-Item -Path $moduleFilePath -ItemType File -Value $moduleFile -Force | Out-Null + } - $module = @' + $module = @' enum Ensure { Present Absent @@ -331,6 +70,9 @@ class PSClassResource { [DscProperty()] [Ensure] $Ensure = [Ensure]::Present + [DscProperty()] + [PSCredential] $Credential + PSClassResource() { } @@ -343,7 +85,13 @@ class PSClassResource { } [void] Set() { + if ($null -eq $this.Credential) { + throw 'Credential property is required' + } + if ($this.Credential.UserName -ne 'MyUser') { + throw 'Invalid user name' + } } static [PSClassResource[]] Export() @@ -365,39 +113,120 @@ class PSClassResource { } '@ - $modulePath = Join-Path $windowsPowerShellPath 'PSClassResource' '0.1.0' 'PSClassResource.psm1' - if (-not (Test-Path -Path $modulePath)) { - New-Item -Path $modulePath -ItemType File -Value $module -Force | Out-Null - } + $modulePath = Join-Path $windowsPowerShellPath 'PSClassResource' '0.1.0' 'PSClassResource.psm1' + if (-not (Test-Path -Path $modulePath)) { + New-Item -Path $modulePath -ItemType File -Value $module -Force | Out-Null + } + + $env:PSModulePath = $windowsPowerShellPath + [System.IO.Path]::PathSeparator + $env:PSModulePath + } + + AfterAll { + $env:PSModulePath = $OldPSModulePath + $env:DSC_RESOURCE_PATH = $null + } + + It '_inDesiredState is returned correction: ' -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 + type: Microsoft.Windows/WindowsPowerShell + properties: + resources: + - name: Check Spooler service 1 + type: PsDesiredStateConfiguration/Service + properties: + Name: Spooler + State: $FirstState + - name: Check Spooler service 2 + type: PsDesiredStateConfiguration/Service + properties: + Name: Spooler + State: $SecondState +"@ + + $inDesiredState = if ($FirstState -eq $SecondState) { + $FirstState -eq (Get-Service Spooler).Status } + else { + $false + } + + $out = dsc -l trace config test -i $yaml 2>"$testdrive/error.log" | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path "$testdrive/error.log" -Raw | Out-String) + $out.results[0].result.inDesiredState | Should -Be $inDesiredState + } - $out = dsc -l trace resource list --adapter Microsoft.Windows/WindowsPowerShell | ConvertFrom-Json - $LASTEXITCODE | Should -Be 0 - $out.type | Should -Contain 'PSClassResource/PSClassResource' - $out | Where-Object -Property type -EQ PSClassResource/PSClassResource | Select-Object -ExpandProperty implementedAs | Should -Be 1 # Class-based + It 'Config works with credential object' { + $yaml = @' + $schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + resources: + - name: Cred test + type: PSClassResource/PSClassResource + properties: + Name: Test + Credential: + UserName: 'MyUser' + Password: 'MyPassword' +'@ + + $out = dsc -l debug config set -i $yaml 2> "$testdrive/error.log" | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path "$testdrive/error.log" -Raw | Out-String) + $out.results[0].result.afterState.Credential | Should -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: Service info + type: PsDesiredStateConfiguration/Service + properties: + Name: Spooler + Credential: + UserName: 'User' + OtherProperty: 'Password' +'@ + # Compared to PowerShell we use test here as it filters out the properties + $out = dsc -l debug config test -i $yaml 2> "$testdrive/error.log" | Out-String + $LASTEXITCODE | Should -Be 2 + $out | Should -BeNullOrEmpty + (Get-Content -Path "$testdrive/error.log" -Raw) | Should -BeLike "*ERROR*Credential object 'Credential' requires both 'username' and 'password' properties*" -Because (Get-Content -Path "$testdrive/error.log" -Raw | Out-String) + } + + It 'List works with class-based PS DSC resources' { + $out = dsc resource list --adapter Microsoft.Windows/WindowsPowerShell 2> "$testdrive/error.log" | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path "$testdrive/error.log" -Raw | Out-String) + $out.type | Should -Contain 'PSClassResource/PSClassResource' -Because ($out.type | Out-String) + $out | Where-Object -Property type -EQ PSClassResource/PSClassResource | Select-Object -ExpandProperty implementedAs | Should -Be 1 ($out | Where-Object -Property type -EQ 'PSClassResource/PSClassResource').capabilities | Should -BeIn @('get', 'test', 'set', 'export') } It 'Get works with class-based PS DSC resources' { - - $out = dsc resource get -r PSClassResource/PSClassResource --input (@{Name = 'TestName' } | ConvertTo-Json) | ConvertFrom-Json - $LASTEXITCODE | Should -Be 0 + $out = dsc resource get -r PSClassResource/PSClassResource --input (@{Name = 'TestName' } 2> "$testdrive/error.log" | ConvertTo-Json) | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path "$testdrive/error.log" -Raw | Out-String) $out.actualState.Name | Should -Be 'TestName' + $out.actualState.Ensure | Should -Be 'Present' $propCount = $out.actualState | Get-Member -MemberType NoteProperty - $propCount.Count | Should -Be 1 # Only the DscProperty should be returned + $propCount.Count | Should -Be 3 -Because ($out | Out-String) } It 'Set works with class-based PS DSC resources' { - - $out = dsc resource set -r PSClassResource/PSClassResource --input (@{Name = 'TestName' } | ConvertTo-Json) | ConvertFrom-Json - $LASTEXITCODE | Should -Be 0 + $out = dsc resource set -r PSClassResource/PSClassResource --input (@{Name = 'TestName' } 2> "$testdrive/error.log" | ConvertTo-Json) | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path "$testdrive/error.log" -Raw | Out-String) $out.afterstate.InDesiredState | Should -Be $true } It 'Export works with class-based PS DSC resources' { - - $out = dsc resource export -r PSClassResource/PSClassResource | ConvertFrom-Json - $LASTEXITCODE | Should -Be 0 + $out = dsc resource export -r PSClassResource/PSClassResource 2> "$testdrive/error.log" | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path "$testdrive/error.log" -Raw | Out-String) $out | Should -Not -BeNullOrEmpty $out.resources.count | Should -Be 5 $out.resources[0].properties.Ensure | Should -Be 'Present' # Check for enum property diff --git a/powershell-adapter/psDscAdapter/powershell.resource.ps1 b/powershell-adapter/psDscAdapter/powershell.resource.ps1 index ccee164ae..73717a900 100644 --- a/powershell-adapter/psDscAdapter/powershell.resource.ps1 +++ b/powershell-adapter/psDscAdapter/powershell.resource.ps1 @@ -22,6 +22,10 @@ function Write-DscTrace { $host.ui.WriteErrorLine($trace) } +trap { + Write-DscTrace -Operation Debug -Message ($_ | Format-List -Force | Out-String) +} + # Adding some debug info to STDERR 'PSVersion=' + $PSVersionTable.PSVersion.ToString() | Write-DscTrace 'PSPath=' + $PSHome | Write-DscTrace diff --git a/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 b/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 index ba302ac87..47ea29bee 100644 --- a/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 +++ b/powershell-adapter/psDscAdapter/win_psDscAdapter.psm1 @@ -4,6 +4,10 @@ $global:ProgressPreference = 'SilentlyContinue' $script:CurrentCacheSchemaVersion = 1 +trap { + Write-DscTrace -Operation Debug -Message ($_ | Format-List -Force | Out-String) +} + function Write-DscTrace { param( [Parameter(Mandatory = $false)] @@ -133,11 +137,14 @@ function Invoke-DscCacheRefresh { # improve by performance by having the option to only get details for named modules # workaround for File and SignatureValidation resources that ship in Windows + Write-DscTrace -Operation Debug "Named module count: $($namedModules.Count)" if ($namedModules.Count -gt 0) { + Write-DscTrace -Operation Debug "Modules specified, getting DSC resources from modules: $($namedModules -join ', ')" $DscResources = [System.Collections.Generic.List[Object]]::new() $Modules = [System.Collections.Generic.List[Object]]::new() $filteredResources = @() foreach ($m in $namedModules) { + Write-DscTrace -Operation Debug "Getting DSC resources for module '$($m | Out-String)'" $DscResources.AddRange(@(Get-DscResource -Module $m)) $Modules.AddRange(@(Get-Module -Name $m -ListAvailable)) } @@ -156,7 +163,7 @@ function Invoke-DscCacheRefresh { # Exclude the one module that was passed in as a parameter $existingDscResourceCacheEntries = @($cache.ResourceCache | Where-Object -Property Type -NotIn $filteredResources) } else { - # if no module is specified, get all resources + Write-DscTrace -Operation Debug "No modules specified, getting all DSC resources" $DscResources = Get-DscResource $Modules = Get-Module -ListAvailable } @@ -352,7 +359,7 @@ 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 - Write-DscTrace -Operation Debug -Message ($validateProperty | Out-String) + Write-DscTrace -Operation Debug -Message "Property type: $($validateProperty.PropertyType)" if ($validateProperty -and $validateProperty.PropertyType -eq '[PSCredential]') { if (-not $_.Value.Username -or -not $_.Value.Password) { "Credential object '$($_.Name)' requires both 'username' and 'password' properties" | Write-DscTrace -Operation Error @@ -406,7 +413,8 @@ 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]') { + Write-DscTrace -Operation Debug -Message "Property type: $($validateProperty.PropertyType)" + if ($validateProperty.PropertyType -eq 'PSCredential') { if (-not $_.Value.Username -or -not $_.Value.Password) { "Credential object '$($_.Name)' requires both 'username' and 'password' properties" | Write-DscTrace -Operation Error exit 1 @@ -483,6 +491,7 @@ 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 + Write-DscTrace -Operation Debug -Message "Property type: $($validateProperty.PropertyType)" if ($validateProperty.PropertyType -eq '[PSCredential]') { if (-not $_.Value.Username -or -not $_.Value.Password) { "Credential object '$($_.Name)' requires both 'username' and 'password' properties" | Write-DscTrace -Operation Error diff --git a/reboot_pending/tests/reboot_pending.tests.ps1 b/reboot_pending/tests/reboot_pending.tests.ps1 index 97cb307bf..dd4e34a68 100644 --- a/reboot_pending/tests/reboot_pending.tests.ps1 +++ b/reboot_pending/tests/reboot_pending.tests.ps1 @@ -13,7 +13,7 @@ Describe 'reboot_pending resource tests' -Skip:(!$IsWindows -or !$isElevated) { BeforeAll { $keyPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update" $keyName = "RebootRequired" - if (-not (Get-ItemProperty "$keyPath\$keyName" -ErrorAction SilentlyContinue)) { + if (-not (Get-ItemProperty "$keyPath\$keyName" -ErrorAction Ignore)) { New-ItemProperty -Path $keyPath -Name $keyName -Value 1 -PropertyType DWord -Force | Out-Null } } From db9530e6ffa0c0329f95500826159d7588d49bbd Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Mon, 4 Aug 2025 18:20:40 -0700 Subject: [PATCH 07/11] also insert adapters into resource list --- dsc_lib/src/discovery/command_discovery.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/dsc_lib/src/discovery/command_discovery.rs b/dsc_lib/src/discovery/command_discovery.rs index 83d3f2ff4..debe120e0 100644 --- a/dsc_lib/src/discovery/command_discovery.rs +++ b/dsc_lib/src/discovery/command_discovery.rs @@ -201,7 +201,7 @@ impl ResourceDiscovery for CommandDiscovery { info!("{}", t!("discovery.commandDiscovery.discoverResources", kind = kind : {:?}, filter = filter)); // if kind is DscResource and DSC_RESOURCE_PATH is not defined, we need to discover extensions first - if *kind == DiscoveryKind::Resource && env::var("DSC_RESOURCE_PATH").is_err() { + if *kind == DiscoveryKind::Resource { self.discover(&DiscoveryKind::Extension, "*")?; } @@ -286,10 +286,9 @@ impl ResourceDiscovery for CommandDiscovery { if manifest.kind == Some(Kind::Adapter) { trace!("{}", t!("discovery.commandDiscovery.adapterFound", adapter = resource.type_name)); insert_resource(&mut adapters, &resource, true); - } else { - trace!("{}", t!("discovery.commandDiscovery.resourceFound", resource = resource.type_name)); - insert_resource(&mut resources, &resource, true); } + trace!("{}", t!("discovery.commandDiscovery.resourceFound", resource = resource.type_name)); + insert_resource(&mut resources, &resource, true); } } } @@ -563,11 +562,9 @@ impl ResourceDiscovery for CommandDiscovery { // TODO: This should be a BTreeMap of the resource name and a BTreeMap of the version and DscResource, this keeps it version sorted more efficiently fn insert_resource(resources: &mut BTreeMap>, resource: &DscResource, skip_duplicate_version: bool) { - if resources.contains_key(&resource.type_name) { - let Some(resource_versions) = resources.get_mut(&resource.type_name) else { - resources.insert(resource.type_name.clone(), vec![resource.clone()]); - return; - }; + debug!("Inserting resource: {} version {} from {}", resource.type_name, resource.version, resource.directory); + if let Some(resource_versions) = resources.get_mut(&resource.type_name) { + debug!("Resource '{}' already exists, checking versions", resource.type_name); // compare the resource versions and insert newest to oldest using semver let mut insert_index = resource_versions.len(); for (index, resource_instance) in resource_versions.iter().enumerate() { From ce3735898f97364e2ad18137a07d27b8586d7a4d Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Mon, 4 Aug 2025 19:14:11 -0700 Subject: [PATCH 08/11] fix tests --- powershell-adapter/Tests/win_powershellgroup.tests.ps1 | 1 - reboot_pending/tests/reboot_pending.tests.ps1 | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 index 26cb02cd2..7f99b136b 100644 --- a/powershell-adapter/Tests/win_powershellgroup.tests.ps1 +++ b/powershell-adapter/Tests/win_powershellgroup.tests.ps1 @@ -179,7 +179,6 @@ resources: $out = dsc -l debug config set -i $yaml 2> "$testdrive/error.log" | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Path "$testdrive/error.log" -Raw | Out-String) - $out.results[0].result.afterState.Credential | Should -BeNullOrEmpty } It 'Config does not work when credential properties are missing required fields' { diff --git a/reboot_pending/tests/reboot_pending.tests.ps1 b/reboot_pending/tests/reboot_pending.tests.ps1 index dd4e34a68..a86fcae43 100644 --- a/reboot_pending/tests/reboot_pending.tests.ps1 +++ b/reboot_pending/tests/reboot_pending.tests.ps1 @@ -38,6 +38,6 @@ Describe 'reboot_pending resource tests' -Skip:(!$IsWindows -or !$isElevated) { It 'reboot_pending should have a reason' { $out = dsc resource get -r Microsoft.Windows/RebootPending | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 - $out.actualState.reason.count | Should -BeGreaterThan 0 -Because ($out | Out-String) + $out.actualState.reason.count | Should -BeGreaterThan 0 -Because ($out | ConvertTo-Json -Depth 10 |Out-String) } } From ffa98433365295930c4c94059635ccdc5bb671df Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Mon, 4 Aug 2025 19:58:19 -0700 Subject: [PATCH 09/11] fix case where no reboot is pending --- reboot_pending/tests/reboot_pending.tests.ps1 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/reboot_pending/tests/reboot_pending.tests.ps1 b/reboot_pending/tests/reboot_pending.tests.ps1 index a86fcae43..81658ac14 100644 --- a/reboot_pending/tests/reboot_pending.tests.ps1 +++ b/reboot_pending/tests/reboot_pending.tests.ps1 @@ -38,6 +38,10 @@ Describe 'reboot_pending resource tests' -Skip:(!$IsWindows -or !$isElevated) { It 'reboot_pending should have a reason' { $out = dsc resource get -r Microsoft.Windows/RebootPending | ConvertFrom-Json $LASTEXITCODE | Should -Be 0 - $out.actualState.reason.count | Should -BeGreaterThan 0 -Because ($out | ConvertTo-Json -Depth 10 |Out-String) + if ($out.actualState.rebootPending) { + $out.actualState.reason.count | Should -BeGreaterThan 0 -Because ($out | ConvertTo-Json -Depth 10 |Out-String) + } else { + $out.actualState.reason | Should -BeNullOrEmpty -Because ($out | ConvertTo-Json -Depth 10 |Out-String) + } } } From 831b1dd79d407b8fb4e4640b0bccabdd5f92a71f Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Mon, 4 Aug 2025 20:03:03 -0700 Subject: [PATCH 10/11] fix comments --- dsc_lib/src/discovery/command_discovery.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/dsc_lib/src/discovery/command_discovery.rs b/dsc_lib/src/discovery/command_discovery.rs index debe120e0..d838a3f08 100644 --- a/dsc_lib/src/discovery/command_discovery.rs +++ b/dsc_lib/src/discovery/command_discovery.rs @@ -200,7 +200,7 @@ impl ResourceDiscovery for CommandDiscovery { fn discover(&mut self, kind: &DiscoveryKind, filter: &str) -> Result<(), DscError> { info!("{}", t!("discovery.commandDiscovery.discoverResources", kind = kind : {:?}, filter = filter)); - // if kind is DscResource and DSC_RESOURCE_PATH is not defined, we need to discover extensions first + // if kind is DscResource, we need to discover extensions first if *kind == DiscoveryKind::Resource { self.discover(&DiscoveryKind::Extension, "*")?; } @@ -287,6 +287,7 @@ impl ResourceDiscovery for CommandDiscovery { trace!("{}", t!("discovery.commandDiscovery.adapterFound", adapter = resource.type_name)); insert_resource(&mut adapters, &resource, true); } + // also make sure to add adapters as a resource as well trace!("{}", t!("discovery.commandDiscovery.resourceFound", resource = resource.type_name)); insert_resource(&mut resources, &resource, true); } @@ -562,7 +563,6 @@ impl ResourceDiscovery for CommandDiscovery { // TODO: This should be a BTreeMap of the resource name and a BTreeMap of the version and DscResource, this keeps it version sorted more efficiently fn insert_resource(resources: &mut BTreeMap>, resource: &DscResource, skip_duplicate_version: bool) { - debug!("Inserting resource: {} version {} from {}", resource.type_name, resource.version, resource.directory); if let Some(resource_versions) = resources.get_mut(&resource.type_name) { debug!("Resource '{}' already exists, checking versions", resource.type_name); // compare the resource versions and insert newest to oldest using semver @@ -571,16 +571,12 @@ fn insert_resource(resources: &mut BTreeMap>, resource: let resource_instance_version = match Version::parse(&resource_instance.version) { Ok(v) => v, Err(err) => { - // write as info since PowerShell resources tend to have invalid semver - info!("Resource '{}' has invalid version: {err}", resource_instance.type_name); continue; }, }; let resource_version = match Version::parse(&resource.version) { Ok(v) => v, Err(err) => { - // write as info since PowerShell resources tend to have invalid semver - info!("Resource '{}' has invalid version: {err}", resource.type_name); continue; }, }; From a788b9f7e038c364b70c49d3dabedbd5c50cf2bf Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Mon, 4 Aug 2025 20:17:25 -0700 Subject: [PATCH 11/11] fix build --- dsc_lib/src/discovery/command_discovery.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dsc_lib/src/discovery/command_discovery.rs b/dsc_lib/src/discovery/command_discovery.rs index d838a3f08..f63668721 100644 --- a/dsc_lib/src/discovery/command_discovery.rs +++ b/dsc_lib/src/discovery/command_discovery.rs @@ -570,13 +570,13 @@ fn insert_resource(resources: &mut BTreeMap>, resource: for (index, resource_instance) in resource_versions.iter().enumerate() { let resource_instance_version = match Version::parse(&resource_instance.version) { Ok(v) => v, - Err(err) => { + Err(_err) => { continue; }, }; let resource_version = match Version::parse(&resource.version) { Ok(v) => v, - Err(err) => { + Err(_err) => { continue; }, };