diff --git a/src/dsc/psresourceget.ps1 b/src/dsc/psresourceget.ps1 index 9c8752722..57fe1dbcf 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,67 @@ 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 + } else { + $data['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 +210,56 @@ 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 + } else { + $data['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 +300,31 @@ 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 + } + if ($this._exist) { + $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 } } @@ -568,9 +653,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" @@ -861,6 +949,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, @@ -915,24 +1019,32 @@ function PopulatePSResourceListObject { $allPSResources = @() - $allPSResources += $allUsersPSResources | ForEach-Object { - return [PSResource]::new( - $_.Name, - $_.Version, - [Scope]"AllUsers", - $_.Repository, - $_.PreRelease ? $true : $false - ) + if (@($allUsersPSResources).count -gt 0) { + $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 0) { + $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 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": {