From ce097a18f917cf6a980262fe7beb0d95a05e818b Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Wed, 4 Feb 2026 21:30:17 -0800 Subject: [PATCH 01/15] add tracing tests --- .../0.0.1/TestClassResource.psm1 | 6 + .../Tests/powershellgroup.config.tests.ps1 | 6 +- .../Tests/powershellgroup.resource.tests.ps1 | 44 ++ .../psDscAdapter/powershell.resource.ps1 | 543 +++++++++++------- .../powershell/psDscAdapter/psDscAdapter.psm1 | 71 +-- .../psDscAdapter/win_psDscAdapter.psm1 | 113 ++-- dsc/examples/powershell.dsc.yaml | 18 +- 7 files changed, 473 insertions(+), 328 deletions(-) diff --git a/adapters/powershell/Tests/TestClassResource/0.0.1/TestClassResource.psm1 b/adapters/powershell/Tests/TestClassResource/0.0.1/TestClassResource.psm1 index ae0a79253..ab9e3be51 100644 --- a/adapters/powershell/Tests/TestClassResource/0.0.1/TestClassResource.psm1 +++ b/adapters/powershell/Tests/TestClassResource/0.0.1/TestClassResource.psm1 @@ -54,10 +54,14 @@ class TestClassResource : BaseTestClass [void] Set() { + Write-Host "This is a Host message" + Write-Information "This is an Information message" + Write-Error "This is an Error message" } [bool] Test() { + Write-Warning "This is a Warning message" if (($this.Name -eq "TestClassResource1") -and ($this.Prop1 -eq "ValueForProp1")) { return $true @@ -70,6 +74,7 @@ class TestClassResource : BaseTestClass [TestClassResource] Get() { + Write-Verbose "This is a Verbose message" if ($this.Name -eq "TestClassResource1") { $this.Prop1 = "ValueForProp1" @@ -88,6 +93,7 @@ class TestClassResource : BaseTestClass static [TestClassResource[]] Export() { + Write-Debug "This is a Debug message" $resultList = [List[TestClassResource]]::new() $resultCount = 5 if ($env:TestClassResourceResultCount) { diff --git a/adapters/powershell/Tests/powershellgroup.config.tests.ps1 b/adapters/powershell/Tests/powershellgroup.config.tests.ps1 index c3f9ddc7b..f64ed376e 100644 --- a/adapters/powershell/Tests/powershellgroup.config.tests.ps1 +++ b/adapters/powershell/Tests/powershellgroup.config.tests.ps1 @@ -26,8 +26,8 @@ Describe 'PowerShell adapter resource tests' { It 'Get works on config with class-based resources' { - $r = Get-Content -Raw $pwshConfigPath | dsc config get -f - - $LASTEXITCODE | Should -Be 0 + $r = Get-Content -Raw $pwshConfigPath | dsc -l trace config get -f - 2> $TestDrive/tracing.txt + $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Raw -Path $TestDrive/tracing.txt) $res = $r | ConvertFrom-Json $res.results[0].result.actualState.result[0].properties.Prop1 | Should -BeExactly 'ValueForProp1' $res.results[0].result.actualState.result[0].properties.EnumProp | Should -BeExactly 'Expected' @@ -126,7 +126,7 @@ Describe 'PowerShell adapter resource tests' { $res.resources[0].properties.result.count | Should -Be 1 $res.resources[0].properties.result[0].Name | Should -Be "FilteredExport" $res.resources[0].properties.result[0].Prop1 | Should -Be "Filtered Property for FilteredExport" - "$TestDrive/export_trace.txt" | Should -FileContentMatch "Properties provided for filtered export" + "$TestDrive/export_trace.txt" | Should -FileContentMatch "Properties provided for filtered export" -Because (Get-Content -Raw -Path $TestDrive/export_trace.txt) } It 'Export fails when filtered export is requested but not implemented' { diff --git a/adapters/powershell/Tests/powershellgroup.resource.tests.ps1 b/adapters/powershell/Tests/powershellgroup.resource.tests.ps1 index c5170ad7f..948192270 100644 --- a/adapters/powershell/Tests/powershellgroup.resource.tests.ps1 +++ b/adapters/powershell/Tests/powershellgroup.resource.tests.ps1 @@ -377,4 +377,48 @@ Describe 'PowerShell adapter resource tests' { $res = $r | ConvertFrom-Json $res.actualState.SecureStringProp | Should -Not -BeNullOrEmpty } + + Context 'Tracing works' { + It 'Error messages come from Write-Error' { + $null = dsc -l error resource set -r TestClassResource/TestClassResource -i '{"Name":"TestClassResource1"}' 2> $TestDrive/error.log + $logContent = Get-Content -Path $TestDrive/error.log -Raw + $LASTEXITCODE | Should -Be 2 -Because $logContent + $logContent | Should -Match 'ERROR .*? This is an Error message' -Because $logContent + } + + It 'Warning messages come from Write-Warning' { + $null = "{'Name':'TestClassResource1','Prop1':'ValueForProp1'}" | dsc -l warn resource test -r 'TestClassResource/TestClassResource' -f - 2> $TestDrive/warning.log + $logContent = Get-Content -Path $TestDrive/warning.log -Raw + $LASTEXITCODE | Should -Be 0 -Because $logContent + $logContent | Should -Match 'WARN .*? This is a Warning message' -Because $logContent + } + + It 'Info messages come from Write-Host' { + $null = "{'Name':'TestClassResource1'}" | dsc -l info resource set -r 'TestClassResource/TestClassResource' -f - 2> $TestDrive/verbose.log + $logContent = Get-Content -Path $TestDrive/verbose.log -Raw + $LASTEXITCODE | Should -Be 2 -Because $logContent + $logContent | Should -Match 'INFO .*? This is a Host message' -Because $logContent + } + + It 'Debug messages come from Write-Verbose' { + $null = "{'Name':'TestClassResource1'}" | dsc -l debug resource get -r 'TestClassResource/TestClassResource' -f - 2> $TestDrive/debug.log + $logContent = Get-Content -Path $TestDrive/debug.log -Raw + $LASTEXITCODE | Should -Be 0 -Because $logContent + $logContent | Should -Match 'DEBUG .*? This is a Verbose message' -Because $logContent + } + + It 'Trace messages come from Write-Debug' { + $null = dsc -l trace resource export -r TestClassResource/TestClassResource 2> $TestDrive/trace.log + $logContent = Get-Content -Path $TestDrive/trace.log -Raw + $LASTEXITCODE | Should -Be 2 -Because $logContent + $logContent | Should -Match 'TRACE .*? This is a Debug message' -Because $logContent + } + + It 'Trace messages come from Write-Information' { + $null = dsc -l trace resource set -r TestClassResource/TestClassResource -i '{"Name":"TestClassResource1"}' 2> $TestDrive/trace_info.log + $logContent = Get-Content -Path $TestDrive/trace_info.log -Raw + $LASTEXITCODE | Should -Be 2 -Because $logContent + $logContent | Should -Match 'TRACE .*? This is an Information message' -Because $logContent + } + } } diff --git a/adapters/powershell/psDscAdapter/powershell.resource.ps1 b/adapters/powershell/psDscAdapter/powershell.resource.ps1 index 6a98c27c7..663e1c5c6 100644 --- a/adapters/powershell/psDscAdapter/powershell.resource.ps1 +++ b/adapters/powershell/psDscAdapter/powershell.resource.ps1 @@ -11,277 +11,406 @@ param( [string]$ResourceType ) +$traceQueue = [System.Collections.Concurrent.ConcurrentQueue[object]]::new() + function Write-DscTrace { param( - [Parameter(Mandatory = $false)] + [Parameter(Mandatory = $true)] [ValidateSet('Error', 'Warn', 'Info', 'Debug', 'Trace')] - [string]$Operation = 'Debug', + [string]$Operation, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [string]$Message + [string]$Message, + [switch]$Now ) - $trace = @{$Operation.ToLower() = $Message } | ConvertTo-Json -Compress - $host.ui.WriteErrorLine($trace) + $trace = @{$Operation.ToLower() = $Message } + + if ($Now) { + $host.ui.WriteErrorLine(($trace | ConvertTo-Json -Compress -Depth 10)) + } else { + $traceQueue.Enqueue($trace) + } } trap { - Write-DscTrace -Operation Debug -Message ($_ | Format-List -Force | Out-String) + Write-DscTrace -Operation Error -Message ($_ | Format-List -Force | Out-String) } -if ($Operation -eq 'ClearCache') { - $cacheFilePath = if ($IsWindows) { - # PS 6+ on Windows - Join-Path $env:LocalAppData "dsc\PSAdapterCache.json" - } else { - # either WinPS or PS 6+ on Linux/Mac - if ($PSVersionTable.PSVersion.Major -le 5) { - Join-Path $env:LocalAppData "dsc\WindowsPSAdapterCache.json" - } else { - Join-Path $env:HOME ".dsc" "PSAdapterCache.json" +function Write-TraceQueue() { + $trace = $null + while (!$traceQueue.IsEmpty) { + if ($traceQueue.TryDequeue([ref] $trace)) { + $host.ui.WriteErrorLine(($trace | ConvertTo-Json -Compress -Depth 10)) } } - - Remove-Item -Force -ea SilentlyContinue -Path $cacheFilePath - exit 0 } -# Adding some debug info to STDERR -'PSVersion=' + $PSVersionTable.PSVersion.ToString() | Write-DscTrace -'PSPath=' + $PSHome | Write-DscTrace -'PSModulePath=' + $env:PSModulePath | Write-DscTrace - -if ($PSVersionTable.PSVersion.Major -le 5) { - # For Windows PowerShell, we want to remove any PowerShell 7 paths from PSModulePath - if ($pwshPath = Get-Command 'pwsh' -ErrorAction Ignore | Select-Object -ExpandProperty Source) { - $pwshDefaultModulePaths = @( - "$HOME\Documents\PowerShell\Modules" # CurrentUser - "$Env:ProgramFiles\PowerShell\Modules" # AllUsers - Join-Path $(Split-Path $pwshPath -Parent) 'Modules' # Builtin - ) - $env:PSModulePath = ($env:PSModulePath -split ';' | Where-Object { $_ -notin $pwshDefaultModulePaths }) -join ';' +$ps = [PowerShell]::Create().AddScript({ + [CmdletBinding()] + param( + [Parameter(Mandatory = $true, Position = 0, HelpMessage = 'Operation to perform. Choose from List, Get, Set, Test, Export, Validate, ClearCache.')] + [ValidateSet('List', 'Get', 'Set', 'Test', 'Export', 'Validate', 'ClearCache')] + [string]$Operation, + [Parameter(Mandatory = $false, Position = 1, ValueFromPipeline = $true, HelpMessage = 'Configuration or resource input in JSON format.')] + [string]$jsonInput = '{}', + [Parameter()] + [string]$ResourceType, + [Parameter()] + [string]$ScriptRoot + ) + + $DebugPreference = 'Continue' + $VerbosePreference = 'Continue' + + if ($Operation -eq 'ClearCache') { + $cacheFilePath = if ($IsWindows) { + # PS 6+ on Windows + Join-Path $env:LocalAppData "dsc\PSAdapterCache.json" + } else { + # either WinPS or PS 6+ on Linux/Mac + if ($PSVersionTable.PSVersion.Major -le 5) { + Join-Path $env:LocalAppData "dsc\WindowsPSAdapterCache.json" + } else { + Join-Path $env:HOME ".dsc" "PSAdapterCache.json" + } + } + + Remove-Item -Force -ea SilentlyContinue -Path $cacheFilePath + exit 0 } -} -if ('Validate' -ne $Operation) { - Write-DscTrace -Operation Debug -Message "jsonInput=$jsonInput" + # Adding some debug info to STDERR + Write-Debug ('PSVersion=' + $PSVersionTable.PSVersion.ToString()) + Write-Debug ('PSPath=' + $PSHome) + Write-Debug ('PSModulePath=' + $env:PSModulePath) - # load private functions of psDscAdapter stub module if ($PSVersionTable.PSVersion.Major -le 5) { - $psDscAdapter = Import-Module "$PSScriptRoot/win_psDscAdapter.psd1" -Force -PassThru - } - else { - $psDscAdapter = Import-Module "$PSScriptRoot/psDscAdapter.psd1" -Force -PassThru + # For Windows PowerShell, we want to remove any PowerShell 7 paths from PSModulePath + if ($pwshPath = Get-Command 'pwsh' -ErrorAction Ignore | Select-Object -ExpandProperty Source) { + $pwshDefaultModulePaths = @( + "$HOME\Documents\PowerShell\Modules" # CurrentUser + "$Env:ProgramFiles\PowerShell\Modules" # AllUsers + Join-Path $(Split-Path $pwshPath -Parent) 'Modules' # Builtin + ) + $env:PSModulePath = ($env:PSModulePath -split ';' | Where-Object { $_ -notin $pwshDefaultModulePaths }) -join ';' + } } - # initialize OUTPUT as array - $result = [System.Collections.Generic.List[Object]]::new() -} + if ('Validate' -ne $Operation) { + Write-Debug ("jsonInput=$jsonInput") + + # load private functions of psDscAdapter stub module + if ($PSVersionTable.PSVersion.Major -le 5) { + $psDscAdapter = Import-Module "$ScriptRoot/win_psDscAdapter.psd1" -Force -PassThru + } + else { + $psDscAdapter = Import-Module "$ScriptRoot/psDscAdapter.psd1" -Force -PassThru + } -if ($jsonInput) { - if ($jsonInput -ne '{}') { - $inputobj_pscustomobj = $jsonInput | ConvertFrom-Json + # initialize OUTPUT as array + $result = [System.Collections.Generic.List[Object]]::new() } - $new_psmodulepath = $inputobj_pscustomobj.psmodulepath - if ($new_psmodulepath) - { - $env:PSModulePath = $ExecutionContext.InvokeCommand.ExpandString($new_psmodulepath) + + if ($jsonInput) { + if ($jsonInput -ne '{}') { + $inputobj_pscustomobj = $jsonInput | ConvertFrom-Json + } + $new_psmodulepath = $inputobj_pscustomobj.psmodulepath + if ($new_psmodulepath) + { + $env:PSModulePath = $ExecutionContext.InvokeCommand.ExpandString($new_psmodulepath) + } } -} -# process the operation requested to the script -switch ($Operation) { - 'List' { - $dscResourceCache = Invoke-DscCacheRefresh - - # cache was refreshed on script load - foreach ($dscResource in $dscResourceCache) { - - # https://learn.microsoft.com/dotnet/api/system.management.automation.dscresourceinfo - $DscResourceInfo = $dscResource.DscResourceInfo - - # Provide a way for existing resources to specify their capabilities, or default to Get, Set, Test - # TODO: for perf, it is better to take capabilities from psd1 in Invoke-DscCacheRefresh, not by extra call to Get-Module - if ($DscResourceInfo.ModuleName) { - $module = Get-Module -Name $DscResourceInfo.ModuleName -ListAvailable | Sort-Object -Property Version -Descending | Select-Object -First 1 - # If the DscResourceInfo does have capabilities, use them or else use the module's capabilities - if ($DscResourceInfo.Capabilities) { - $capabilities = $DscResourceInfo.Capabilities - } elseif ($module.PrivateData.PSData.DscCapabilities) { - - $capabilities = $module.PrivateData.PSData.DscCapabilities - } else { - $capabilities = @('get', 'set', 'test') + # process the operation requested to the script + switch ($Operation) { + 'List' { + $dscResourceCache = Invoke-DscCacheRefresh + + # cache was refreshed on script load + foreach ($dscResource in $dscResourceCache) { + + # https://learn.microsoft.com/dotnet/api/system.management.automation.dscresourceinfo + $DscResourceInfo = $dscResource.DscResourceInfo + + # Provide a way for existing resources to specify their capabilities, or default to Get, Set, Test + # TODO: for perf, it is better to take capabilities from psd1 in Invoke-DscCacheRefresh, not by extra call to Get-Module + if ($DscResourceInfo.ModuleName) { + $module = Get-Module -Name $DscResourceInfo.ModuleName -ListAvailable | Sort-Object -Property Version -Descending | Select-Object -First 1 + # If the DscResourceInfo does have capabilities, use them or else use the module's capabilities + if ($DscResourceInfo.Capabilities) { + $capabilities = $DscResourceInfo.Capabilities + } elseif ($module.PrivateData.PSData.DscCapabilities) { + + $capabilities = $module.PrivateData.PSData.DscCapabilities + } else { + $capabilities = @('get', 'set', 'test') + } } - } - # this text comes directly from the resource manifest for v3 native resources - if ($DscResourceInfo.Description) { - $description = $DscResourceInfo.Description - } - elseif ($module.Description) { - # some modules have long multi-line descriptions. to avoid issue, use only the first line. - $description = $module.Description.split("`r`n")[0] - } - else { - $description = '' - } + # this text comes directly from the resource manifest for v3 native resources + if ($DscResourceInfo.Description) { + $description = $DscResourceInfo.Description + } + elseif ($module.Description) { + # some modules have long multi-line descriptions. to avoid issue, use only the first line. + $description = $module.Description.split("`r`n")[0] + } + else { + $description = '' + } - # match adapter to version of powershell - if ($PSVersionTable.PSVersion.Major -le 5) { - if ($ResourceType) { - $requireAdapter = 'Microsoft.Adapter/WindowsPowerShell' - } else { - $requireAdapter = 'Microsoft.Windows/WindowsPowerShell' + # match adapter to version of powershell + if ($PSVersionTable.PSVersion.Major -le 5) { + if ($ResourceType) { + $requireAdapter = 'Microsoft.Adapter/WindowsPowerShell' + } else { + $requireAdapter = 'Microsoft.Windows/WindowsPowerShell' + } } - } - else { - if ($ResourceType) { - $requireAdapter = 'Microsoft.Adapter/PowerShell' - } else { - $requireAdapter = 'Microsoft.DSC/PowerShell' + else { + if ($ResourceType) { + $requireAdapter = 'Microsoft.Adapter/PowerShell' + } else { + $requireAdapter = 'Microsoft.DSC/PowerShell' + } } - } - $properties = @() - foreach ($prop in $DscResourceInfo.Properties) { - $properties += $prop.Name - } + $properties = @() + foreach ($prop in $DscResourceInfo.Properties) { + $properties += $prop.Name + } - # OUTPUT dsc is expecting the following properties - [resourceOutput]@{ - type = $dscResource.Type - kind = 'resource' - version = [string]$DscResourceInfo.version - capabilities = $capabilities - path = $DscResourceInfo.Path - directory = $DscResourceInfo.ParentPath - implementedAs = $DscResourceInfo.ImplementationDetail - author = $DscResourceInfo.CompanyName - properties = $properties - requireAdapter = $requireAdapter - description = $description - } | ConvertTo-Json -Compress + # OUTPUT dsc is expecting the following properties + [resourceOutput]@{ + type = $dscResource.Type + kind = 'resource' + version = [string]$DscResourceInfo.version + capabilities = $capabilities + path = $DscResourceInfo.Path + directory = $DscResourceInfo.ParentPath + implementedAs = $DscResourceInfo.ImplementationDetail + author = $DscResourceInfo.CompanyName + properties = $properties + requireAdapter = $requireAdapter + description = $description + } + } } - } - { @('Get','Set','Test','Export') -contains $_ } { - if ($ResourceType) { - Write-DscTrace -Operation Debug -Message "Using resource type override: $ResourceType" - $dscResourceCache = Invoke-DscCacheRefresh -Module $ResourceType.Split('/')[0] - if ($null -eq $dscResourceCache) { - Write-DscTrace -Operation Error -Message ("DSC resource '{0}' module not found." -f $ResourceType) - exit 1 + { @('Get','Set','Test','Export') -contains $_ } { + if ($ResourceType) { + Write-Debug ("Using resource type override: $ResourceType") + $dscResourceCache = Invoke-DscCacheRefresh -Module $ResourceType.Split('/')[0] + if ($null -eq $dscResourceCache) { + Write-Error ("DSC resource '{0}' module not found." -f $ResourceType) + exit 1 + } + + $desiredState = $psDscAdapter.invoke( { param($jsonInput, $type) Get-DscResourceObject -jsonInput $jsonInput -type $type }, $jsonInput, $ResourceType ) + if ($null -eq $desiredState) { + Write-Error 'Failed to create configuration object from provided input JSON.' + exit 1 + } + + $desiredState.Type = $ResourceType + $inDesiredState = $true + $actualState = $psDscAdapter.invoke( { param($op, $ds, $dscResourceCache) Invoke-DscOperation -Operation $op -DesiredState $ds -dscResourceCache $dscResourceCache }, $Operation, $desiredState, $dscResourceCache) + if ($null -eq $actualState) { + Write-Error 'Incomplete GET for resource ' + $desiredState.Name + exit 1 + } + if ($actualState.Properties.InDesiredState -eq $false) { + $inDesiredState = $false + } + + if ($Operation -in @('Set', 'Test')) { + $actualState = $psDscAdapter.Invoke( { param($ds, $dscResourceCache) Invoke-DscOperation -Operation 'Get' -DesiredState $ds -dscResourceCache $dscResourceCache }, $desiredState, $dscResourceCache) + } + + if ($Operation -eq 'Test') { + $actualState.Properties | Add-Member -MemberType NoteProperty -Name _inDesiredState -Value $inDesiredState -Force + } + + if ($Operation -eq 'Export') { + foreach ($instance in $actualState) { + $instance + } + exit 0 + } + + $result = $actualState.Properties + return $result } - $desiredState = $psDscAdapter.invoke( { param($jsonInput, $type) Get-DscResourceObject -jsonInput $jsonInput -type $type }, $jsonInput, $ResourceType ) + $desiredState = $psDscAdapter.invoke( { param($jsonInput) Get-DscResourceObject -jsonInput $jsonInput }, $jsonInput ) if ($null -eq $desiredState) { - Write-DscTrace -Operation Error -message 'Failed to create configuration object from provided input JSON.' + Write-Error 'Failed to create configuration object from provided input JSON.' exit 1 } - $desiredState.Type = $ResourceType - $inDesiredState = $true - $actualState = $psDscAdapter.invoke( { param($op, $ds, $dscResourceCache) Invoke-DscOperation -Operation $op -DesiredState $ds -dscResourceCache $dscResourceCache }, $Operation, $desiredState, $dscResourceCache) - if ($null -eq $actualState) { - Write-DscTrace -Operation Error -Message 'Incomplete GET for resource ' + $desiredState.Name + # only need to cache the resources that are used + $dscResourceModules = $desiredState | ForEach-Object { $_.Type.Split('/')[0] } + if ($null -eq $dscResourceModules) { + Write-Error 'Could not get list of DSC resource types from provided JSON.' exit 1 } - if ($actualState.Properties.InDesiredState -eq $false) { - $inDesiredState = $false - } - if ($Operation -in @('Set', 'Test')) { - $actualState = $psDscAdapter.Invoke( { param($ds, $dscResourceCache) Invoke-DscOperation -Operation 'Get' -DesiredState $ds -dscResourceCache $dscResourceCache }, $desiredState, $dscResourceCache) - } + # get unique module names from the desiredState input + $moduleInput = $desiredState | Select-Object -ExpandProperty Type | Sort-Object -Unique - if ($Operation -eq 'Test') { - $actualState.Properties | Add-Member -MemberType NoteProperty -Name _inDesiredState -Value $inDesiredState -Force + # refresh the cache with the modules that are available on the system + $dscResourceCache = Invoke-DscCacheRefresh -module $dscResourceModules + + # check if all the desired modules are in the cache + $moduleInput | ForEach-Object { + if ($dscResourceCache.type -notcontains $_) { + Write-Error ("DSC resource '{0}' module not found." -f $_) + exit 1 + } } - if ($Operation -eq 'Export') { - foreach ($instance in $actualState) { - $instance | ConvertTo-Json -Depth 10 -Compress + $inDesiredState = $true + foreach ($ds in $desiredState) { + # process the INPUT (desiredState) for each resource as dscresourceInfo and return the OUTPUT as actualState + $actualState = $psDscAdapter.invoke( { param($op, $ds, $dscResourceCache) Invoke-DscOperation -Operation $op -DesiredState $ds -dscResourceCache $dscResourceCache }, $Operation, $ds, $dscResourceCache) + if ($null -eq $actualState) { + Write-Error ("Failed to invoke operation '{0}' for resource name '{1}'." -f $Operation, $ds.Name) + exit 1 + } + if ($null -ne $actualState.Properties -and $actualState.Properties.InDesiredState -eq $false) { + $inDesiredState = $false } - exit 0 + $result += $actualState } - $result = $actualState.Properties | ConvertTo-Json -Depth 10 -Compress - Write-DscTrace -Operation Debug -Message "jsonOutput=$result" + # OUTPUT json to stderr for debug, and to stdout + if ($Operation -eq 'Test') { + $result = @{ result = $result; _inDesiredState = $inDesiredState } + } + else { + $result = @{ result = $result } + } return $result } + 'Validate' { + # VALIDATE not implemented - $desiredState = $psDscAdapter.invoke( { param($jsonInput) Get-DscResourceObject -jsonInput $jsonInput }, $jsonInput ) - if ($null -eq $desiredState) { - Write-DscTrace -Operation Error -message 'Failed to create configuration object from provided input JSON.' - exit 1 + # OUTPUT + @{ valid = $true } } - - # only need to cache the resources that are used - $dscResourceModules = $desiredState | ForEach-Object { $_.Type.Split('/')[0] } - if ($null -eq $dscResourceModules) { - Write-DscTrace -Operation Error -Message 'Could not get list of DSC resource types from provided JSON.' - exit 1 + Default { + Write-Error 'Unsupported operation. Please use one of the following: List, Get, Set, Test, Export, Validate' } + } + + # output format for resource list + class resourceOutput { + [string] $type + [string] $kind + [string] $version + [string[]] $capabilities + [string] $path + [string] $directory + [string] $implementedAs + [string] $author + [string[]] $properties + [string] $requireAdapter + [string] $description + } +}).AddParameter('Operation', $Operation).AddParameter('jsonInput', $jsonInput).AddParameter('ResourceType', $ResourceType).AddParameter('ScriptRoot', $PSScriptRoot) + +enum DscTraceLevel { + Error + Warn + Info + Debug + Trace +} - # get unique module names from the desiredState input - $moduleInput = $desiredState | Select-Object -ExpandProperty Type | Sort-Object -Unique +$traceLevel = if ($env:DSC_TRACE_LEVEL) { + try { + [DscTraceLevel]$env:DSC_TRACE_LEVEL + } catch { + [DscTraceLevel]::Warn + } +} else { + [DscTraceLevel]::Warn +} +Write-DscTrace -Operation Debug -Now -Message ("Trace level set to: $traceLevel") - # refresh the cache with the modules that are available on the system - $dscResourceCache = Invoke-DscCacheRefresh -module $dscResourceModules +$null = Register-ObjectEvent -InputObject $ps.Streams.Error -EventName DataAdding -MessageData $traceQueue -Action { + $traceQueue = $Event.MessageData + # convert error to string since it's an ErrorRecord + $traceQueue.Enqueue(@{ error = [string]$EventArgs.ItemAdded }) +} - # check if all the desired modules are in the cache - $moduleInput | ForEach-Object { - if ($dscResourceCache.type -notcontains $_) { - ("DSC resource '{0}' module not found." -f $_) | Write-DscTrace -Operation Error - exit 1 - } - } +if ($traceLevel -ge [DscTraceLevel]::Warn) { + $null = Register-ObjectEvent -InputObject $ps.Streams.Warning -EventName DataAdding -MessageData $traceQueue -Action { + $traceQueue = $Event.MessageData + $traceQueue.Enqueue(@{ warn = $EventArgs.ItemAdded.Message }) + } +} - $inDesiredState = $true - foreach ($ds in $desiredState) { - # process the INPUT (desiredState) for each resource as dscresourceInfo and return the OUTPUT as actualState - $actualState = $psDscAdapter.invoke( { param($op, $ds, $dscResourceCache) Invoke-DscOperation -Operation $op -DesiredState $ds -dscResourceCache $dscResourceCache }, $Operation, $ds, $dscResourceCache) - if ($null -eq $actualState) { - "Failed to invoke operation '{0}' for resource name '{1}'." -f $Operation, $ds.Name | Write-DscTrace -Operation Error - exit 1 - } - if ($null -ne $actualState.Properties -and $actualState.Properties.InDesiredState -eq $false) { - $inDesiredState = $false +if ($traceLevel -ge [DscTraceLevel]::Info) { + $null = Register-ObjectEvent -InputObject $ps.Streams.Information -EventName DataAdding -MessageData $traceQueue -Action { + $traceQueue = $Event.MessageData + if ($null -ne $EventArgs.ItemAdded.MessageData) { + if ($EventArgs.ItemAdded.Tags -contains 'PSHOST') { + $traceQueue.Enqueue(@{ info = $EventArgs.ItemAdded.MessageData.ToString() }) + } else { + $traceQueue.Enqueue(@{ trace = $EventArgs.ItemAdded.MessageData.ToString() }) } - $result += $actualState + return } + } +} - # OUTPUT json to stderr for debug, and to stdout - if ($Operation -eq 'Test') { - $result = @{ result = $result; _inDesiredState = $inDesiredState } | ConvertTo-Json -Depth 10 -Compress - } - else { - $result = @{ result = $result } | ConvertTo-Json -Depth 10 -Compress - } - Write-DscTrace -Operation Debug -Message "jsonOutput=$result" - return $result +if ($traceLevel -ge [DscTraceLevel]::Debug) { + $null = Register-ObjectEvent -InputObject $ps.Streams.Verbose -EventName DataAdding -MessageData $traceQueue -Action { + $traceQueue = $Event.MessageData + # Verbose messages tend to be in large quantity and more useful to developers, so log as Debug + $traceQueue.Enqueue(@{ debug = $EventArgs.ItemAdded.Message }) + } +} + +if ($traceLevel -ge [DscTraceLevel]::Trace) { + $null = Register-ObjectEvent -InputObject $ps.Streams.Debug -EventName DataAdding -MessageData $traceQueue -Action { + $traceQueue = $Event.MessageData + # Debug messages may contain raw info, so log as Trace + $traceQueue.Enqueue(@{ trace = $EventArgs.ItemAdded.Message }) + } +} +$outputObjects = [System.Collections.Generic.List[Object]]::new() + +try { + $asyncResult = $ps.BeginInvoke() + while (-not $asyncResult.IsCompleted) { + Write-TraceQueue + + Start-Sleep -Milliseconds 100 } - 'Validate' { - # VALIDATE not implemented + $outputCollection = $ps.EndInvoke($asyncResult) + Write-TraceQueue - # OUTPUT - @{ valid = $true } | ConvertTo-Json + if ($ps.HadErrors) { + # If there are any errors, we will exit with an error code + Write-DscTrace -Now -Operation Error -Message 'Errors occurred during script execution.' + exit 1 } - Default { - Write-DscTrace -Operation Error -Message 'Unsupported operation. Please use one of the following: List, Get, Set, Test, Export, Validate' + + foreach ($output in $outputCollection) { + $outputObjects.Add($output) } } +catch { + Write-DscTrace -Now -Operation Error -Message $_ + exit 1 +} +finally { + $ps.Dispose() + Get-EventSubscriber | Unregister-Event +} -# output format for resource list -class resourceOutput { - [string] $type - [string] $kind - [string] $version - [string[]] $capabilities - [string] $path - [string] $directory - [string] $implementedAs - [string] $author - [string[]] $properties - [string] $requireAdapter - [string] $description +foreach ($obj in $outputObjects) { + $obj | ConvertTo-Json -Depth 10 -Compress } diff --git a/adapters/powershell/psDscAdapter/psDscAdapter.psm1 b/adapters/powershell/psDscAdapter/psDscAdapter.psm1 index fc1b84f0e..134c5d82c 100644 --- a/adapters/powershell/psDscAdapter/psDscAdapter.psm1 +++ b/adapters/powershell/psDscAdapter/psDscAdapter.psm1 @@ -3,20 +3,6 @@ $script:CurrentCacheSchemaVersion = 3 -function Write-DscTrace { - param( - [Parameter(Mandatory = $false)] - [ValidateSet('Error', 'Warn', 'Info', 'Debug', 'Trace')] - [string]$Operation = 'Debug', - - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [string]$Message - ) - - $trace = @{$Operation.ToLower() = $Message } | ConvertTo-Json -Compress - $host.ui.WriteErrorLine($trace) -} - function Import-PSDSCModule { $m = Get-Module PSDesiredStateConfiguration -ListAvailable | Sort-Object -Descending | Select-Object -First 1 $PSDesiredStateConfiguration = Import-Module $m -Force -PassThru @@ -105,13 +91,13 @@ function FindAndParseResourceDefinitions { return } - "Loading resources from file '$filePath'" | Write-DscTrace -Operation Trace + Write-Debug ("Loading resources from file '$filePath'") #TODO: Ensure embedded instances in properties are working correctly [System.Management.Automation.Language.Token[]] $tokens = $null [System.Management.Automation.Language.ParseError[]] $errors = $null $ast = [System.Management.Automation.Language.Parser]::ParseFile($filePath, [ref]$tokens, [ref]$errors) foreach ($e in $errors) { - $e | Out-String | Write-DscTrace -Operation Error + $e | Out-String | Write-Error } $typeDefinitions = $ast.FindAll( @@ -155,7 +141,7 @@ function GetExportMethod ($ResourceType, $HasFilterProperties, $ResourceTypeName $method = $null if ($HasFilterProperties) { - "Properties provided for filtered export" | Write-DscTrace -Operation Trace + Write-Verbose "Properties provided for filtered export" $method = foreach ($mt in $methods) { if ($mt.GetParameters().Count -gt 0) { $mt @@ -164,12 +150,12 @@ function GetExportMethod ($ResourceType, $HasFilterProperties, $ResourceTypeName } if ($null -eq $method) { - "Export method with parameters not implemented by resource '$ResourceTypeName'. Filtered export is not supported." | Write-DscTrace -Operation Error + Write-Error ("Export method with parameters not implemented by resource '$ResourceTypeName'. Filtered export is not supported.") exit 1 } } else { - "No properties provided, using parameterless export" | Write-DscTrace -Operation Trace + Write-Verbose "No properties provided, using parameterless export" $method = foreach ($mt in $methods) { if ($mt.GetParameters().Count -eq 0) { $mt @@ -178,7 +164,7 @@ function GetExportMethod ($ResourceType, $HasFilterProperties, $ResourceTypeName } if ($null -eq $method) { - "Export method not implemented by resource '$ResourceTypeName'" | Write-DscTrace -Operation Error + Write-Error ("Export method not implemented by resource '$ResourceTypeName'") exit 1 } } @@ -193,12 +179,12 @@ function LoadPowerShellClassResourcesFromModule { [PSModuleInfo]$moduleInfo ) - "Loading resources from module '$($moduleInfo.Path)'" | Write-DscTrace -Operation Trace + Write-Debug ("Loading resources from module '$($moduleInfo.Path)'") if ($moduleInfo.RootModule) { if (".psm1", ".ps1" -notcontains ([System.IO.Path]::GetExtension($moduleInfo.RootModule)) -and (-not $moduleInfo.NestedModules)) { - "RootModule is neither psm1 nor ps1 '$($moduleInfo.RootModule)'" | Write-DscTrace -Operation Trace + Write-Debug ("RootModule is neither psm1 nor ps1 '$($moduleInfo.RootModule)'") return [System.Collections.Generic.List[DscResourceInfo]]::new() } @@ -255,13 +241,13 @@ function Invoke-DscCacheRefresh { } if (Test-Path $cacheFilePath) { - "Reading from Get-DscResource cache file $cacheFilePath" | Write-DscTrace + Write-Verbose ("Reading from Get-DscResource cache file $cacheFilePath") $cache = Get-Content -Raw $cacheFilePath | ConvertFrom-Json if ($cache.CacheSchemaVersion -ne $script:CurrentCacheSchemaVersion) { $refreshCache = $true - "Incompatible version of cache in file '" + $cache.CacheSchemaVersion + "' (expected '" + $script:CurrentCacheSchemaVersion + "')" | Write-DscTrace + Write-Verbose ("Incompatible version of cache in file '" + $cache.CacheSchemaVersion + "' (expected '" + $script:CurrentCacheSchemaVersion + "')") } else { $dscResourceCacheEntries = $cache.ResourceCache @@ -270,10 +256,10 @@ function Invoke-DscCacheRefresh { # if there is nothing in the cache file - refresh cache $refreshCache = $true - "Filtered DscResourceCache cache is empty" | Write-DscTrace + Write-Debug "Filtered DscResourceCache cache is empty" } else { - "Checking cache for stale entries" | Write-DscTrace + Write-Debug "Checking cache for stale entries" foreach ($cacheEntry in $dscResourceCacheEntries) { @@ -289,13 +275,13 @@ function Invoke-DscCacheRefresh { $cache_LastWriteTime = $cache_LastWriteTime.AddTicks( - ($cache_LastWriteTime.Ticks % [TimeSpan]::TicksPerSecond)); if (-not ($file_LastWriteTime.Equals($cache_LastWriteTime))) { - "Detected stale cache entry '$($_.Name)'" | Write-DscTrace + Write-Debug ("Detected stale cache entry '$($_.Name)'") $refreshCache = $true break } } else { - "Detected non-existent cache entry '$($_.Name)'" | Write-DscTrace + Write-Debug ("Detected non-existent cache entry '$($_.Name)'") $refreshCache = $true break } @@ -305,7 +291,7 @@ function Invoke-DscCacheRefresh { } if (-not $refreshCache) { - "Checking cache for stale PSModulePath" | Write-DscTrace + Write-Debug "Checking cache for stale PSModulePath" $m = $env:PSModulePath -split [IO.Path]::PathSeparator | % { Get-ChildItem -Directory -Path $_ -Depth 1 -ea SilentlyContinue } @@ -314,8 +300,7 @@ function Invoke-DscCacheRefresh { $hs_cache.SymmetricExceptWith($hs_live) $diff = $hs_cache - "PSModulePath diff '$diff'" | Write-DscTrace - + Write-Debug ("PSModulePath diff '$diff'") if ($diff.Count -gt 0) { $refreshCache = $true } @@ -324,12 +309,12 @@ function Invoke-DscCacheRefresh { } } else { - "Cache file not found '$cacheFilePath'" | Write-DscTrace + Write-Verbose ("Cache file not found '$cacheFilePath'") $refreshCache = $true } if ($refreshCache) { - 'Constructing Get-DscResource cache' | Write-DscTrace + Write-Verbose "Constructing Get-DscResource cache" # create a list object to store cache of Get-DscResource [dscResourceCacheEntry[]]$dscResourceCacheEntries = [System.Collections.Generic.List[Object]]::new() @@ -346,7 +331,7 @@ function Invoke-DscCacheRefresh { # from several modules with the same name select the one with the highest version $selectedMod = $modules | Where-Object Name -EQ $mod.Name if ($selectedMod.Count -gt 1) { - "Found $($selectedMod.Count) modules with name '$($mod.Name)'" | Write-DscTrace -Operation Trace + Write-Debug ("Found $($selectedMod.Count) modules with name '$($mod.Name)'") $selectedMod = $selectedMod | Sort-Object -Property Version -Descending | Select-Object -First 1 } @@ -382,7 +367,7 @@ function Invoke-DscCacheRefresh { # save cache for future use # TODO: replace this with a high-performance serializer - "Saving Get-DscResource cache to '$cacheFilePath'" | Write-DscTrace + Write-Debug ("Saving Get-DscResource cache to '$cacheFilePath'") $jsonCache = $cache | ConvertTo-Json -Depth 90 New-Item -Force -Path $cacheFilePath -Value $jsonCache -Type File | Out-Null } @@ -435,10 +420,10 @@ function Invoke-DscOperation { ) $osVersion = [System.Environment]::OSVersion.VersionString - 'OS version: ' + $osVersion | Write-DscTrace + Write-Debug ('OS version: ' + $osVersion) $psVersion = $PSVersionTable.PSVersion.ToString() - 'PowerShell version: ' + $psVersion | Write-DscTrace + Write-Debug ('PowerShell version: ' + $psVersion) # get details from cache about the DSC resource, if it exists $cachedDscResourceInfo = $dscResourceCache | Where-Object Type -EQ $DesiredState.type | ForEach-Object DscResourceInfo | Select-Object -First 1 @@ -465,7 +450,7 @@ function Invoke-DscOperation { $ValidProperties = $cachedDscResourceInfo.Properties.Name - $ValidProperties | ConvertTo-Json | Write-DscTrace -Operation Trace + Write-Debug ("Valid properties: " + ($ValidProperties | ConvertTo-Json)) if ($DesiredState.properties) { # set each property of $dscResourceInstance to the value of the property in the $desiredState INPUT object @@ -475,7 +460,7 @@ function Invoke-DscOperation { if ($_.Value -is [System.Management.Automation.PSCustomObject]) { if ($validateProperty -and $validateProperty.PropertyType -in @('PSCredential', 'System.Management.Automation.PSCredential')) { if (-not $_.Value.Username -or -not $_.Value.Password) { - "Credential object '$($_.Name)' requires both 'username' and 'password' properties" | Write-DscTrace -Operation Error + Write-Error ("Credential object '$($_.Name)' requires both 'username' and 'password' properties") exit 1 } $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) @@ -548,22 +533,22 @@ function Invoke-DscOperation { } catch { - 'Exception: ' + $_.Exception.Message | Write-DscTrace -Operation Error + Write-Error ('Exception: ' + $_.Exception.Message) exit 1 } } Default { - 'Resource ImplementationDetail not supported: ' + $cachedDscResourceInfo.ImplementationDetail | Write-DscTrace -Operation Error + Write-Error ('Resource ImplementationDetail not supported: ' + $cachedDscResourceInfo.ImplementationDetail) exit 1 } } - "Output: $($addToActualState | ConvertTo-Json -Depth 10 -Compress)" | Write-DscTrace -Operation Trace + Write-Debug ("Output: $($addToActualState | ConvertTo-Json -Depth 10 -Compress)") return $addToActualState } else { $dsJSON = $DesiredState | ConvertTo-Json -Depth 10 - 'Can not find type "' + $DesiredState.type + '" for resource "' + $dsJSON + '". Please ensure that Get-DscResource returns this resource type.' | Write-DscTrace -Operation Error + Write-Error ('Can not find type "' + $DesiredState.type + '" for resource "' + $dsJSON + '". Please ensure that Get-DscResource returns this resource type.') exit 1 } } diff --git a/adapters/powershell/psDscAdapter/win_psDscAdapter.psm1 b/adapters/powershell/psDscAdapter/win_psDscAdapter.psm1 index 38665e720..1b355216e 100644 --- a/adapters/powershell/psDscAdapter/win_psDscAdapter.psm1 +++ b/adapters/powershell/psDscAdapter/win_psDscAdapter.psm1 @@ -5,21 +5,7 @@ $global:ProgressPreference = 'SilentlyContinue' $script:CurrentCacheSchemaVersion = 1 trap { - Write-DscTrace -Operation Debug -Message ($_ | Format-List -Force | Out-String) -} - -function Write-DscTrace { - param( - [Parameter(Mandatory = $false)] - [ValidateSet('Error', 'Warn', 'Info', 'Debug', 'Trace')] - [string]$Operation = 'Debug', - - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - [string]$Message - ) - - $trace = @{$Operation.ToLower() = $Message } | ConvertTo-Json -Compress - $host.ui.WriteErrorLine($trace) + Write-Error ($_ | Format-List -Force | Out-String) } # if the version of PowerShell is greater than 5, import the PSDesiredStateConfiguration module @@ -32,7 +18,7 @@ if ($PSVersionTable.PSVersion.Major -gt 5) { $env:PSModulePath = "$env:windir\System32\WindowsPowerShell\v1.0\Modules;$env:PSModulePath" $PSDesiredStateConfiguration = Import-Module -Name 'PSDesiredStateConfiguration' -RequiredVersion '1.1' -Force -PassThru -ErrorAction stop -ErrorVariable $importModuleError if (-not [string]::IsNullOrEmpty($importModuleError)) { - 'Could not import PSDesiredStateConfiguration 1.1 in Windows PowerShell. ' + $importModuleError | Write-DscTrace -Operation Error + Write-Error ('Could not import PSDesiredStateConfiguration 1.1 in Windows PowerShell. ' + $importModuleError) } } @@ -64,22 +50,21 @@ function Invoke-DscCacheRefresh { Repair-ValidPSModulePath if (Test-Path $cacheFilePath) { - "Reading from Get-DscResource cache file $cacheFilePath" | Write-DscTrace + Write-Verbose ("Reading from Get-DscResource cache file $cacheFilePath") $cache = Get-Content -Raw $cacheFilePath | ConvertFrom-Json if ($cache.CacheSchemaVersion -ne $script:CurrentCacheSchemaVersion) { $refreshCache = $true - "Incompatible version of cache in file '" + $cache.CacheSchemaVersion + "' (expected '" + $script:CurrentCacheSchemaVersion + "')" | Write-DscTrace + Write-Verbose ("Incompatible version of cache in file '" + $cache.CacheSchemaVersion + "' (expected '" + $script:CurrentCacheSchemaVersion + "')") } else { $dscResourceCacheEntries = $cache.ResourceCache if ($dscResourceCacheEntries.Count -eq 0) { # if there is nothing in the cache file - refresh cache $refreshCache = $true - "Filtered DscResourceCache cache is empty" | Write-DscTrace + Write-Debug ("Filtered DscResourceCache cache is empty") } else { - "Checking cache for stale PSModulePath" | Write-DscTrace - + Write-Debug ("Checking cache for stale PSModulePath") $m = $env:PSModulePath -split [IO.Path]::PathSeparator | ForEach-Object { Get-ChildItem -Directory -Path $_ -Depth 1 -ErrorAction Ignore } $hs_cache = [System.Collections.Generic.HashSet[string]]($cache.PSModulePaths) @@ -87,14 +72,14 @@ function Invoke-DscCacheRefresh { $hs_cache.SymmetricExceptWith($hs_live) $diff = $hs_cache - "PSModulePath diff '$diff'" | Write-DscTrace + Write-Debug ("PSModulePath diff '$diff'") # TODO: Optimise for named module refresh if ($diff.Count -gt 0) { $refreshCache = $true } if (-not $refreshCache) { - "Checking cache for stale entries" | Write-DscTrace + Write-Debug ("Checking cache for stale entries") foreach ($cacheEntry in $dscResourceCacheEntries) { @@ -105,12 +90,12 @@ function Invoke-DscCacheRefresh { $cache_LastWriteTime = [long]$_.Value if ($file_LastWriteTime -ne $cache_LastWriteTime) { - "Detected stale cache entry '$($_.Name)'" | Write-DscTrace + Write-Debug ("Detected stale cache entry '$($_.Name)'") $namedModules.Add($cacheEntry.DscResourceInfo.ModuleName) break } } else { - "Detected non-existent cache entry '$($_.Name)'" | Write-DscTrace + Write-Debug ("Detected non-existent cache entry '$($_.Name)'") $namedModules.Add($cacheEntry.DscResourceInfo.ModuleName) break } @@ -123,31 +108,31 @@ function Invoke-DscCacheRefresh { $namedModules.AddRange(@($Module)) } $namedModules = $namedModules | Sort-Object -Unique - "Module list: $($namedModules -join ', ')" | Write-DscTrace + Write-Debug ("Module list: $($namedModules -join ', ')") } } } } else { - "Cache file not found '$cacheFilePath'" | Write-DscTrace + Write-Verbose ("Cache file not found '$cacheFilePath'") $refreshCache = $true } if ($refreshCache) { - 'Constructing Get-DscResource cache' | Write-DscTrace + Write-Verbose ('Constructing Get-DscResource cache') # create a list object to store cache of Get-DscResource $dscResourceCacheEntries = [System.Collections.Generic.List[dscResourceCacheEntry]]::new() # 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)" + Write-Debug ("Named module count: $($namedModules.Count)") if ($namedModules.Count -gt 0) { - Write-DscTrace -Operation Debug "Modules specified, getting DSC resources from modules: $($namedModules -join ', ')" + Write-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)'" + Write-Debug ("Getting DSC resources for module '$($m | Out-String)'") $DscResources.AddRange(@(Get-DscResource -Module $m)) $Modules.AddRange(@(Get-Module -Name $m -ListAvailable)) } @@ -166,7 +151,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 { - Write-DscTrace -Operation Debug "No modules specified, getting all DSC resources" + Write-Debug ("No modules specified, getting all DSC resources") $DscResources = Get-DscResource $Modules = Get-Module -ListAvailable } @@ -183,7 +168,7 @@ function Invoke-DscCacheRefresh { if ( $psdscVersion -ge '2.0.7' ) { # only support known dscResourceType if ([dscResourceType].GetEnumNames() -notcontains $dscResource.ImplementationDetail) { - 'Implementation detail not found: ' + $dscResource.ImplementationDetail | Write-DscTrace -Operation Warn + Write-Warning ('Implementation detail not found: ' + $dscResource.ImplementationDetail) continue } } @@ -230,7 +215,7 @@ function Invoke-DscCacheRefresh { # workaround: Use GetTypeInstanceFromModule to get the type instance from the module and validate if it is a class-based resource $classBased = GetTypeInstanceFromModule -modulename $moduleName -classname $dscResource.Name -ErrorAction Ignore if ($classBased -and ($classBased.CustomAttributes.AttributeType.Name -eq 'DscResourceAttribute')) { - "Detected class-based resource: $($dscResource.Name) => Type: $($classBased.BaseType.FullName)" | Write-DscTrace + Write-Debug ("Detected class-based resource: $($dscResource.Name) => Type: $($classBased.BaseType.FullName)") $dscResourceInfo.ImplementationDetail = 'ClassBased' $properties = GetClassBasedProperties -filePath $dscResource.Path -className $dscResource.Name if ($null -ne $properties) { @@ -268,7 +253,7 @@ function Invoke-DscCacheRefresh { # save cache for future use # TODO: replace this with a high-performance serializer - "Saving Get-DscResource cache to '$cacheFilePath'" | Write-DscTrace + Write-Debug ("Saving Get-DscResource cache to '$cacheFilePath'") $jsonCache = $cache | ConvertTo-Json -Depth 90 New-Item -Force -Path $cacheFilePath -Value $jsonCache -Type File | Out-Null } @@ -321,13 +306,13 @@ function Invoke-DscOperation { ) $osVersion = [System.Environment]::OSVersion.VersionString - 'OS version: ' + $osVersion | Write-DscTrace + Write-Debug ("OS version: " + $osVersion) $psVersion = $PSVersionTable.PSVersion.ToString() - 'PowerShell version: ' + $psVersion | Write-DscTrace + Write-Debug ("PowerShell version: " + $psVersion) $moduleVersion = Get-Module PSDesiredStateConfiguration | ForEach-Object Version - 'PSDesiredStateConfiguration module version: ' + $moduleVersion | Write-DscTrace + Write-Debug ("PSDesiredStateConfiguration module version: " + $moduleVersion) # get details from cache about the DSC resource, if it exists $cachedDscResourceInfo = $dscResourceCache | Where-Object Type -EQ $DesiredState.type | ForEach-Object DscResourceInfo | Select-Object -First 1 @@ -343,7 +328,7 @@ function Invoke-DscOperation { if ($_.TypeNameOfValue -EQ 'System.String') { $addToActualState.$($_.Name) = $DesiredState.($_.Name) } } - 'DSC resource implementation: ' + [dscResourceType]$cachedDscResourceInfo.ImplementationDetail | Write-DscTrace + Write-Debug ("DSC resource implementation: " + [dscResourceType]$cachedDscResourceInfo.ImplementationDetail) # workaround: script based resources do not validate Get parameter consistency, so we need to remove any parameters the author chose not to include in Get-TargetResource switch ([dscResourceType]$cachedDscResourceInfo.ImplementationDetail) { @@ -351,7 +336,7 @@ function Invoke-DscOperation { # For Linux/MacOS, only class based resources are supported and are called directly. if ($IsLinux) { - 'Script based resources are only supported on Windows.' | Write-DscTrace -Operation Error + Write-Error 'Script based resources are only supported on Windows.' exit 1 } @@ -372,10 +357,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 - Write-DscTrace -Operation Debug -Message "Property type: $($validateProperty.PropertyType)" + Write-Debug ("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 + Write-Error ("Credential object '$($_.Name)' requires both 'username' and 'password' properties") exit 1 } $property.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) @@ -389,7 +374,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 | ConvertTo-Json -Compress)" + Write-Debug ("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,11 +387,11 @@ function Invoke-DscOperation { # set the properties of the OUTPUT object from the result of Get-TargetResource $addToActualState.properties = $ResultProperties } catch { - $_.Exception | Format-List * -Force | Out-String | Write-DscTrace -Operation Debug + Write-Debug ($_.Exception | Format-List * -Force | Out-String) if ($_.Exception.MessageId -eq 'DscResourceNotFound') { - Write-DscTrace -Operation Warn -Message 'For Windows PowerShell, DSC resources must be installed with scope AllUsers' + Write-Warning 'For Windows PowerShell, DSC resources must be installed with scope AllUsers' } - 'Exception: ' + $_.Exception.Message | Write-DscTrace -Operation Error + Write-Error ('Exception: ' + $_.Exception.Message) exit 1 } } @@ -418,7 +403,7 @@ function Invoke-DscOperation { $ValidProperties = $cachedDscResourceInfo.Properties.Name - $ValidProperties | ConvertTo-Json | Write-DscTrace -Operation Trace + Write-Debug ($ValidProperties | ConvertTo-Json) if ($DesiredState.properties) { # set each property of $dscResourceInstance to the value of the property in the $desiredState INPUT object @@ -426,10 +411,10 @@ 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 - Write-DscTrace -Operation Debug -Message "Property type: $($validateProperty.PropertyType)" + Write-Debug ("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 + Write-Error ("Credential object '$($_.Name)' requires both 'username' and 'password' properties") exit 1 } $dscResourceInstance.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) @@ -481,22 +466,22 @@ function Invoke-DscOperation { } } } catch { - $_.Exception | Format-List * -Force | Out-String | Write-DscTrace -Operation Debug + Write-Debug ($_.Exception | Format-List * -Force | Out-String) if ($_.Exception.MessageId -eq 'DscResourceNotFound') { - Write-DscTrace -Operation Warn -Message 'For Windows PowerShell, DSC resources must be installed with scope AllUsers' + Write-Warning 'For Windows PowerShell, DSC resources must be installed with scope AllUsers' } - 'Exception: ' + $_.Exception.Message | Write-DscTrace -Operation Error + Write-Error ('Exception: ' + $_.Exception.Message) exit 1 } } 'Binary' { if ($PSVersionTable.PSVersion.Major -gt 5) { - 'To use a binary resource such as File, Log, or SignatureValidation, use the Microsoft.Windows/WindowsPowerShell adapter.' | Write-DscTrace + Write-Debug 'To use a binary resource such as File, Log, or SignatureValidation, use the Microsoft.Windows/WindowsPowerShell adapter.' exit 1 } if (-not (($cachedDscResourceInfo.ImplementedAs -eq 'Binary') -and ('File', 'Log', 'SignatureValidation' -contains $cachedDscResourceInfo.Name))) { - 'Only File, Log, and SignatureValidation are supported as Binary resources.' | Write-DscTrace + Write-Debug 'Only File, Log, and SignatureValidation are supported as Binary resources.' exit 1 } @@ -504,10 +489,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 - Write-DscTrace -Operation Debug -Message "Property type: $($validateProperty.PropertyType)" + Write-Debug ("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 + Write-Error ("Credential object '$($_.Name)' requires both 'username' and 'password' properties") exit 1 } $property.$($_.Name) = [System.Management.Automation.PSCredential]::new($_.Value.Username, (ConvertTo-SecureString -AsPlainText $_.Value.Password -Force)) @@ -521,7 +506,7 @@ function Invoke-DscOperation { # 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)" + Write-Debug "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.$_ } @@ -533,12 +518,12 @@ function Invoke-DscOperation { # set the properties of the OUTPUT object from the result of Get-TargetResource $addToActualState.properties = $ResultProperties } catch { - 'Exception: ' + $_.Exception.Message | Write-DscTrace -Operation Error + Write-Error ('Exception: ' + $_.Exception.Message) exit 1 } } Default { - 'Can not find implementation of type: ' + $cachedDscResourceInfo.ImplementationDetail | Write-DscTrace + Write-Error ('Can not find implementation of type: ' + $cachedDscResourceInfo.ImplementationDetail) exit 1 } } @@ -546,7 +531,7 @@ function Invoke-DscOperation { return $addToActualState } else { $dsJSON = $DesiredState | ConvertTo-Json -Depth 10 - 'Can not find type "' + $DesiredState.type + '" for resource "' + $dsJSON + '". Please ensure that Get-DscResource returns this resource type.' | Write-DscTrace -Operation Error + Write-Error ('Can not find type "' + $DesiredState.type + '" for resource "' + $dsJSON + '". Please ensure that Get-DscResource returns this resource type.') exit 1 } } @@ -583,7 +568,7 @@ function ValidateMethod { } if ($null -eq $method) { - "Method '$operation' not implemented by resource '$($t.Name)'" | Write-DscTrace -Operation Error + Write-Error ("Method '$operation' not implemented by resource '$($t.Name)'") exit 1 } @@ -612,7 +597,7 @@ function GetClassBasedProperties { [System.Management.Automation.Language.ParseError[]] $errors = $null $ast = [System.Management.Automation.Language.Parser]::ParseFile($module.Path, [ref]$tokens, [ref]$errors) foreach ($e in $errors) { - $e | Out-String | Write-DscTrace -Operation Warn + $e | Out-String | Write-Warning } $typeDefinitions = $ast.FindAll( @@ -676,7 +661,7 @@ function GetClassBasedCapabilities { [System.Management.Automation.Language.ParseError[]] $errors = $null $ast = [System.Management.Automation.Language.Parser]::ParseFile($module, [ref]$tokens, [ref]$errors) foreach ($e in $errors) { - $e | Out-String | Write-DscTrace -Operation Error + $e | Out-String | Write-Error } $typeDefinitions = $ast.FindAll( @@ -720,7 +705,7 @@ function Repair-ValidPSModulePath { end { if (($env:PSModulePath -split [System.IO.Path]::PathSeparator) -contains '') { - "Removing empty entry from PSModulePath: '$env:PSModulePath'" | Write-DscTrace -Operation Debug + Write-Debug "Removing empty entry from PSModulePath: '$env:PSModulePath'" $env:PSModulePath = [String]::Join([System.IO.Path]::PathSeparator, ($env:PSModulePath.Split([System.IO.Path]::PathSeparator, [System.StringSplitOptions]::RemoveEmptyEntries))).TrimEnd([System.IO.Path]::PathSeparator) } } diff --git a/dsc/examples/powershell.dsc.yaml b/dsc/examples/powershell.dsc.yaml index 3bb47d62d..1706a6da7 100644 --- a/dsc/examples/powershell.dsc.yaml +++ b/dsc/examples/powershell.dsc.yaml @@ -4,18 +4,14 @@ metadata: Microsoft.DSC: securityContext: elevated resources: -- name: Use class PowerShell resources - type: Microsoft.Windows/WindowsPowerShell +- name: OpenSSH service + type: PsDesiredStateConfiguration/Service properties: - resources: - - name: OpenSSH service - type: PsDesiredStateConfiguration/Service - properties: - Name: sshd - - name: Administrator - type: PsDesiredStateConfiguration/User - properties: - UserName: administrator + Name: sshd +- name: Administrator + type: PsDesiredStateConfiguration/User + properties: + UserName: administrator - name: current user registry type: Microsoft.Windows/Registry properties: From 6e04aaf7b65f6cc41433cd98c8ad0a2545d38d5e Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Thu, 5 Feb 2026 16:00:53 -0800 Subject: [PATCH 02/15] fix haderrors to trace but ignore since anything in stderr sets it --- .../0.0.1/TestClassResource.psd1 | 2 +- .../0.0.1/TestClassResource.psm1 | 36 +++++++++++++++---- .../Tests/powershellgroup.resource.tests.ps1 | 22 ++++++------ .../psDscAdapter/powershell.resource.ps1 | 17 +++++---- 4 files changed, 53 insertions(+), 24 deletions(-) diff --git a/adapters/powershell/Tests/TestClassResource/0.0.1/TestClassResource.psd1 b/adapters/powershell/Tests/TestClassResource/0.0.1/TestClassResource.psd1 index ec4d3a17e..67d781ecc 100644 --- a/adapters/powershell/Tests/TestClassResource/0.0.1/TestClassResource.psd1 +++ b/adapters/powershell/Tests/TestClassResource/0.0.1/TestClassResource.psd1 @@ -34,7 +34,7 @@ VariablesToExport = @() AliasesToExport = @() # DSC resources to export from this module -DscResourcesToExport = @('TestClassResource', 'NoExport', 'FilteredExport') +DscResourcesToExport = @('TestClassResource', 'NoExport', 'FilteredExport', 'StreamResource') # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. PrivateData = @{ diff --git a/adapters/powershell/Tests/TestClassResource/0.0.1/TestClassResource.psm1 b/adapters/powershell/Tests/TestClassResource/0.0.1/TestClassResource.psm1 index ab9e3be51..517071aae 100644 --- a/adapters/powershell/Tests/TestClassResource/0.0.1/TestClassResource.psm1 +++ b/adapters/powershell/Tests/TestClassResource/0.0.1/TestClassResource.psm1 @@ -54,14 +54,10 @@ class TestClassResource : BaseTestClass [void] Set() { - Write-Host "This is a Host message" - Write-Information "This is an Information message" - Write-Error "This is an Error message" } [bool] Test() { - Write-Warning "This is a Warning message" if (($this.Name -eq "TestClassResource1") -and ($this.Prop1 -eq "ValueForProp1")) { return $true @@ -74,7 +70,6 @@ class TestClassResource : BaseTestClass [TestClassResource] Get() { - Write-Verbose "This is a Verbose message" if ($this.Name -eq "TestClassResource1") { $this.Prop1 = "ValueForProp1" @@ -93,7 +88,6 @@ class TestClassResource : BaseTestClass static [TestClassResource[]] Export() { - Write-Debug "This is a Debug message" $resultList = [List[TestClassResource]]::new() $resultCount = 5 if ($env:TestClassResourceResultCount) { @@ -207,6 +201,36 @@ class FilteredExport : BaseTestClass } } +[DscResource()] +class StreamResource : BaseTestClass +{ + [DscProperty(Key)] + [string] $Name + + [DscProperty()] + [string] $Prop1 + + [void] Set() + { + Write-Verbose "This is a Verbose message" + Write-Debug "This is a Debug message" + Write-Error "This is an Error message" + } + + [bool] Test() + { + Write-Host "This is a Host message" + Write-Information "This is an Information message" + return $true + } + + [StreamResource] Get() + { + Write-Warning "This is a Warning message" + return $this + } +} + function Test-World() { "Hello world from PSTestModule!" diff --git a/adapters/powershell/Tests/powershellgroup.resource.tests.ps1 b/adapters/powershell/Tests/powershellgroup.resource.tests.ps1 index 948192270..91ff83a59 100644 --- a/adapters/powershell/Tests/powershellgroup.resource.tests.ps1 +++ b/adapters/powershell/Tests/powershellgroup.resource.tests.ps1 @@ -115,10 +115,10 @@ Describe 'PowerShell adapter resource tests' { It 'Verify that ClearCache works in PSAdapter' { # generate the cache - $null = dsc resource list '*' -a Microsoft.DSC/PowerShell + $null = dsc resource list '*' -a Microsoft.DSC/PowerShell # call the ClearCache operation $scriptPath = Join-Path $PSScriptRoot '..' 'psDscAdapter' 'powershell.resource.ps1' - $null = & $scriptPath -Operation ClearCache + $null = & $scriptPath -Operation ClearCache 2>$null # verify that PSAdapter does not find the cache dsc -l debug resource list '*' -a Microsoft.DSC/PowerShell 2> $TestDrive/tracing.txt $LASTEXITCODE | Should -Be 0 @@ -380,44 +380,44 @@ Describe 'PowerShell adapter resource tests' { Context 'Tracing works' { It 'Error messages come from Write-Error' { - $null = dsc -l error resource set -r TestClassResource/TestClassResource -i '{"Name":"TestClassResource1"}' 2> $TestDrive/error.log + $null = dsc -l error resource set -r TestClassResource/StreamResource -i '{"Name":"TestClassResource1"}' 2> $TestDrive/error.log $logContent = Get-Content -Path $TestDrive/error.log -Raw $LASTEXITCODE | Should -Be 2 -Because $logContent $logContent | Should -Match 'ERROR .*? This is an Error message' -Because $logContent } It 'Warning messages come from Write-Warning' { - $null = "{'Name':'TestClassResource1','Prop1':'ValueForProp1'}" | dsc -l warn resource test -r 'TestClassResource/TestClassResource' -f - 2> $TestDrive/warning.log + $null = "{'Name':'TestClassResource1','Prop1':'ValueForProp1'}" | dsc -l warn resource get -r 'TestClassResource/StreamResource' -f - 2> $TestDrive/warning.log $logContent = Get-Content -Path $TestDrive/warning.log -Raw $LASTEXITCODE | Should -Be 0 -Because $logContent $logContent | Should -Match 'WARN .*? This is a Warning message' -Because $logContent } It 'Info messages come from Write-Host' { - $null = "{'Name':'TestClassResource1'}" | dsc -l info resource set -r 'TestClassResource/TestClassResource' -f - 2> $TestDrive/verbose.log + $null = "{'Name':'TestClassResource1'}" | dsc -l info resource test -r 'TestClassResource/StreamResource' -f - 2> $TestDrive/verbose.log $logContent = Get-Content -Path $TestDrive/verbose.log -Raw - $LASTEXITCODE | Should -Be 2 -Because $logContent + $LASTEXITCODE | Should -Be 0 -Because $logContent $logContent | Should -Match 'INFO .*? This is a Host message' -Because $logContent } It 'Debug messages come from Write-Verbose' { - $null = "{'Name':'TestClassResource1'}" | dsc -l debug resource get -r 'TestClassResource/TestClassResource' -f - 2> $TestDrive/debug.log + $null = "{'Name':'TestClassResource1'}" | dsc -l debug resource set -r 'TestClassResource/StreamResource' -f - 2> $TestDrive/debug.log $logContent = Get-Content -Path $TestDrive/debug.log -Raw - $LASTEXITCODE | Should -Be 0 -Because $logContent + $LASTEXITCODE | Should -Be 2 -Because $logContent $logContent | Should -Match 'DEBUG .*? This is a Verbose message' -Because $logContent } It 'Trace messages come from Write-Debug' { - $null = dsc -l trace resource export -r TestClassResource/TestClassResource 2> $TestDrive/trace.log + $null = dsc -l trace resource set -r TestClassResource/StreamResource -i '{"Name":"TestClassResource1"}' 2> $TestDrive/trace.log $logContent = Get-Content -Path $TestDrive/trace.log -Raw $LASTEXITCODE | Should -Be 2 -Because $logContent $logContent | Should -Match 'TRACE .*? This is a Debug message' -Because $logContent } It 'Trace messages come from Write-Information' { - $null = dsc -l trace resource set -r TestClassResource/TestClassResource -i '{"Name":"TestClassResource1"}' 2> $TestDrive/trace_info.log + $null = dsc -l trace resource test -r TestClassResource/StreamResource -i '{"Name":"TestClassResource1"}' 2> $TestDrive/trace_info.log $logContent = Get-Content -Path $TestDrive/trace_info.log -Raw - $LASTEXITCODE | Should -Be 2 -Because $logContent + $LASTEXITCODE | Should -Be 0 -Because $logContent $logContent | Should -Match 'TRACE .*? This is an Information message' -Because $logContent } } diff --git a/adapters/powershell/psDscAdapter/powershell.resource.ps1 b/adapters/powershell/psDscAdapter/powershell.resource.ps1 index 663e1c5c6..a7102ca51 100644 --- a/adapters/powershell/psDscAdapter/powershell.resource.ps1 +++ b/adapters/powershell/psDscAdapter/powershell.resource.ps1 @@ -59,8 +59,15 @@ $ps = [PowerShell]::Create().AddScript({ [string]$ScriptRoot ) + trap { + Write-Error ($_ | Format-List -Force | Out-String) + } + $DebugPreference = 'Continue' $VerbosePreference = 'Continue' + $ErrorActionPreference = 'Continue' + $InformationPreference = 'Continue' + $ProgressPreference = 'SilentlyContinue' if ($Operation -eq 'ClearCache') { $cacheFilePath = if ($IsWindows) { @@ -75,8 +82,8 @@ $ps = [PowerShell]::Create().AddScript({ } } - Remove-Item -Force -ea SilentlyContinue -Path $cacheFilePath - exit 0 + Remove-Item -Force -ErrorAction Ignore -Path $cacheFilePath + exit } # Adding some debug info to STDERR @@ -336,7 +343,6 @@ $traceLevel = if ($env:DSC_TRACE_LEVEL) { } else { [DscTraceLevel]::Warn } -Write-DscTrace -Operation Debug -Now -Message ("Trace level set to: $traceLevel") $null = Register-ObjectEvent -InputObject $ps.Streams.Error -EventName DataAdding -MessageData $traceQueue -Action { $traceQueue = $Event.MessageData @@ -393,9 +399,8 @@ try { Write-TraceQueue if ($ps.HadErrors) { - # If there are any errors, we will exit with an error code - Write-DscTrace -Now -Operation Error -Message 'Errors occurred during script execution.' - exit 1 + # Anything written to stderr sets this flag, so we'll write a debug trace, but not treat as error + Write-DscTrace -Now -Operation Debug -Message 'HadErrors set during script execution.' } foreach ($output in $outputCollection) { From 170fab8252b044091fc882b8fbdf26c0dbf93043 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 5 Feb 2026 16:21:57 -0800 Subject: [PATCH 03/15] fix how errors are detected in PS --- .../Tests/powershellgroup.config.tests.ps1 | 8 ++++---- .../powershell/psDscAdapter/powershell.resource.ps1 | 13 ++++++++++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/adapters/powershell/Tests/powershellgroup.config.tests.ps1 b/adapters/powershell/Tests/powershellgroup.config.tests.ps1 index f64ed376e..4a00a8316 100644 --- a/adapters/powershell/Tests/powershellgroup.config.tests.ps1 +++ b/adapters/powershell/Tests/powershellgroup.config.tests.ps1 @@ -99,10 +99,10 @@ Describe 'PowerShell adapter resource tests' { - name: Class-resource Info type: TestClassResource/NoExport '@ - $out = $yaml | dsc config export -f - 2>&1 | Out-String - $LASTEXITCODE | Should -Be 2 - $out | Should -Not -BeNullOrEmpty - $out | Should -BeLike "*ERROR*Export method not implemented by resource 'TestClassResource/NoExport'*" + $null = $yaml | dsc -l trace config export -f - 2>$TestDrive/error.log + $logContent = Get-Content -Raw -Path $TestDrive/error.log + $LASTEXITCODE | Should -Be 2 -Because $logContent + $logContent | Should -BeLike "*ERROR*Export method not implemented by resource 'TestClassResource/NoExport'*" } It 'Export works with filtered export property' { diff --git a/adapters/powershell/psDscAdapter/powershell.resource.ps1 b/adapters/powershell/psDscAdapter/powershell.resource.ps1 index a7102ca51..8386c760e 100644 --- a/adapters/powershell/psDscAdapter/powershell.resource.ps1 +++ b/adapters/powershell/psDscAdapter/powershell.resource.ps1 @@ -12,6 +12,7 @@ param( ) $traceQueue = [System.Collections.Concurrent.ConcurrentQueue[object]]::new() +$hadErrors = [System.Collections.Concurrent.ConcurrentQueue[bool]]::new() function Write-DscTrace { param( @@ -344,10 +345,11 @@ $traceLevel = if ($env:DSC_TRACE_LEVEL) { [DscTraceLevel]::Warn } -$null = Register-ObjectEvent -InputObject $ps.Streams.Error -EventName DataAdding -MessageData $traceQueue -Action { - $traceQueue = $Event.MessageData +$null = Register-ObjectEvent -InputObject $ps.Streams.Error -EventName DataAdding -MessageData @{traceQueue=$traceQueue; hadErrors=$hadErrors} -Action { + $traceQueue = $Event.MessageData.traceQueue # convert error to string since it's an ErrorRecord $traceQueue.Enqueue(@{ error = [string]$EventArgs.ItemAdded }) + $Event.MessageData.hadErrors.Enqueue($true) } if ($traceLevel -ge [DscTraceLevel]::Warn) { @@ -392,7 +394,7 @@ try { $asyncResult = $ps.BeginInvoke() while (-not $asyncResult.IsCompleted) { Write-TraceQueue - + Start-Sleep -Milliseconds 100 } $outputCollection = $ps.EndInvoke($asyncResult) @@ -416,6 +418,11 @@ finally { Get-EventSubscriber | Unregister-Event } +if ($hadErrors.Count -gt 0) { + Write-DscTrace -Now -Operation Error -Message 'Errors were captured during script execution. Check previous error traces for details.' + exit 1 +} + foreach ($obj in $outputObjects) { $obj | ConvertTo-Json -Depth 10 -Compress } From 17a2e3a472dddcb09445a602e29791f820de5be5 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 5 Feb 2026 16:34:25 -0800 Subject: [PATCH 04/15] fix ignoring errors --- adapters/powershell/psDscAdapter/psDscAdapter.psm1 | 4 ++-- adapters/powershell/psDscAdapter/win_psDscAdapter.psm1 | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/adapters/powershell/psDscAdapter/psDscAdapter.psm1 b/adapters/powershell/psDscAdapter/psDscAdapter.psm1 index 134c5d82c..c6997aef7 100644 --- a/adapters/powershell/psDscAdapter/psDscAdapter.psm1 +++ b/adapters/powershell/psDscAdapter/psDscAdapter.psm1 @@ -293,7 +293,7 @@ function Invoke-DscCacheRefresh { if (-not $refreshCache) { Write-Debug "Checking cache for stale PSModulePath" - $m = $env:PSModulePath -split [IO.Path]::PathSeparator | % { Get-ChildItem -Directory -Path $_ -Depth 1 -ea SilentlyContinue } + $m = $env:PSModulePath -split [IO.Path]::PathSeparator | % { Get-ChildItem -Directory -Path $_ -Depth 1 -ErrorAction Ignore } $hs_cache = [System.Collections.Generic.HashSet[string]]($cache.PSModulePaths) $hs_live = [System.Collections.Generic.HashSet[string]]($m.FullName) @@ -361,7 +361,7 @@ function Invoke-DscCacheRefresh { [dscResourceCache]$cache = [dscResourceCache]::new() $cache.ResourceCache = $dscResourceCacheEntries - $m = $env:PSModulePath -split [IO.Path]::PathSeparator | ForEach-Object { Get-ChildItem -Directory -Path $_ -Depth 1 -ea SilentlyContinue } + $m = $env:PSModulePath -split [IO.Path]::PathSeparator | ForEach-Object { Get-ChildItem -Directory -Path $_ -Depth 1 -ErrorAction Ignore } $cache.PSModulePaths = $m.FullName $cache.CacheSchemaVersion = $script:CurrentCacheSchemaVersion diff --git a/adapters/powershell/psDscAdapter/win_psDscAdapter.psm1 b/adapters/powershell/psDscAdapter/win_psDscAdapter.psm1 index 1b355216e..fbe9450ab 100644 --- a/adapters/powershell/psDscAdapter/win_psDscAdapter.psm1 +++ b/adapters/powershell/psDscAdapter/win_psDscAdapter.psm1 @@ -247,7 +247,7 @@ function Invoke-DscCacheRefresh { [dscResourceCache]$cache = [dscResourceCache]::new() $cache.ResourceCache = $dscResourceCacheEntries.ToArray() - $m = $env:PSModulePath -split [IO.Path]::PathSeparator | ForEach-Object { Get-ChildItem -Directory -Path $_ -Depth 1 -ea SilentlyContinue } + $m = $env:PSModulePath -split [IO.Path]::PathSeparator | ForEach-Object { Get-ChildItem -Directory -Path $_ -Depth 1 -ErrorAction Ignore } $cache.PSModulePaths = $m.FullName $cache.CacheSchemaVersion = $script:CurrentCacheSchemaVersion From c502a11932ff1da79676f27d2f99563f359e4cc2 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 5 Feb 2026 16:47:24 -0800 Subject: [PATCH 05/15] ignore more errors --- adapters/powershell/psDscAdapter/psDscAdapter.psm1 | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/adapters/powershell/psDscAdapter/psDscAdapter.psm1 b/adapters/powershell/psDscAdapter/psDscAdapter.psm1 index c6997aef7..c0d758ea4 100644 --- a/adapters/powershell/psDscAdapter/psDscAdapter.psm1 +++ b/adapters/powershell/psDscAdapter/psDscAdapter.psm1 @@ -16,9 +16,8 @@ function Get-DSCResourceModules { continue } - foreach ($moduleFolder in Get-ChildItem $folder -Directory) { - $addModule = $false - foreach ($psd1 in Get-ChildItem -Recurse -Filter "$($moduleFolder.Name).psd1" -Path $moduleFolder.fullname -Depth 2) { + foreach ($moduleFolder in Get-ChildItem $folder -Directory -ErrorAction Ignore) { + foreach ($psd1 in Get-ChildItem -Recurse -Filter "$($moduleFolder.Name).psd1" -Path $moduleFolder.fullname -Depth 2 -ErrorAction Ignore) { $containsDSCResource = select-string -LiteralPath $psd1 -pattern '^[^#]*\bDscResourcesToExport\b.*' if ($null -ne $containsDSCResource) { $dscModulePsd1List.Add($psd1) | Out-Null @@ -322,7 +321,7 @@ function Invoke-DscCacheRefresh { $DscResources = [System.Collections.Generic.List[DscResourceInfo]]::new() $dscResourceModulePsd1s = Get-DSCResourceModules if ($null -ne $dscResourceModulePsd1s) { - $modules = Get-Module -ListAvailable -Name ($dscResourceModulePsd1s) + $modules = Get-Module -ListAvailable -Name ($dscResourceModulePsd1s) -ErrorAction Ignore $processedModuleNames = @{} foreach ($mod in $modules) { if (-not ($processedModuleNames.ContainsKey($mod.Name))) { @@ -348,7 +347,7 @@ function Invoke-DscCacheRefresh { # fill in resource files (and their last-write-times) that will be used for up-do-date checks $lastWriteTimes = @{} - Get-ChildItem -Recurse -File -Path $dscResource.ParentPath -Include "*.ps1", "*.psd1", "*.psm1", "*.mof" -ea Ignore | ForEach-Object { + Get-ChildItem -Recurse -File -Path $dscResource.ParentPath -Include "*.ps1", "*.psd1", "*.psm1", "*.mof" -ErrorAction Ignore | ForEach-Object { $lastWriteTimes.Add($_.FullName, $_.LastWriteTime) } From 966a2cf85b143eb521ca5a0bd32afca04472e9ed Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 5 Feb 2026 17:26:59 -0800 Subject: [PATCH 06/15] change verbose to silent --- adapters/powershell/psDscAdapter/powershell.resource.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/powershell/psDscAdapter/powershell.resource.ps1 b/adapters/powershell/psDscAdapter/powershell.resource.ps1 index 8386c760e..f91abe5dd 100644 --- a/adapters/powershell/psDscAdapter/powershell.resource.ps1 +++ b/adapters/powershell/psDscAdapter/powershell.resource.ps1 @@ -65,7 +65,7 @@ $ps = [PowerShell]::Create().AddScript({ } $DebugPreference = 'Continue' - $VerbosePreference = 'Continue' + $VerbosePreference = 'SilentlyContinue' $ErrorActionPreference = 'Continue' $InformationPreference = 'Continue' $ProgressPreference = 'SilentlyContinue' From c399bbff64bd670a6979aee3ae394b890e3ac502 Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Thu, 5 Feb 2026 20:16:29 -0800 Subject: [PATCH 07/15] fix writing to stream --- .../Tests/win_powershell_cache.tests.ps1 | 6 +- .../psDscAdapter/powershell.resource.ps1 | 12 ++-- .../powershell/psDscAdapter/psDscAdapter.psm1 | 42 ++++++------ .../psDscAdapter/win_psDscAdapter.psm1 | 67 ++++++++++--------- 4 files changed, 66 insertions(+), 61 deletions(-) diff --git a/adapters/powershell/Tests/win_powershell_cache.tests.ps1 b/adapters/powershell/Tests/win_powershell_cache.tests.ps1 index 8f2ca4b58..a7b6a724e 100644 --- a/adapters/powershell/Tests/win_powershell_cache.tests.ps1 +++ b/adapters/powershell/Tests/win_powershell_cache.tests.ps1 @@ -88,12 +88,14 @@ Describe 'WindowsPowerShell adapter resource tests - requires elevated permissio 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' + $tracingContent = Get-Content -Path $TestDrive/tracing.txt | Out-String + $tracingContent | Should -BeLike '*Constructing Get-DscResource cache*' -Because $tracingContent # 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' + $tracingContent = Get-Content -Path $TestDrive/tracing.txt | Out-String + $tracingContent | Should -Not -BeLike '*Constructing Get-DscResource cache*' -Because $tracingContent } } diff --git a/adapters/powershell/psDscAdapter/powershell.resource.ps1 b/adapters/powershell/psDscAdapter/powershell.resource.ps1 index f91abe5dd..30c34e4bc 100644 --- a/adapters/powershell/psDscAdapter/powershell.resource.ps1 +++ b/adapters/powershell/psDscAdapter/powershell.resource.ps1 @@ -64,7 +64,7 @@ $ps = [PowerShell]::Create().AddScript({ Write-Error ($_ | Format-List -Force | Out-String) } - $DebugPreference = 'Continue' + $DebugPreference = 'SilentlyContinue' $VerbosePreference = 'SilentlyContinue' $ErrorActionPreference = 'Continue' $InformationPreference = 'Continue' @@ -88,9 +88,9 @@ $ps = [PowerShell]::Create().AddScript({ } # Adding some debug info to STDERR - Write-Debug ('PSVersion=' + $PSVersionTable.PSVersion.ToString()) - Write-Debug ('PSPath=' + $PSHome) - Write-Debug ('PSModulePath=' + $env:PSModulePath) + Write-Debug -Debug ('PSVersion=' + $PSVersionTable.PSVersion.ToString()) + Write-Debug -Debug ('PSPath=' + $PSHome) + Write-Debug -Debug ('PSModulePath=' + $env:PSModulePath) if ($PSVersionTable.PSVersion.Major -le 5) { # For Windows PowerShell, we want to remove any PowerShell 7 paths from PSModulePath @@ -105,7 +105,7 @@ $ps = [PowerShell]::Create().AddScript({ } if ('Validate' -ne $Operation) { - Write-Debug ("jsonInput=$jsonInput") + Write-Debug -Debug ("jsonInput=$jsonInput") # load private functions of psDscAdapter stub module if ($PSVersionTable.PSVersion.Major -le 5) { @@ -207,7 +207,7 @@ $ps = [PowerShell]::Create().AddScript({ } { @('Get','Set','Test','Export') -contains $_ } { if ($ResourceType) { - Write-Debug ("Using resource type override: $ResourceType") + Write-Debug -Debug ("Using resource type override: $ResourceType") $dscResourceCache = Invoke-DscCacheRefresh -Module $ResourceType.Split('/')[0] if ($null -eq $dscResourceCache) { Write-Error ("DSC resource '{0}' module not found." -f $ResourceType) diff --git a/adapters/powershell/psDscAdapter/psDscAdapter.psm1 b/adapters/powershell/psDscAdapter/psDscAdapter.psm1 index c0d758ea4..9ffeef026 100644 --- a/adapters/powershell/psDscAdapter/psDscAdapter.psm1 +++ b/adapters/powershell/psDscAdapter/psDscAdapter.psm1 @@ -90,7 +90,7 @@ function FindAndParseResourceDefinitions { return } - Write-Debug ("Loading resources from file '$filePath'") + Write-Debug -Debug ("Loading resources from file '$filePath'") #TODO: Ensure embedded instances in properties are working correctly [System.Management.Automation.Language.Token[]] $tokens = $null [System.Management.Automation.Language.ParseError[]] $errors = $null @@ -140,7 +140,7 @@ function GetExportMethod ($ResourceType, $HasFilterProperties, $ResourceTypeName $method = $null if ($HasFilterProperties) { - Write-Verbose "Properties provided for filtered export" + Write-Verbose -Verbose "Properties provided for filtered export" $method = foreach ($mt in $methods) { if ($mt.GetParameters().Count -gt 0) { $mt @@ -154,7 +154,7 @@ function GetExportMethod ($ResourceType, $HasFilterProperties, $ResourceTypeName } } else { - Write-Verbose "No properties provided, using parameterless export" + Write-Verbose -Verbose "No properties provided, using parameterless export" $method = foreach ($mt in $methods) { if ($mt.GetParameters().Count -eq 0) { $mt @@ -178,12 +178,12 @@ function LoadPowerShellClassResourcesFromModule { [PSModuleInfo]$moduleInfo ) - Write-Debug ("Loading resources from module '$($moduleInfo.Path)'") + Write-Debug -Debug ("Loading resources from module '$($moduleInfo.Path)'") if ($moduleInfo.RootModule) { if (".psm1", ".ps1" -notcontains ([System.IO.Path]::GetExtension($moduleInfo.RootModule)) -and (-not $moduleInfo.NestedModules)) { - Write-Debug ("RootModule is neither psm1 nor ps1 '$($moduleInfo.RootModule)'") + Write-Debug -Debug ("RootModule is neither psm1 nor ps1 '$($moduleInfo.RootModule)'") return [System.Collections.Generic.List[DscResourceInfo]]::new() } @@ -240,13 +240,13 @@ function Invoke-DscCacheRefresh { } if (Test-Path $cacheFilePath) { - Write-Verbose ("Reading from Get-DscResource cache file $cacheFilePath") + Write-Verbose -Verbose ("Reading from Get-DscResource cache file $cacheFilePath") $cache = Get-Content -Raw $cacheFilePath | ConvertFrom-Json if ($cache.CacheSchemaVersion -ne $script:CurrentCacheSchemaVersion) { $refreshCache = $true - Write-Verbose ("Incompatible version of cache in file '" + $cache.CacheSchemaVersion + "' (expected '" + $script:CurrentCacheSchemaVersion + "')") + Write-Verbose -Verbose ("Incompatible version of cache in file '" + $cache.CacheSchemaVersion + "' (expected '" + $script:CurrentCacheSchemaVersion + "')") } else { $dscResourceCacheEntries = $cache.ResourceCache @@ -255,10 +255,10 @@ function Invoke-DscCacheRefresh { # if there is nothing in the cache file - refresh cache $refreshCache = $true - Write-Debug "Filtered DscResourceCache cache is empty" + Write-Debug -Debug "Filtered DscResourceCache cache is empty" } else { - Write-Debug "Checking cache for stale entries" + Write-Debug -Debug "Checking cache for stale entries" foreach ($cacheEntry in $dscResourceCacheEntries) { @@ -274,13 +274,13 @@ function Invoke-DscCacheRefresh { $cache_LastWriteTime = $cache_LastWriteTime.AddTicks( - ($cache_LastWriteTime.Ticks % [TimeSpan]::TicksPerSecond)); if (-not ($file_LastWriteTime.Equals($cache_LastWriteTime))) { - Write-Debug ("Detected stale cache entry '$($_.Name)'") + Write-Debug -Debug ("Detected stale cache entry '$($_.Name)'") $refreshCache = $true break } } else { - Write-Debug ("Detected non-existent cache entry '$($_.Name)'") + Write-Debug -Debug ("Detected non-existent cache entry '$($_.Name)'") $refreshCache = $true break } @@ -290,7 +290,7 @@ function Invoke-DscCacheRefresh { } if (-not $refreshCache) { - Write-Debug "Checking cache for stale PSModulePath" + Write-Debug -Debug "Checking cache for stale PSModulePath" $m = $env:PSModulePath -split [IO.Path]::PathSeparator | % { Get-ChildItem -Directory -Path $_ -Depth 1 -ErrorAction Ignore } @@ -299,7 +299,7 @@ function Invoke-DscCacheRefresh { $hs_cache.SymmetricExceptWith($hs_live) $diff = $hs_cache - Write-Debug ("PSModulePath diff '$diff'") + Write-Debug -Debug ("PSModulePath diff '$diff'") if ($diff.Count -gt 0) { $refreshCache = $true } @@ -308,12 +308,12 @@ function Invoke-DscCacheRefresh { } } else { - Write-Verbose ("Cache file not found '$cacheFilePath'") + Write-Verbose -Verbose ("Cache file not found '$cacheFilePath'") $refreshCache = $true } if ($refreshCache) { - Write-Verbose "Constructing Get-DscResource cache" + Write-Verbose -Verbose "Constructing Get-DscResource cache" # create a list object to store cache of Get-DscResource [dscResourceCacheEntry[]]$dscResourceCacheEntries = [System.Collections.Generic.List[Object]]::new() @@ -330,7 +330,7 @@ function Invoke-DscCacheRefresh { # from several modules with the same name select the one with the highest version $selectedMod = $modules | Where-Object Name -EQ $mod.Name if ($selectedMod.Count -gt 1) { - Write-Debug ("Found $($selectedMod.Count) modules with name '$($mod.Name)'") + Write-Debug -Debug ("Found $($selectedMod.Count) modules with name '$($mod.Name)'") $selectedMod = $selectedMod | Sort-Object -Property Version -Descending | Select-Object -First 1 } @@ -366,7 +366,7 @@ function Invoke-DscCacheRefresh { # save cache for future use # TODO: replace this with a high-performance serializer - Write-Debug ("Saving Get-DscResource cache to '$cacheFilePath'") + Write-Debug -Debug ("Saving Get-DscResource cache to '$cacheFilePath'") $jsonCache = $cache | ConvertTo-Json -Depth 90 New-Item -Force -Path $cacheFilePath -Value $jsonCache -Type File | Out-Null } @@ -419,10 +419,10 @@ function Invoke-DscOperation { ) $osVersion = [System.Environment]::OSVersion.VersionString - Write-Debug ('OS version: ' + $osVersion) + Write-Debug -Debug ('OS version: ' + $osVersion) $psVersion = $PSVersionTable.PSVersion.ToString() - Write-Debug ('PowerShell version: ' + $psVersion) + Write-Debug -Debug ('PowerShell version: ' + $psVersion) # get details from cache about the DSC resource, if it exists $cachedDscResourceInfo = $dscResourceCache | Where-Object Type -EQ $DesiredState.type | ForEach-Object DscResourceInfo | Select-Object -First 1 @@ -449,7 +449,7 @@ function Invoke-DscOperation { $ValidProperties = $cachedDscResourceInfo.Properties.Name - Write-Debug ("Valid properties: " + ($ValidProperties | ConvertTo-Json)) + Write-Debug -Debug ("Valid properties: " + ($ValidProperties | ConvertTo-Json)) if ($DesiredState.properties) { # set each property of $dscResourceInstance to the value of the property in the $desiredState INPUT object @@ -542,7 +542,7 @@ function Invoke-DscOperation { } } - Write-Debug ("Output: $($addToActualState | ConvertTo-Json -Depth 10 -Compress)") + Write-Debug -Debug ("Output: $($addToActualState | ConvertTo-Json -Depth 10 -Compress)") return $addToActualState } else { diff --git a/adapters/powershell/psDscAdapter/win_psDscAdapter.psm1 b/adapters/powershell/psDscAdapter/win_psDscAdapter.psm1 index fbe9450ab..f30d7a98a 100644 --- a/adapters/powershell/psDscAdapter/win_psDscAdapter.psm1 +++ b/adapters/powershell/psDscAdapter/win_psDscAdapter.psm1 @@ -13,6 +13,9 @@ trap { # In Windows PowerShell, we should always use version 1.1 that ships in Windows. if ($PSVersionTable.PSVersion.Major -gt 5) { $m = Get-Module PSDesiredStateConfiguration -ListAvailable | Sort-Object -Descending | Select-Object -First 1 + if (-not $m) { + throw "PSDesiredStateConfiguration module not found. Please install PSDesiredStateConfiguration from PowerShell Gallery or ensure it is available in the PSModulePath." + } $PSDesiredStateConfiguration = Import-Module $m -Force -PassThru } else { $env:PSModulePath = "$env:windir\System32\WindowsPowerShell\v1.0\Modules;$env:PSModulePath" @@ -50,21 +53,21 @@ function Invoke-DscCacheRefresh { Repair-ValidPSModulePath if (Test-Path $cacheFilePath) { - Write-Verbose ("Reading from Get-DscResource cache file $cacheFilePath") + Write-Verbose -Verbose ("Reading from Get-DscResource cache file $cacheFilePath") $cache = Get-Content -Raw $cacheFilePath | ConvertFrom-Json if ($cache.CacheSchemaVersion -ne $script:CurrentCacheSchemaVersion) { $refreshCache = $true - Write-Verbose ("Incompatible version of cache in file '" + $cache.CacheSchemaVersion + "' (expected '" + $script:CurrentCacheSchemaVersion + "')") + Write-Verbose -Verbose ("Incompatible version of cache in file '" + $cache.CacheSchemaVersion + "' (expected '" + $script:CurrentCacheSchemaVersion + "')") } else { $dscResourceCacheEntries = $cache.ResourceCache if ($dscResourceCacheEntries.Count -eq 0) { # if there is nothing in the cache file - refresh cache $refreshCache = $true - Write-Debug ("Filtered DscResourceCache cache is empty") + Write-Debug -Debug ("Filtered DscResourceCache cache is empty") } else { - Write-Debug ("Checking cache for stale PSModulePath") + Write-Debug -Debug ("Checking cache for stale PSModulePath") $m = $env:PSModulePath -split [IO.Path]::PathSeparator | ForEach-Object { Get-ChildItem -Directory -Path $_ -Depth 1 -ErrorAction Ignore } $hs_cache = [System.Collections.Generic.HashSet[string]]($cache.PSModulePaths) @@ -72,14 +75,14 @@ function Invoke-DscCacheRefresh { $hs_cache.SymmetricExceptWith($hs_live) $diff = $hs_cache - Write-Debug ("PSModulePath diff '$diff'") + Write-Debug -Debug ("PSModulePath diff '$diff'") # TODO: Optimise for named module refresh if ($diff.Count -gt 0) { $refreshCache = $true } if (-not $refreshCache) { - Write-Debug ("Checking cache for stale entries") + Write-Debug -Debug ("Checking cache for stale entries") foreach ($cacheEntry in $dscResourceCacheEntries) { @@ -90,12 +93,12 @@ function Invoke-DscCacheRefresh { $cache_LastWriteTime = [long]$_.Value if ($file_LastWriteTime -ne $cache_LastWriteTime) { - Write-Debug ("Detected stale cache entry '$($_.Name)'") + Write-Debug -Debug ("Detected stale cache entry '$($_.Name)'") $namedModules.Add($cacheEntry.DscResourceInfo.ModuleName) break } } else { - Write-Debug ("Detected non-existent cache entry '$($_.Name)'") + Write-Debug -Debug ("Detected non-existent cache entry '$($_.Name)'") $namedModules.Add($cacheEntry.DscResourceInfo.ModuleName) break } @@ -108,31 +111,31 @@ function Invoke-DscCacheRefresh { $namedModules.AddRange(@($Module)) } $namedModules = $namedModules | Sort-Object -Unique - Write-Debug ("Module list: $($namedModules -join ', ')") + Write-Debug -Debug ("Module list: $($namedModules -join ', ')") } } } } else { - Write-Verbose ("Cache file not found '$cacheFilePath'") + Write-Verbose -Verbose ("Cache file not found '$cacheFilePath'") $refreshCache = $true } if ($refreshCache) { - Write-Verbose ('Constructing Get-DscResource cache') + Write-Verbose -Verbose ('Constructing Get-DscResource cache') # create a list object to store cache of Get-DscResource $dscResourceCacheEntries = [System.Collections.Generic.List[dscResourceCacheEntry]]::new() # 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-Debug ("Named module count: $($namedModules.Count)") + Write-Debug -Debug ("Named module count: $($namedModules.Count)") if ($namedModules.Count -gt 0) { - Write-Debug ("Modules specified, getting DSC resources from modules: $($namedModules -join ', ')") + Write-Debug -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-Debug ("Getting DSC resources for module '$($m | Out-String)'") + Write-Debug -Debug ("Getting DSC resources for module '$($m | Out-String)'") $DscResources.AddRange(@(Get-DscResource -Module $m)) $Modules.AddRange(@(Get-Module -Name $m -ListAvailable)) } @@ -151,7 +154,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 { - Write-Debug ("No modules specified, getting all DSC resources") + Write-Debug -Debug ("No modules specified, getting all DSC resources") $DscResources = Get-DscResource $Modules = Get-Module -ListAvailable } @@ -215,7 +218,7 @@ function Invoke-DscCacheRefresh { # workaround: Use GetTypeInstanceFromModule to get the type instance from the module and validate if it is a class-based resource $classBased = GetTypeInstanceFromModule -modulename $moduleName -classname $dscResource.Name -ErrorAction Ignore if ($classBased -and ($classBased.CustomAttributes.AttributeType.Name -eq 'DscResourceAttribute')) { - Write-Debug ("Detected class-based resource: $($dscResource.Name) => Type: $($classBased.BaseType.FullName)") + Write-Debug -Debug ("Detected class-based resource: $($dscResource.Name) => Type: $($classBased.BaseType.FullName)") $dscResourceInfo.ImplementationDetail = 'ClassBased' $properties = GetClassBasedProperties -filePath $dscResource.Path -className $dscResource.Name if ($null -ne $properties) { @@ -253,7 +256,7 @@ function Invoke-DscCacheRefresh { # save cache for future use # TODO: replace this with a high-performance serializer - Write-Debug ("Saving Get-DscResource cache to '$cacheFilePath'") + Write-Debug -Debug ("Saving Get-DscResource cache to '$cacheFilePath'") $jsonCache = $cache | ConvertTo-Json -Depth 90 New-Item -Force -Path $cacheFilePath -Value $jsonCache -Type File | Out-Null } @@ -306,13 +309,13 @@ function Invoke-DscOperation { ) $osVersion = [System.Environment]::OSVersion.VersionString - Write-Debug ("OS version: " + $osVersion) + Write-Debug -Debug ("OS version: " + $osVersion) $psVersion = $PSVersionTable.PSVersion.ToString() - Write-Debug ("PowerShell version: " + $psVersion) + Write-Debug -Debug ("PowerShell version: " + $psVersion) $moduleVersion = Get-Module PSDesiredStateConfiguration | ForEach-Object Version - Write-Debug ("PSDesiredStateConfiguration module version: " + $moduleVersion) + Write-Debug -Debug ("PSDesiredStateConfiguration module version: " + $moduleVersion) # get details from cache about the DSC resource, if it exists $cachedDscResourceInfo = $dscResourceCache | Where-Object Type -EQ $DesiredState.type | ForEach-Object DscResourceInfo | Select-Object -First 1 @@ -328,7 +331,7 @@ function Invoke-DscOperation { if ($_.TypeNameOfValue -EQ 'System.String') { $addToActualState.$($_.Name) = $DesiredState.($_.Name) } } - Write-Debug ("DSC resource implementation: " + [dscResourceType]$cachedDscResourceInfo.ImplementationDetail) + Write-Debug -Debug ("DSC resource implementation: " + [dscResourceType]$cachedDscResourceInfo.ImplementationDetail) # workaround: script based resources do not validate Get parameter consistency, so we need to remove any parameters the author chose not to include in Get-TargetResource switch ([dscResourceType]$cachedDscResourceInfo.ImplementationDetail) { @@ -357,7 +360,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-Debug ("Property type: $($validateProperty.PropertyType)") + Write-Debug -Debug ("Property type: $($validateProperty.PropertyType)") if ($validateProperty -and $validateProperty.PropertyType -eq '[PSCredential]') { if (-not $_.Value.Username -or -not $_.Value.Password) { Write-Error ("Credential object '$($_.Name)' requires both 'username' and 'password' properties") @@ -374,7 +377,7 @@ function Invoke-DscOperation { # using the cmdlet the appropriate dsc module, and handle errors try { - Write-Debug ("Module: $($cachedDscResourceInfo.ModuleName), Name: $($cachedDscResourceInfo.Name), Property: $($property | ConvertTo-Json -Compress)") + Write-Debug -Debug ("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') { @@ -387,7 +390,7 @@ function Invoke-DscOperation { # set the properties of the OUTPUT object from the result of Get-TargetResource $addToActualState.properties = $ResultProperties } catch { - Write-Debug ($_.Exception | Format-List * -Force | Out-String) + Write-Debug -Debug ($_.Exception | Format-List * -Force | Out-String) if ($_.Exception.MessageId -eq 'DscResourceNotFound') { Write-Warning 'For Windows PowerShell, DSC resources must be installed with scope AllUsers' } @@ -403,7 +406,7 @@ function Invoke-DscOperation { $ValidProperties = $cachedDscResourceInfo.Properties.Name - Write-Debug ($ValidProperties | ConvertTo-Json) + Write-Debug -Debug ($ValidProperties | ConvertTo-Json) if ($DesiredState.properties) { # set each property of $dscResourceInstance to the value of the property in the $desiredState INPUT object @@ -411,7 +414,7 @@ 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 - Write-Debug ("Property type: $($validateProperty.PropertyType)") + Write-Debug -Debug ("Property type: $($validateProperty.PropertyType)") if ($validateProperty.PropertyType -eq 'PSCredential') { if (-not $_.Value.Username -or -not $_.Value.Password) { Write-Error ("Credential object '$($_.Name)' requires both 'username' and 'password' properties") @@ -466,7 +469,7 @@ function Invoke-DscOperation { } } } catch { - Write-Debug ($_.Exception | Format-List * -Force | Out-String) + Write-Debug -Debug ($_.Exception | Format-List * -Force | Out-String) if ($_.Exception.MessageId -eq 'DscResourceNotFound') { Write-Warning 'For Windows PowerShell, DSC resources must be installed with scope AllUsers' } @@ -476,12 +479,12 @@ function Invoke-DscOperation { } 'Binary' { if ($PSVersionTable.PSVersion.Major -gt 5) { - Write-Debug 'To use a binary resource such as File, Log, or SignatureValidation, use the Microsoft.Windows/WindowsPowerShell adapter.' + Write-Debug -Debug 'To use a binary resource such as File, Log, or SignatureValidation, use the Microsoft.Windows/WindowsPowerShell adapter.' exit 1 } if (-not (($cachedDscResourceInfo.ImplementedAs -eq 'Binary') -and ('File', 'Log', 'SignatureValidation' -contains $cachedDscResourceInfo.Name))) { - Write-Debug 'Only File, Log, and SignatureValidation are supported as Binary resources.' + Write-Debug -Debug 'Only File, Log, and SignatureValidation are supported as Binary resources.' exit 1 } @@ -489,7 +492,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-Debug ("Property type: $($validateProperty.PropertyType)") + Write-Debug -Debug ("Property type: $($validateProperty.PropertyType)") if ($validateProperty.PropertyType -eq '[PSCredential]') { if (-not $_.Value.Username -or -not $_.Value.Password) { Write-Error ("Credential object '$($_.Name)' requires both 'username' and 'password' properties") @@ -506,7 +509,7 @@ function Invoke-DscOperation { # using the cmdlet from PSDesiredStateConfiguration module in Windows try { - Write-Debug "Module: $($cachedDscResourceInfo.ModuleName), Name: $($cachedDscResourceInfo.Name), Property: $($property | ConvertTo-Json -Compress)" + Write-Debug -Debug "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.$_ } @@ -705,7 +708,7 @@ function Repair-ValidPSModulePath { end { if (($env:PSModulePath -split [System.IO.Path]::PathSeparator) -contains '') { - Write-Debug "Removing empty entry from PSModulePath: '$env:PSModulePath'" + Write-Debug -Debug "Removing empty entry from PSModulePath: '$env:PSModulePath'" $env:PSModulePath = [String]::Join([System.IO.Path]::PathSeparator, ($env:PSModulePath.Split([System.IO.Path]::PathSeparator, [System.StringSplitOptions]::RemoveEmptyEntries))).TrimEnd([System.IO.Path]::PathSeparator) } } From 72fc9c0d0e84293b38fd920369656370e207844b Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Thu, 5 Feb 2026 21:19:01 -0800 Subject: [PATCH 08/15] fix test resource tracing --- .../Tests/TestClassResource/0.0.1/TestClassResource.psm1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adapters/powershell/Tests/TestClassResource/0.0.1/TestClassResource.psm1 b/adapters/powershell/Tests/TestClassResource/0.0.1/TestClassResource.psm1 index 517071aae..a6ea35030 100644 --- a/adapters/powershell/Tests/TestClassResource/0.0.1/TestClassResource.psm1 +++ b/adapters/powershell/Tests/TestClassResource/0.0.1/TestClassResource.psm1 @@ -212,8 +212,8 @@ class StreamResource : BaseTestClass [void] Set() { - Write-Verbose "This is a Verbose message" - Write-Debug "This is a Debug message" + Write-Verbose -Verbose "This is a Verbose message" + Write-Debug -Debug "This is a Debug message" Write-Error "This is an Error message" } From b6f52f03fa841fb0a90330f46219188972df1e00 Mon Sep 17 00:00:00 2001 From: "Steve Lee (POWERSHELL HE/HIM) (from Dev Box)" Date: Thu, 5 Feb 2026 21:39:34 -0800 Subject: [PATCH 09/15] add ignore to test-path --- adapters/powershell/psDscAdapter/psDscAdapter.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/powershell/psDscAdapter/psDscAdapter.psm1 b/adapters/powershell/psDscAdapter/psDscAdapter.psm1 index 9ffeef026..02bd10bf4 100644 --- a/adapters/powershell/psDscAdapter/psDscAdapter.psm1 +++ b/adapters/powershell/psDscAdapter/psDscAdapter.psm1 @@ -12,7 +12,7 @@ function Get-DSCResourceModules { $listPSModuleFolders = $env:PSModulePath.Split([IO.Path]::PathSeparator) $dscModulePsd1List = [System.Collections.Generic.HashSet[System.String]]::new() foreach ($folder in $listPSModuleFolders) { - if (!(Test-Path $folder)) { + if (!(Test-Path $folder -ErrorAction Ignore)) { continue } From 3101acd948778b10c3418280f4b66150f1e972f2 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Thu, 5 Feb 2026 22:27:40 -0800 Subject: [PATCH 10/15] set erroractionpreference to stop --- adapters/powershell/psDscAdapter/psDscAdapter.psm1 | 1 + adapters/powershell/psDscAdapter/win_psDscAdapter.psm1 | 1 + 2 files changed, 2 insertions(+) diff --git a/adapters/powershell/psDscAdapter/psDscAdapter.psm1 b/adapters/powershell/psDscAdapter/psDscAdapter.psm1 index 02bd10bf4..244f2c67b 100644 --- a/adapters/powershell/psDscAdapter/psDscAdapter.psm1 +++ b/adapters/powershell/psDscAdapter/psDscAdapter.psm1 @@ -2,6 +2,7 @@ # Licensed under the MIT License. $script:CurrentCacheSchemaVersion = 3 +$ErrorActionPreference = 'Stop' function Import-PSDSCModule { $m = Get-Module PSDesiredStateConfiguration -ListAvailable | Sort-Object -Descending | Select-Object -First 1 diff --git a/adapters/powershell/psDscAdapter/win_psDscAdapter.psm1 b/adapters/powershell/psDscAdapter/win_psDscAdapter.psm1 index f30d7a98a..4cd32a98b 100644 --- a/adapters/powershell/psDscAdapter/win_psDscAdapter.psm1 +++ b/adapters/powershell/psDscAdapter/win_psDscAdapter.psm1 @@ -2,6 +2,7 @@ # Licensed under the MIT License. $global:ProgressPreference = 'SilentlyContinue' +$ErrorActionPreference = 'Stop' $script:CurrentCacheSchemaVersion = 1 trap { From 18058a84d650c3170afba70e69a3bcc8cbe2b648 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Fri, 6 Feb 2026 07:00:25 -0800 Subject: [PATCH 11/15] Update adapters/powershell/psDscAdapter/powershell.resource.ps1 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- adapters/powershell/psDscAdapter/powershell.resource.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/powershell/psDscAdapter/powershell.resource.ps1 b/adapters/powershell/psDscAdapter/powershell.resource.ps1 index 30c34e4bc..14933ef47 100644 --- a/adapters/powershell/psDscAdapter/powershell.resource.ps1 +++ b/adapters/powershell/psDscAdapter/powershell.resource.ps1 @@ -34,7 +34,7 @@ function Write-DscTrace { } trap { - Write-DscTrace -Operation Error -Message ($_ | Format-List -Force | Out-String) + Write-DscTrace -Operation Error -Message ($_ | Format-List -Force | Out-String) -Now } function Write-TraceQueue() { From 77014afe95c4ee90848637ad4eb7d6af1665f4ea Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Fri, 6 Feb 2026 07:00:44 -0800 Subject: [PATCH 12/15] Update adapters/powershell/psDscAdapter/powershell.resource.ps1 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- adapters/powershell/psDscAdapter/powershell.resource.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/adapters/powershell/psDscAdapter/powershell.resource.ps1 b/adapters/powershell/psDscAdapter/powershell.resource.ps1 index 14933ef47..bf28e5b90 100644 --- a/adapters/powershell/psDscAdapter/powershell.resource.ps1 +++ b/adapters/powershell/psDscAdapter/powershell.resource.ps1 @@ -339,6 +339,7 @@ $traceLevel = if ($env:DSC_TRACE_LEVEL) { try { [DscTraceLevel]$env:DSC_TRACE_LEVEL } catch { + Write-DscTrace -Operation Warn -Message ("Invalid DSC_TRACE_LEVEL value '{0}'. Defaulting to 'Warn'." -f $env:DSC_TRACE_LEVEL) -Now [DscTraceLevel]::Warn } } else { From 9b40538e7d042fe1a0b9061ffcc9f86fee657530 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Fri, 6 Feb 2026 07:01:25 -0800 Subject: [PATCH 13/15] Update adapters/powershell/psDscAdapter/powershell.resource.ps1 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../psDscAdapter/powershell.resource.ps1 | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/adapters/powershell/psDscAdapter/powershell.resource.ps1 b/adapters/powershell/psDscAdapter/powershell.resource.ps1 index bf28e5b90..8c4660f64 100644 --- a/adapters/powershell/psDscAdapter/powershell.resource.ps1 +++ b/adapters/powershell/psDscAdapter/powershell.resource.ps1 @@ -64,6 +64,21 @@ $ps = [PowerShell]::Create().AddScript({ Write-Error ($_ | Format-List -Force | Out-String) } + # NOTE: + # The adapter explicitly suppresses Debug and Verbose output by default to avoid + # excessively noisy logs from DSC resources that call Write-Debug / Write-Verbose. + # As a result, plain: + # Write-Verbose "message" + # Write-Debug "message" + # will NOT emit any output when invoked through this adapter. + # + # Resource authors who need to guarantee that diagnostic output is emitted MUST + # use the -Verbose / -Debug switches, for example: + # Write-Verbose -Verbose "message" + # Write-Debug -Debug "message" + # + # See the adapter's own usage below (e.g. Write-Debug -Debug 'PSVersion=...') for + # an example of this pattern. $DebugPreference = 'SilentlyContinue' $VerbosePreference = 'SilentlyContinue' $ErrorActionPreference = 'Continue' From 3a275e8cdfeb8d96073bec586b07dd7eed801b3e Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Fri, 6 Feb 2026 07:01:54 -0800 Subject: [PATCH 14/15] Update adapters/powershell/psDscAdapter/powershell.resource.ps1 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- adapters/powershell/psDscAdapter/powershell.resource.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/adapters/powershell/psDscAdapter/powershell.resource.ps1 b/adapters/powershell/psDscAdapter/powershell.resource.ps1 index 8c4660f64..9e2ebf9b6 100644 --- a/adapters/powershell/psDscAdapter/powershell.resource.ps1 +++ b/adapters/powershell/psDscAdapter/powershell.resource.ps1 @@ -434,6 +434,8 @@ finally { Get-EventSubscriber | Unregister-Event } +# Allow any remaining event handlers time to enqueue to $traceQueue and $hadErrors +Start-Sleep -Milliseconds 200 if ($hadErrors.Count -gt 0) { Write-DscTrace -Now -Operation Error -Message 'Errors were captured during script execution. Check previous error traces for details.' exit 1 From df9771951b32e7823972fba3e5fb106aed05887c Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Fri, 6 Feb 2026 13:34:07 -0800 Subject: [PATCH 15/15] change write-information to log to info and fix clearcache to use return --- .../powershell/Tests/powershellgroup.resource.tests.ps1 | 4 ++-- adapters/powershell/psDscAdapter/powershell.resource.ps1 | 9 ++------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/adapters/powershell/Tests/powershellgroup.resource.tests.ps1 b/adapters/powershell/Tests/powershellgroup.resource.tests.ps1 index 91ff83a59..f2d964f71 100644 --- a/adapters/powershell/Tests/powershellgroup.resource.tests.ps1 +++ b/adapters/powershell/Tests/powershellgroup.resource.tests.ps1 @@ -115,7 +115,7 @@ Describe 'PowerShell adapter resource tests' { It 'Verify that ClearCache works in PSAdapter' { # generate the cache - $null = dsc resource list '*' -a Microsoft.DSC/PowerShell + $null = dsc resource list '*' -a Microsoft.DSC/PowerShell # call the ClearCache operation $scriptPath = Join-Path $PSScriptRoot '..' 'psDscAdapter' 'powershell.resource.ps1' $null = & $scriptPath -Operation ClearCache 2>$null @@ -418,7 +418,7 @@ Describe 'PowerShell adapter resource tests' { $null = dsc -l trace resource test -r TestClassResource/StreamResource -i '{"Name":"TestClassResource1"}' 2> $TestDrive/trace_info.log $logContent = Get-Content -Path $TestDrive/trace_info.log -Raw $LASTEXITCODE | Should -Be 0 -Because $logContent - $logContent | Should -Match 'TRACE .*? This is an Information message' -Because $logContent + $logContent | Should -Match 'INFO .*? This is an Information message' -Because $logContent } } } diff --git a/adapters/powershell/psDscAdapter/powershell.resource.ps1 b/adapters/powershell/psDscAdapter/powershell.resource.ps1 index 9e2ebf9b6..a5e7eba40 100644 --- a/adapters/powershell/psDscAdapter/powershell.resource.ps1 +++ b/adapters/powershell/psDscAdapter/powershell.resource.ps1 @@ -99,7 +99,7 @@ $ps = [PowerShell]::Create().AddScript({ } Remove-Item -Force -ErrorAction Ignore -Path $cacheFilePath - exit + return } # Adding some debug info to STDERR @@ -379,12 +379,7 @@ if ($traceLevel -ge [DscTraceLevel]::Info) { $null = Register-ObjectEvent -InputObject $ps.Streams.Information -EventName DataAdding -MessageData $traceQueue -Action { $traceQueue = $Event.MessageData if ($null -ne $EventArgs.ItemAdded.MessageData) { - if ($EventArgs.ItemAdded.Tags -contains 'PSHOST') { - $traceQueue.Enqueue(@{ info = $EventArgs.ItemAdded.MessageData.ToString() }) - } else { - $traceQueue.Enqueue(@{ trace = $EventArgs.ItemAdded.MessageData.ToString() }) - } - return + $traceQueue.Enqueue(@{ info = $EventArgs.ItemAdded.MessageData.ToString() }) } } }