From 445b675ac09a708070072a2b83135a99cfaf16c2 Mon Sep 17 00:00:00 2001 From: Mikey Lombardi Date: Mon, 14 Sep 2026 16:10:59 -0500 Subject: [PATCH 1/4] (DSC) Idiomaticize resource schemas Prior to this change, there were a few issues with the DSC resource schemas regarding the idiomatic definitions and semantics of JSON schema: 1. Optional properties should not define the `type` keyword with `null` as a valid data type unless `null` is an explicitly modeled state for the property. In JSON Schema, the absence of a property in an object is semantically distinct from a property explicitly set to `null`. Instead, we control the optionality of properties through the `required` or `dependentRequired` keywords. 1. The schema for `Microsoft.PowerShell.PSResourceGet/Repository` didn't define `required` at the top-level. Instead, it used the `allOf` keyword to indicate whether the `name` and `uri` properties are required or just `name`. When a property is always required, we should hoist it to the top-level `required` keyword array. We can still use the `allOf` to conditionally extend the schema as needed. Parsing a JSON Schema is difficult for consumers, moreso when it requires unrolling schema composition. We should surface as much information to the consumer in the simplest form possible. To address these issues, this change: 1. Removes `null` from the `type` keyword for optional properties that don't explicitly model `null` as a valid state. 1. Updates the `Microsoft.PowerShell.PSResourceGet/Repository` schema to define `required` at the top-level to indicate that `name` is always required and simplifies the `allOf` keyword for the remaining case (when `_exist` is `true` then `uri` is required). --- src/dsc/psresourcelist.dsc.resource.json | 10 +++++----- src/dsc/repository.dsc.resource.json | 11 +++-------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/dsc/psresourcelist.dsc.resource.json b/src/dsc/psresourcelist.dsc.resource.json index 6681c9d72..99ccce8de 100644 --- a/src/dsc/psresourcelist.dsc.resource.json +++ b/src/dsc/psresourcelist.dsc.resource.json @@ -106,7 +106,7 @@ "repositoryName": { "title": "Repository Name", "description": "The name of the repository from where the resources are acquired.", - "type": ["string", "null"] + "type": "string" }, "trustedRepository": { "title": "Trusted Repository", @@ -129,7 +129,7 @@ }, "$defs": { "Scope": { - "type": ["string", "null"], + "type": "string", "title": "Scope", "description": "Scope of the resource installation.", "enum": [ @@ -147,12 +147,12 @@ "name": { "title": "Name", "description": "The name of the resource.", - "type": ["string", "null"] + "type": "string" }, "version": { "title": "Version", "description": "The version range of the resource.", - "type": ["string", "null"] + "type": "string" }, "scope": { "title": "Scope", @@ -162,7 +162,7 @@ "repositoryName": { "title": "Repository Name", "description": "The name of the repository from where the resource is acquired.", - "type": ["string", "null"] + "type": "string" }, "preRelease": { "title": "Pre-Release version", diff --git a/src/dsc/repository.dsc.resource.json b/src/dsc/repository.dsc.resource.json index d5a7c4218..7babf4631 100644 --- a/src/dsc/repository.dsc.resource.json +++ b/src/dsc/repository.dsc.resource.json @@ -74,23 +74,18 @@ "description": "A PowerShell Resource repository from where to acquire the resources.", "type": "object", "additionalProperties": false, + "required": ["name"], "allOf": [ { "if": { "properties": { "_exist": { - "const": false + "const": true } } }, "then": { "required": [ - "name" - ] - }, - "else": { - "required": [ - "name", "uri" ] } @@ -105,7 +100,7 @@ "uri": { "title": "URI", "description": "The URI of the repository.", - "type": ["string", "null"], + "type": "string", "format": "uri" }, "trusted": { From 8df04863c095cc8ef144b18e5d29f566f3e2c891 Mon Sep 17 00:00:00 2001 From: Mikey Lombardi Date: Mon, 14 Sep 2026 16:22:05 -0500 Subject: [PATCH 2/4] (DSC) Update resource script for idiomatic serialization Prior to this change, the resource script serialized the data by just converting the instances to JSON (stripping `_inDesiredState` for non-test operations). This caused the resource to serialize `null` for properties that weren't defined on the instance, causing a mismatch with the idiomatic schema. This change updates the serialization logic to omit undefined optional fields from the serialized output by: 1. Defining the `ToData` method to convert instances to a data representation that omits undefined optional fields while preserving property order for predictable serialization. The method defines two overloads: - `ToData([bool$forTest)`: Converts the instance to a data representation, omitting undefined optional fields. If `$forTest` is `$false`, the method doesn't insert `_inDesiredState`. - `ToData()`: Convenience overload to call `ToData($false)`. 1. Standardizing the serialization trace messaging and extracting into reusable methods. --- src/dsc/psresourceget.ps1 | 159 ++++++++++++++++++++++++++++---------- 1 file changed, 119 insertions(+), 40 deletions(-) diff --git a/src/dsc/psresourceget.ps1 b/src/dsc/psresourceget.ps1 index 9c8752722..d09e42c4c 100644 --- a/src/dsc/psresourceget.ps1 +++ b/src/dsc/psresourceget.ps1 @@ -1,6 +1,8 @@ ## Copyright (c) Microsoft Corporation. All rights reserved. ## Licensed under the MIT License. +using namespace System.Collections.Specialized + [CmdletBinding()] param( [Parameter(Mandatory = $true)] @@ -99,19 +101,65 @@ class PSResource { return $retValue } + [OrderedDictionary] ToData([bool]$forTest) { + $data = [OrderedDictionary]::new() + + $data.name = $this.name + if (-not [string]::IsNullOrEmpty($this.version)) { + $data.version = $this.version + } + $data.scope = $this.scope + if (-not [string]::IsNullOrEmpty($this.repositoryName)) { + $data.repositoryName = $this.repositoryName + } + $data.preRelease = $this.preRelease + $data._exist = $this._exist + + if ($forTest) { + $data._inDesiredState = $this._inDesiredState + } + + if ($null -ne $this._metadata) { + $data._metadata = $this._metadata + } + + return $data + } + + [OrderedDictionary] ToData() { + return $this.ToData($false) + } + [string] ToJson() { - [string[]]$excludeProps = @('_inDesiredState') - if ($null -eq $this._metadata) { $excludeProps += '_metadata' } - $retVal = ($this | Select-Object -ExcludeProperty $excludeProps | ConvertTo-Json -Compress -EnumsAsStrings) - Write-Trace -message "Serializing PSResource to JSON. Name: $($this.name), Version: $($this.version), Scope: $($this.scope), RepositoryName: $($this.repositoryName), PreRelease: $($this.preRelease), _exist: $($this._exist)" -level debug - Write-Trace -message "Serialized JSON: $retVal" -level trace + $retVal = $this.ToData() | ConvertTo-Json -Compress -EnumsAsStrings + $this.WriteSerializationTrace($retVal) return $retVal } [string] ToJsonForTest() { - [string[]]$excludeProps = @() - if ($null -eq $this._metadata) { $excludeProps += '_metadata' } - return ($this | Select-Object -ExcludeProperty $excludeProps | ConvertTo-Json -Compress -Depth 5 -EnumsAsStrings) + $retVal = $this.ToData($true) | ConvertTo-Json -Compress -Depth 5 -EnumsAsStrings + $this.WriteSerializationTrace($retVal, $true) + return $retVal + } + + [void] WriteSerializationTrace([string]$json, [bool]$forTest) { + $pairs = @( + "Name: $($this.name)" + "Version: $($this.version)" + "Scope: $($this.scope)" + "RepositoryName: $($this.repositoryName)" + "PreRelease: $($this.preRelease)" + "_exist: $($this._exist)" + ) + if ($forTest) { + $pairs += "_inDesiredState: $($this._inDesiredState)" + } + Write-Trace -message "Serializing PSResource to JSON. $($pairs -join ', ')" -level debug + Write-Trace -message "Serialized JSON: $json" -level trace + } + + [void] WriteSerializationTrace([string]$json) { + $this.WriteSerializationTrace($json, $false) } } @@ -160,44 +208,54 @@ class PSResourceList { return $true } - [string] ToJson() { - ## Assign the array directly so that an empty list serializes as [] rather than null - [object[]]$resourceObjects = @() + [OrderedDictionary] ToData([bool]$forTest) { + $data = [OrderedDictionary]::new() + if (-not [string]::IsNullOrEmpty($this.repositoryName)) { + $data['repositoryName'] = $this.repositoryName + } if ($this.resources) { - $resourceObjects = @($this.resources | ForEach-Object { - [string[]]$excludeProps = @('_inDesiredState') - if ($null -eq $_._metadata) { $excludeProps += '_metadata' } - $_ | Select-Object -ExcludeProperty $excludeProps - }) - } - $retVal = [ordered]@{ - repositoryName = $this.repositoryName - resources = $resourceObjects - } | ConvertTo-Json -Compress -Depth 5 -EnumsAsStrings - Write-Trace -message "Serializing PSResourceList to JSON. RepositoryName: $($this.repositoryName), TrustedRepository: $($this.trustedRepository), Resources count: $($this.resources.Count)" -level debug - Write-Trace -message "Serialized JSON: $retVal" -level trace + [OrderedDictionary[]] $resourceData = $this.resources | ForEach-Object { $_.ToData($forTest) } + $data['resources'] = $resourceData + } + if ($this.trustedRepository) { + $data['trustedRepository'] = $this.trustedRepository + } + if ($forTest) { + $data['_inDesiredState'] = $this._inDesiredState + } + return $data + } + [OrderedDictionary] ToData() { + return $this.ToData($false) + } + + [string] ToJson() { + $retVal = $this.ToData() | ConvertTo-Json -Compress -EnumsAsStrings + $this.WriteSerializationTrace($retVal) + return $retVal } [string] ToJsonForTest() { - Write-Trace -message "Serializing PSResourceList to JSON for test output. RepositoryName: $($this.repositoryName), TrustedRepository: $($this.trustedRepository), Resources count: $($this.resources.Count)" -level debug - [object[]]$resourceObjects = @() - if ($this.resources) { - $resourceObjects = @($this.resources | ForEach-Object { - [string[]]$excludeProps = @() - if ($null -eq $_._metadata) { $excludeProps += '_metadata' } - if ($excludeProps.Count -gt 0) { $_ | Select-Object -ExcludeProperty $excludeProps } else { $_ } - }) - } - $retVal = [ordered]@{ - repositoryName = $this.repositoryName - resources = $resourceObjects - trustedRepository = $this.trustedRepository - _inDesiredState = $this._inDesiredState - } | ConvertTo-Json -Compress -Depth 5 -EnumsAsStrings - Write-Trace -message "Serialized JSON: $retVal" -level trace + $retVal = $this.ToData($true) | ConvertTo-Json -Compress -EnumsAsStrings + $this.WriteSerializationTrace($retVal, $true) return $retVal } + + [void] WriteSerializationTrace([string]$json, [bool]$forTest) { + $preamble = 'Serializing PSResourceList to JSON' + $preamble += $forTest ? ' for test output.' : '.' + $pairs = @( + "repositoryName: $($this.repositoryName)" + "trustedRepository: $($this.trustedRepository)" + "resourcesCount: $($this.resources.Count)" + ) + Write-Trace -message "$preamble $($pairs -join ', ')" -level debug + Write-Trace -message "Serialized JSON: $json" -level trace + } + [void] WriteSerializationTrace([string]$json) { + $this.WriteSerializationTrace($json, $false) + } } class Repository { @@ -238,8 +296,29 @@ class Repository { $this.repositoryType = 'Unknown' } + [OrderedDictionary] ToData([bool]$forTest) { + $data = [OrderedDictionary]::new() + $data['name'] = $this.name + if (-not [string]::IsNullOrEmpty($this.uri)) { + $data['uri'] = $this.uri + } + $data['trusted'] = $this.trusted + $data['priority'] = $this.priority + if (-not [string]::IsNullOrEmpty($this.repositoryType)) { + $data['repositoryType'] = $this.repositoryType + } + $data['_exist'] = $this._exist + + + return $data + } + [OrderedDictionary] ToData() { + return $this.ToData($false) + } + [string] ToJson() { - return ($this | ConvertTo-Json -Compress -EnumsAsStrings) + $retVal = $this.ToData() | ConvertTo-Json -Compress -EnumsAsStrings + return $retVal } } From 4fd5eeeab0a94bf92678baf4a5adba5dc0f17f53 Mon Sep 17 00:00:00 2001 From: Mikey Lombardi Date: Mon, 14 Sep 2026 16:29:26 -0500 Subject: [PATCH 3/4] (DSC) Handle when a scope has no installed resources Prior to this change, the `PSResourceList` resource would fail during `export` operations when a scope had no installed resources because it would emit an empty/invalid item. The non-terminating error for no resources in a scope was also erroneously emitted. This change: 1. Defines the `GetAllPsResourcesInScope` function as a wrapper around `Get-PsResource` to retrieve all resources in a given scope, returning an empty array if no resources are found and erroring if the command fails for any other reason. 1. Updates `PopulatePSResourceListObject` to handle empty resource arrays gracefully, emitting an info message instead of invalid data. --- src/dsc/psresourceget.ps1 | 65 +++++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/src/dsc/psresourceget.ps1 b/src/dsc/psresourceget.ps1 index d09e42c4c..cff3e9b3c 100644 --- a/src/dsc/psresourceget.ps1 +++ b/src/dsc/psresourceget.ps1 @@ -647,9 +647,12 @@ function ExportOperation { exit [ExitCode]::ExportNotImplemented } 'psresourcelist' { - $currentUserPSResources = Get-PSResource - $allUsersPSResources = Get-PSResource -Scope AllUsers - PopulatePSResourceListObject -allUsersPSResources $allUsersPSResources -currentUserPSResources $currentUserPSResources + $populatingParams = @{ + currentUserPSResources = GetAllPsResourcesInScope -Scope CurrentUser + allUsersPSResources = GetAllPsResourcesInScope -Scope AllUsers + } + + PopulatePSResourceListObject @populatingParams } default { Write-Trace -level error -message "Unknown ResourceType: $ResourceType" @@ -940,6 +943,22 @@ function DeleteOperation { } } +function GetAllPsResourcesInScope { + param( + [Scope]$Scope + ) + + try { + Get-PSResource -Scope $Scope -ErrorAction Stop + } catch [Microsoft.PowerShell.PSResourceGet.UtilClasses.ResourceNotFoundException] { + # Everything is fine, there's just no installed resources + @() + } catch { + Write-Trace -level error -message "Failed to get PSResources for '$Scope' scope: $_" + @() + } +} + function PopulatePSResourceListObjectByRepository { param ( $resourcesExist, @@ -994,24 +1013,32 @@ function PopulatePSResourceListObject { $allPSResources = @() - $allPSResources += $allUsersPSResources | ForEach-Object { - return [PSResource]::new( - $_.Name, - $_.Version, - [Scope]"AllUsers", - $_.Repository, - $_.PreRelease ? $true : $false - ) + if ($allUsersPSResources.count -gt 1) { + $allPSResources += $allUsersPSResources | ForEach-Object { + return [PSResource]::new( + $_.Name, + $_.Version, + [Scope]"AllUsers", + $_.Repository, + $_.PreRelease ? $true : $false + ) + } + } else { + Write-Trace -level info "No PSResources found for AllUsers scope." } - $allPSResources += $currentUserPSResources | ForEach-Object { - return [PSResource]::new( - $_.Name, - $_.Version, - [Scope]"CurrentUser", - $_.Repository, - $_.PreRelease ? $true : $false - ) + if ($currentUserPSResources.count -gt 1) { + $allPSResources += $currentUserPSResources | ForEach-Object { + return [PSResource]::new( + $_.Name, + $_.Version, + [Scope]"CurrentUser", + $_.Repository, + $_.PreRelease ? $true : $false + ) + } + } else { + Write-Trace -level info "No PSResources found for CurrentUser scope." } $repoGrps = $allPSResources | Group-Object -Property repositoryName From b07a7932e403ec5fa32b3784b01917fcaa0c177d Mon Sep 17 00:00:00 2001 From: Mikey Lombardi Date: Mon, 19 Oct 2026 11:20:14 -0500 Subject: [PATCH 4/4] Address review comments --- src/dsc/psresourceget.ps1 | 41 +++++++++------- src/dsc/repository.dsc.resource.json | 15 +++--- .../PSResourceGetDSCResource.Tests.ps1 | 48 +++++++++++++++---- 3 files changed, 74 insertions(+), 30 deletions(-) diff --git a/src/dsc/psresourceget.ps1 b/src/dsc/psresourceget.ps1 index cff3e9b3c..7d403c5e3 100644 --- a/src/dsc/psresourceget.ps1 +++ b/src/dsc/psresourceget.ps1 @@ -104,23 +104,25 @@ class PSResource { [OrderedDictionary] ToData([bool]$forTest) { $data = [OrderedDictionary]::new() - $data.name = $this.name + $data['name'] = $this.name if (-not [string]::IsNullOrEmpty($this.version)) { - $data.version = $this.version + $data['version'] = $this.version } - $data.scope = $this.scope + $data['scope'] = $this.scope if (-not [string]::IsNullOrEmpty($this.repositoryName)) { - $data.repositoryName = $this.repositoryName + $data['repositoryName'] = $this.repositoryName + } else { + $data['repositoryName'] = "" } - $data.preRelease = $this.preRelease - $data._exist = $this._exist + $data['preRelease'] = $this.preRelease + $data['_exist'] = $this._exist if ($forTest) { - $data._inDesiredState = $this._inDesiredState + $data['_inDesiredState'] = $this._inDesiredState } if ($null -ne $this._metadata) { - $data._metadata = $this._metadata + $data['_metadata'] = $this._metadata } return $data @@ -131,7 +133,7 @@ class PSResource { } [string] ToJson() { - $retVal = $this.ToData() | ConvertTo-Json -Compress -EnumsAsStrings + $retVal = $this.ToData() | ConvertTo-Json -Compress -Depth 5 -EnumsAsStrings $this.WriteSerializationTrace($retVal) return $retVal } @@ -212,6 +214,8 @@ class PSResourceList { $data = [OrderedDictionary]::new() if (-not [string]::IsNullOrEmpty($this.repositoryName)) { $data['repositoryName'] = $this.repositoryName + } else { + $data['repositoryName'] = "" } if ($this.resources) { [OrderedDictionary[]] $resourceData = $this.resources | ForEach-Object { $_.ToData($forTest) } @@ -230,14 +234,14 @@ class PSResourceList { } [string] ToJson() { - $retVal = $this.ToData() | ConvertTo-Json -Compress -EnumsAsStrings + $retVal = $this.ToData() | ConvertTo-Json -Compress -Depth 5 -EnumsAsStrings $this.WriteSerializationTrace($retVal) return $retVal } [string] ToJsonForTest() { - $retVal = $this.ToData($true) | ConvertTo-Json -Compress -EnumsAsStrings + $retVal = $this.ToData($true) | ConvertTo-Json -Compress -Depth 5 -EnumsAsStrings $this.WriteSerializationTrace($retVal, $true) return $retVal } @@ -302,8 +306,10 @@ class Repository { if (-not [string]::IsNullOrEmpty($this.uri)) { $data['uri'] = $this.uri } - $data['trusted'] = $this.trusted - $data['priority'] = $this.priority + if ($this._exist) { + $data['trusted'] = $this.trusted + $data['priority'] = $this.priority + } if (-not [string]::IsNullOrEmpty($this.repositoryType)) { $data['repositoryType'] = $this.repositoryType } @@ -317,7 +323,7 @@ class Repository { } [string] ToJson() { - $retVal = $this.ToData() | ConvertTo-Json -Compress -EnumsAsStrings + $retVal = $this.ToData() | ConvertTo-Json -Compress -EnumsAsStrings -Depth 5 return $retVal } } @@ -863,6 +869,9 @@ function SetOperation { if ($property -eq 'repositoryType') { $splatt['ApiVersion'] = $inputObj.$property } + elseif ($property -eq 'uri' -and $inputObj._exist -and $inputObj.name -eq 'PSGallery') { + continue + } else { $splatt[$property] = $inputObj.$property } @@ -1013,7 +1022,7 @@ function PopulatePSResourceListObject { $allPSResources = @() - if ($allUsersPSResources.count -gt 1) { + if (@($allUsersPSResources).count -gt 0) { $allPSResources += $allUsersPSResources | ForEach-Object { return [PSResource]::new( $_.Name, @@ -1027,7 +1036,7 @@ function PopulatePSResourceListObject { Write-Trace -level info "No PSResources found for AllUsers scope." } - if ($currentUserPSResources.count -gt 1) { + if (@($currentUserPSResources).count -gt 0) { $allPSResources += $currentUserPSResources | ForEach-Object { return [PSResource]::new( $_.Name, diff --git a/src/dsc/repository.dsc.resource.json b/src/dsc/repository.dsc.resource.json index 7babf4631..12c1c9246 100644 --- a/src/dsc/repository.dsc.resource.json +++ b/src/dsc/repository.dsc.resource.json @@ -77,17 +77,20 @@ "required": ["name"], "allOf": [ { + "$comment": "If the repository name is not `PSGallery` (case-insensitive) and `_exist` is `true` or undefined, then the 'uri' property is required.", "if": { - "properties": { - "_exist": { - "const": true + "not": { + "properties": { + "name": { "pattern": "(?i)^PSGallery$" } } + }, + "properties": { + "_exist": { "const": true } } }, "then": { - "required": [ - "uri" - ] + "required": ["uri"], + "errorMessage": "The `uri` property is required when `name` is not `PSGallery` (case-insensitive) and `_exist` is not defined as `false`" } } ], diff --git a/test/DscResource/PSResourceGetDSCResource.Tests.ps1 b/test/DscResource/PSResourceGetDSCResource.Tests.ps1 index fafc91006..974cf6282 100644 --- a/test/DscResource/PSResourceGetDSCResource.Tests.ps1 +++ b/test/DscResource/PSResourceGetDSCResource.Tests.ps1 @@ -56,7 +56,11 @@ Describe "DSC resource schema tests" -tags 'CI' { $skipTest = $null -eq (Get-Command -Type Application -Name pwsh -ErrorAction SilentlyContinue) if ($skipTest) { - $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $originalDefaultParameterValues = if ($null -ne $PSDefaultParameterValues) { + $PSDefaultParameterValues.Clone() + } else { + @{} + } $PSDefaultParameterValues['it:skip'] = $skipTest return } @@ -105,7 +109,11 @@ Describe 'Repository Resource Tests' -Tags 'CI' { $skipTest = $null -eq (Get-Command -Type Application -Name pwsh -ErrorAction SilentlyContinue) if ($skipTest) { - $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $originalDefaultParameterValues = if ($null -ne $PSDefaultParameterValues) { + $PSDefaultParameterValues.Clone() + } else { + @{} + } $PSDefaultParameterValues['it:skip'] = $skipTest return } @@ -207,7 +215,11 @@ Describe "PSResourceList Resource Tests" -Tags 'CI' { $skipTest = $null -eq (Get-Command -Type Application -Name pwsh -ErrorAction SilentlyContinue) if ($skipTest) { - $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $originalDefaultParameterValues = if ($null -ne $PSDefaultParameterValues) { + $PSDefaultParameterValues.Clone() + } else { + @{} + } $PSDefaultParameterValues['it:skip'] = $skipTest return } @@ -451,7 +463,11 @@ Describe 'E2E tests for Repository resource' -Tags 'CI' { $skipTest = $null -eq (Get-Command -Type Application -Name pwsh -ErrorAction SilentlyContinue) if ($skipTest) { - $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $originalDefaultParameterValues = if ($null -ne $PSDefaultParameterValues) { + $PSDefaultParameterValues.Clone() + } else { + @{} + } $PSDefaultParameterValues['it:skip'] = $skipTest return } @@ -512,7 +528,11 @@ Describe 'E2E tests for PSResourceList resource' -Tags 'CI' { $skipTest = $null -eq (Get-Command -Type Application -Name pwsh -ErrorAction SilentlyContinue) if ($skipTest) { - $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $originalDefaultParameterValues = if ($null -ne $PSDefaultParameterValues) { + $PSDefaultParameterValues.Clone() + } else { + @{} + } $PSDefaultParameterValues['it:skip'] = $skipTest return } @@ -523,7 +543,11 @@ Describe 'E2E tests for PSResourceList resource' -Tags 'CI' { ## Therefore, skipping these tests on Windows PowerShell is necessary because the modules installed by DSC will not be visible in that environment. $isOnWindowsPowerShell = $PSVersionTable.PSVersion.Major -lt 6 - $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $originalDefaultParameterValues = if ($null -ne $PSDefaultParameterValues) { + $PSDefaultParameterValues.Clone() + } else { + @{} + } $PSDefaultParameterValues['it:skip'] = $isOnWindowsPowerShell } @@ -636,7 +660,11 @@ Describe "Error code tests" -Tags 'CI' { $skipTest = $null -eq (Get-Command -Type Application -Name pwsh -ErrorAction SilentlyContinue) if ($skipTest) { - $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $originalDefaultParameterValues = if ($null -ne $PSDefaultParameterValues) { + $PSDefaultParameterValues.Clone() + } else { + @{} + } $PSDefaultParameterValues['it:skip'] = $skipTest return } @@ -677,7 +705,11 @@ Describe 'PSResourceList what-if tests' -Tags 'CI' { SetupTestRepos $isOnWindowsPowerShell = $PSVersionTable.PSVersion.Major -lt 6 - $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $originalDefaultParameterValues = if ($null -ne $PSDefaultParameterValues) { + $PSDefaultParameterValues.Clone() + } else { + @{} + } $PSDefaultParameterValues['it:skip'] = $isOnWindowsPowerShell }