Support/Package/Lifecycle/Eigenverft.Manifested.Package.Package.AssignmentPlan.ps1
|
<#
Eigenverft.Manifested.Package.Package.AssignmentPlan Shared dependency-plan entry point and mutation-free assignment preview enrichment. #> function New-PackageAssignmentPlanIssue { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateSet('Warning', 'Blocker')] [string]$Severity, [Parameter(Mandatory = $true)] [string]$Code, [Parameter(Mandatory = $true)] [string]$Message, [AllowNull()] [string]$NodeKey = $null, [AllowNull()] [string]$DefinitionId = $null, [AllowNull()] [string]$ArtifactFileId = $null, [AllowNull()] [string]$ExpectedDepotPath = $null ) return [pscustomobject]@{ Severity = $Severity Code = $Code Message = $Message NodeKey = $NodeKey DefinitionId = $DefinitionId ArtifactFileId = $ArtifactFileId ExpectedDepotPath = $ExpectedDepotPath } } function Test-PackageAssignmentArchiveEntry { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$ArchivePath, [Parameter(Mandatory = $true)] [string]$EntryPath, [AllowNull()] [psobject]$Verification, [switch]$VerifyContent ) $normalizedEntryPath = $EntryPath.Replace('\', '/').TrimStart('/') Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction SilentlyContinue try { $archive = [System.IO.Compression.ZipFile]::OpenRead($ArchivePath) try { $entryMatches = @($archive.Entries | Where-Object { [string]::Equals($_.FullName.Replace('\', '/').TrimStart('/'), $normalizedEntryPath, [System.StringComparison]::OrdinalIgnoreCase) }) if ($entryMatches.Count -ne 1 -or [string]::IsNullOrWhiteSpace([string]$entryMatches[0].Name)) { return [pscustomobject]@{ Accepted = $false; Status = 'ArchiveEntryMissing'; Verified = $false; ExpectedHash = $null; ActualHash = $null } } if (-not $VerifyContent.IsPresent) { return [pscustomobject]@{ Accepted = $true; Status = 'ArchiveEntryPresentNotVerified'; Verified = $false; ExpectedHash = $null; ActualHash = $null } } $algorithm = if ($Verification -and $Verification.PSObject.Properties['algorithm']) { ([string]$Verification.algorithm).ToLowerInvariant() } else { 'sha256' } $expectedHash = if ($Verification -and $Verification.PSObject.Properties[$algorithm]) { ([string]$Verification.$algorithm).Trim().ToLowerInvariant() } elseif ($Verification -and $Verification.PSObject.Properties['value']) { ([string]$Verification.value).Trim().ToLowerInvariant() } else { $null } if ([string]::IsNullOrWhiteSpace($expectedHash)) { return [pscustomobject]@{ Accepted = $true; Status = 'ArchiveEntryVerificationDeferred'; Verified = $false; ExpectedHash = $null; ActualHash = $null } } $hashAlgorithm = switch -Exact ($algorithm) { 'sha256' { [System.Security.Cryptography.SHA256]::Create(); break } 'sha512' { [System.Security.Cryptography.SHA512]::Create(); break } default { $null } } if (-not $hashAlgorithm) { return [pscustomobject]@{ Accepted = $false; Status = 'VerificationAlgorithmUnsupported'; Verified = $false; ExpectedHash = $expectedHash; ActualHash = $null } } try { $stream = $entryMatches[0].Open() try { $actualHash = (($hashAlgorithm.ComputeHash($stream) | ForEach-Object { $_.ToString('x2') }) -join '') } finally { $stream.Dispose() } } finally { $hashAlgorithm.Dispose() } return [pscustomobject]@{ Accepted = [string]::Equals($expectedHash, $actualHash, [System.StringComparison]::OrdinalIgnoreCase) Status = if ([string]::Equals($expectedHash, $actualHash, [System.StringComparison]::OrdinalIgnoreCase)) { 'VerificationPassed' } else { 'VerificationFailed' } Verified = $true ExpectedHash = $expectedHash ActualHash = $actualHash } } finally { $archive.Dispose() } } catch { return [pscustomobject]@{ Accepted = $false; Status = 'ArchiveInspectionFailed'; Verified = $false; ExpectedHash = $null; ActualHash = $null; ErrorMessage = $_.Exception.Message } } } function Resolve-PackageAssignmentArtifactPreview { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageResult, [Parameter(Mandatory = $true)] [psobject]$ArtifactFile, [Parameter(Mandatory = $true)] [hashtable]$Resolved, [Parameter(Mandatory = $true)] [hashtable]$Visiting, [switch]$VerifyDepotContent ) $artifactFileId = [string]$ArtifactFile.Id if ($Resolved.ContainsKey($artifactFileId)) { return $Resolved[$artifactFileId] } if ($Visiting.ContainsKey($artifactFileId)) { throw "Artifact acquisition cycle encountered while planning '$artifactFileId'." } $Visiting[$artifactFileId] = $true $candidatePreviews = New-Object System.Collections.Generic.List[object] $selectedCandidate = $null $availablePath = $null $fileStatus = 'Missing' foreach ($candidate in @($ArtifactFile.AcquisitionPlan.Candidates)) { $kind = [string]$candidate.kind $candidatePreview = $null switch -Exact ($kind) { 'packageDepot' { $sourceId = if ($candidate.sourceRef) { [string]$candidate.sourceRef.id } else { $null } $resolvedPath = $null $exists = $false $verification = $null try { $source = Get-PackageSourceDefinition -PackageConfig $PackageResult.PackageConfig -SourceRef $candidate.sourceRef if (-not [string]::IsNullOrWhiteSpace([string]$source.BasePath)) { $resolvedPath = [System.IO.Path]::GetFullPath((Join-Path ([string]$source.BasePath) ([string]$candidate.sourcePath))) $exists = Test-Path -LiteralPath $resolvedPath -PathType Leaf } if ($exists -and $VerifyDepotContent.IsPresent) { $verification = Test-PackageSavedFile -Path $resolvedPath -Verification $candidate.verification } } catch { $candidatePreview = [pscustomobject]@{ Kind = $kind; SourceId = $sourceId; Source = $resolvedPath; Reachability = 'Local'; Status = 'InvalidSource' Exists = $false; Verification = $null; ErrorMessage = $_.Exception.Message } } if (-not $candidatePreview) { $accepted = $exists -and ((-not $VerifyDepotContent.IsPresent) -or [bool]$verification.Accepted) $candidatePreview = [pscustomobject]@{ Kind = $kind; SourceId = $sourceId; Source = $resolvedPath; Reachability = 'Local' Status = if (-not $exists) { 'Missing' } elseif ($VerifyDepotContent.IsPresent -and -not $verification.Accepted) { 'Invalid' } elseif ($VerifyDepotContent.IsPresent) { 'Verified' } else { 'PresentNotVerified' } Exists = $exists; Verification = $verification; ErrorMessage = $null } if ($accepted -and -not $selectedCandidate) { $selectedCandidate = $candidatePreview $availablePath = $resolvedPath $fileStatus = 'Available' } } } 'vendorDownload' { $sourceId = if ($candidate.sourceRef) { [string]$candidate.sourceRef.id } else { 'directDownload' } $sourceText = if (-not [string]::IsNullOrWhiteSpace([string]$candidate.url)) { [string]$candidate.url } else { [string]$candidate.sourcePath } $candidatePreview = [pscustomobject]@{ Kind = $kind; SourceId = $sourceId; Source = $sourceText; Reachability = 'NotTested'; Status = 'ResolvableOnline' Exists = $null; Verification = $candidate.verification; ErrorMessage = $null } if (-not $selectedCandidate) { $selectedCandidate = $candidatePreview $fileStatus = 'ResolvableOnline' } } 'archiveEntry' { $sourceArtifact = Get-PackageArtifactFileResult -PackageResult $PackageResult -ArtifactFileId ([string]$candidate.sourceArtifactFileId) $sourcePreview = if ($sourceArtifact) { Resolve-PackageAssignmentArtifactPreview -PackageResult $PackageResult -ArtifactFile $sourceArtifact -Resolved $Resolved -Visiting $Visiting -VerifyDepotContent:$VerifyDepotContent } else { $null } $entryCheck = $null if ($sourcePreview -and -not [string]::IsNullOrWhiteSpace([string]$sourcePreview.AvailablePath)) { $entryCheck = Test-PackageAssignmentArchiveEntry -ArchivePath ([string]$sourcePreview.AvailablePath) -EntryPath ([string]$candidate.entryPath) -Verification $candidate.verification -VerifyContent:$VerifyDepotContent } $sourceFeasible = $sourcePreview -and [string]$sourcePreview.Status -in @('Available', 'ResolvableOnline', 'Derivable') $accepted = $sourceFeasible -and ((-not $entryCheck) -or [bool]$entryCheck.Accepted) $candidatePreview = [pscustomobject]@{ Kind = $kind; SourceId = [string]$candidate.sourceArtifactFileId; Source = [string]$candidate.entryPath Reachability = if ($entryCheck) { 'LocalArchive' } else { 'DependsOnArtifact' } Status = if (-not $sourceArtifact) { 'MissingSourceArtifact' } elseif (-not $sourceFeasible) { 'SourceArtifactUnavailable' } elseif ($entryCheck -and -not $entryCheck.Accepted) { [string]$entryCheck.Status } elseif ($entryCheck) { [string]$entryCheck.Status } else { 'Derivable' } Exists = if ($entryCheck) { [bool]$entryCheck.Accepted } else { $null }; Verification = $entryCheck; ErrorMessage = if ($entryCheck -and $entryCheck.PSObject.Properties['ErrorMessage']) { [string]$entryCheck.ErrorMessage } else { $null } } if ($accepted -and -not $selectedCandidate) { $selectedCandidate = $candidatePreview $fileStatus = 'Derivable' } } default { $candidatePreview = [pscustomobject]@{ Kind = $kind; SourceId = $null; Source = $null; Reachability = 'Unknown'; Status = 'Unsupported' Exists = $null; Verification = $null; ErrorMessage = "Unsupported artifact acquisition candidate kind '$kind'." } } } $candidatePreviews.Add($candidatePreview) | Out-Null } if ($PackageResult.Offline) { foreach ($rawCandidate in @($ArtifactFile.AcquisitionCandidates | Where-Object { [string]$_.kind -notin @('packageDepot', 'archiveEntry') })) { $candidatePreviews.Add([pscustomobject]@{ Kind = [string]$rawCandidate.kind SourceId = if ($rawCandidate.PSObject.Properties['sourceId']) { [string]$rawCandidate.sourceId } else { 'directDownload' } Source = if ($rawCandidate.PSObject.Properties['url']) { [string]$rawCandidate.url } elseif ($rawCandidate.PSObject.Properties['sourcePath']) { [string]$rawCandidate.sourcePath } else { $null } Reachability = 'DisabledOffline'; Status = 'DisabledOffline'; Exists = $null; Verification = $rawCandidate.verification; ErrorMessage = $null }) | Out-Null } } $preview = [pscustomobject]@{ Id = $artifactFileId RelativePath = [string]$ArtifactFile.RelativePath ExpectedDepotPath = [string]$ArtifactFile.DefaultDepotPath StagingPath = [string]$ArtifactFile.StagingPath Status = $fileStatus Ready = [bool]$selectedCandidate AvailablePath = $availablePath SelectedCandidate = $selectedCandidate Candidates = @($candidatePreviews.ToArray()) } $Resolved[$artifactFileId] = $preview $Visiting.Remove($artifactFileId) return $preview } function Get-PackageAssignmentPlannedAction { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageResult, [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [object[]]$ArtifactFiles, [AllowNull()] [psobject]$NpmMaterialization, [switch]$MaterializeOnly ) if ($MaterializeOnly.IsPresent) { $artifactWorkRequired = @($ArtifactFiles | Where-Object { [string]$_.Status -ne 'Available' }).Count -gt 0 $npmWorkRequired = $NpmMaterialization -and [string]$NpmMaterialization.Status -ne 'Available' return $(if ($artifactWorkRequired -or $npmWorkRequired) { 'Materialize' } else { 'NoChange' }) } if ($PackageResult.Assigned -and [string]::Equals([string]$PackageResult.Assigned.Status, 'AlreadySatisfied', [System.StringComparison]::OrdinalIgnoreCase)) { return 'NoChange' } if (-not $PackageResult.ExistingPackage) { return 'Install' } switch -Exact ([string]$PackageResult.ExistingPackage.Decision) { 'ReusePackageOwned' { return 'Reuse' } 'AdoptExternal' { return 'Adopt' } 'ReplacePackageOwnedInstall' { return 'Replace' } 'UpgradeAdoptedInstall' { return 'Replace' } 'ExistingInstallReadinessFailed' { if ([string]::Equals([string]$PackageResult.ExistingPackage.SearchKind, 'packageTargetInstallPath', [System.StringComparison]::OrdinalIgnoreCase)) { return 'Repair' } return 'Install' } default { return 'Install' } } } function ConvertTo-PackageAssignmentNextCommand { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string[]]$DefinitionId, [AllowNull()] [string]$PublisherId, [AllowNull()] [string]$PackageVersion, [switch]$Offline, [switch]$MaterializeOnly ) $quotedIds = @($DefinitionId | ForEach-Object { "'{0}'" -f (([string]$_).Replace("'", "''")) }) -join ',' $parts = New-Object System.Collections.Generic.List[string] $parts.Add('Invoke-Package') | Out-Null if (-not [string]::IsNullOrWhiteSpace($PublisherId)) { $parts.Add(("-PublisherId '{0}'" -f $PublisherId.Replace("'", "''"))) | Out-Null } $parts.Add("-DefinitionId $quotedIds") | Out-Null if (-not [string]::IsNullOrWhiteSpace($PackageVersion)) { $parts.Add(("-PackageVersion '{0}'" -f $PackageVersion.Replace("'", "''"))) | Out-Null } if ($Offline.IsPresent) { $parts.Add('-Offline') | Out-Null } if ($MaterializeOnly.IsPresent) { $parts.Add('-MaterializeOnly') | Out-Null } return ($parts -join ' ') } function New-PackageAssignmentPlanCore { [CmdletBinding()] param( [AllowNull()] [string]$PublisherId = $null, [Parameter(Mandatory = $true)] [string[]]$DefinitionId, [AllowNull()] [string]$PackageVersion = $null, [bool]$PackageVersionOverrideSpecified = $false, [ValidateSet('Execution', 'Inspection')] [string]$Purpose = 'Execution', [switch]$AcceptUnknownSigningKey, [switch]$RequireAlreadyTrusted, [switch]$Offline, [switch]$MaterializeOnly, [switch]$VerifyDepotContent ) $dependencyPlan = New-PackageDependencyPlan -PublisherId $PublisherId -DefinitionId $DefinitionId -DesiredState 'Assigned' -PackageVersion $PackageVersion -PackageVersionOverrideSpecified $PackageVersionOverrideSpecified -AcceptUnknownSigningKey:$AcceptUnknownSigningKey -RequireAlreadyTrusted:$RequireAlreadyTrusted -InspectionOnly:([string]::Equals($Purpose, 'Inspection', [System.StringComparison]::OrdinalIgnoreCase)) if ([string]::Equals($Purpose, 'Execution', [System.StringComparison]::OrdinalIgnoreCase)) { return [pscustomobject]@{ DependencyPlan = $dependencyPlan; Purpose = 'Execution'; MutationFree = $false } } $warnings = New-Object System.Collections.Generic.List[object] $blockers = New-Object System.Collections.Generic.List[object] $trustActions = New-Object System.Collections.Generic.List[object] foreach ($violation in @(ConvertTo-PackageDependencyPlanArray -Value $dependencyPlan.Violations)) { $violationCode = [string]$violation.Reason if ([string]::Equals($violationCode, 'DependencyDefinitionNotFound', [System.StringComparison]::OrdinalIgnoreCase) -and [string]$violation.Message -match 'catalog trust') { $violationCode = 'DefinitionTrustRejected' } $blockers.Add((New-PackageAssignmentPlanIssue -Severity Blocker -Code $violationCode -Message ([string]$violation.Message) -NodeKey ([string]$violation.NodeKey) -DefinitionId ([string]$violation.DefinitionId))) | Out-Null } $nodePreviews = New-Object System.Collections.Generic.List[object] foreach ($node in @(ConvertTo-PackageDependencyPlanArray -Value $dependencyPlan.Nodes)) { $nodeWarnings = New-Object System.Collections.Generic.List[object] $nodeBlockers = New-Object System.Collections.Generic.List[object] $config = $node.PackageConfig $trustStatus = [string]$config.DefinitionCatalogTrustStatus if ($trustStatus -in @('signedUnknownKeyPrompt', 'signedUnknownKeyAutoTrust')) { $trustAction = [pscustomobject]@{ Action = 'TrustSigningKey' PublisherId = [string]$node.PublisherId DefinitionId = [string]$node.DefinitionId KeyThumbprint = [string]$config.DefinitionSignatureKeyThumbprint SignerDisplayName = [string]$config.DefinitionSignatureSignerDisplayName RecommendedCommand = "Import-PackageTrust -Path '<public-signing-cert.cer>'" } $trustActions.Add($trustAction) | Out-Null $nodeBlockers.Add((New-PackageAssignmentPlanIssue -Severity Blocker -Code 'DefinitionTrustRequired' -Message "Definition '$($node.DefinitionId)' has a valid signature from an unknown signing key and requires an explicit trust action." -NodeKey ([string]$node.NodeKey) -DefinitionId ([string]$node.DefinitionId))) | Out-Null } elseif ([string]::Equals($trustStatus, 'unsignedConfigTrust', [System.StringComparison]::OrdinalIgnoreCase)) { $nodeWarnings.Add((New-PackageAssignmentPlanIssue -Severity Warning -Code 'UnsignedDefinitionAllowed' -Message "Definition '$($node.DefinitionId)' is unsigned and allowed by configured catalog policy." -NodeKey ([string]$node.NodeKey) -DefinitionId ([string]$node.DefinitionId))) | Out-Null } elseif (-not [string]::Equals($trustStatus, 'signedTrusted', [System.StringComparison]::OrdinalIgnoreCase)) { $nodeBlockers.Add((New-PackageAssignmentPlanIssue -Severity Blocker -Code 'DefinitionTrustRejected' -Message "Definition '$($node.DefinitionId)' has catalog trust status '$trustStatus'." -NodeKey ([string]$node.NodeKey) -DefinitionId ([string]$node.DefinitionId))) | Out-Null } $artifactPreviews = @() $npmPreview = $null $packageResult = $null $plannedAction = 'Blocked' try { $isRoot = @($dependencyPlan.Roots | Where-Object { [string]::Equals([string]$_.NodeKey, [string]$node.NodeKey, [System.StringComparison]::OrdinalIgnoreCase) }).Count -gt 0 $resultParams = @{ DesiredState = 'Assigned' CommandMode = if ($MaterializeOnly.IsPresent) { 'MaterializeOnly' } else { 'Assigned' } Offline = $Offline MaterializeOnly = $MaterializeOnly PackageConfig = $config } if ($isRoot -and $PackageVersionOverrideSpecified) { $resultParams.PackageVersionSelector = $PackageVersion } $packageResult = New-PackageResult @resultParams if (-not [string]::IsNullOrWhiteSpace([string]$node.VersionRange)) { $packageResult | Add-Member -MemberType NoteProperty -Name PackageVersionRange -Value ([string]$node.VersionRange) -Force } $packageResult = Resolve-PackagePackage -PackageResult $packageResult if (-not [string]::Equals([string]$packageResult.PackageVersion, [string]$node.PackageVersion, [System.StringComparison]::OrdinalIgnoreCase)) { throw "Preview selected version '$($packageResult.PackageVersion)' but dependency planning selected '$($node.PackageVersion)'." } $packageResult = Resolve-PackagePaths -PackageResult $packageResult $packageResult = Build-PackageAcquisitionPlan -PackageResult $packageResult $resolvedArtifacts = @{} $visitingArtifacts = @{} $verifyContent = $Offline.IsPresent -or $VerifyDepotContent.IsPresent $artifactPreviews = @( foreach ($artifactFile in @($packageResult.ArtifactFiles)) { Resolve-PackageAssignmentArtifactPreview -PackageResult $packageResult -ArtifactFile $artifactFile -Resolved $resolvedArtifacts -Visiting $visitingArtifacts -VerifyDepotContent:$verifyContent } ) foreach ($artifactPreview in @($artifactPreviews | Where-Object { -not $_.Ready })) { $nodeBlockers.Add((New-PackageAssignmentPlanIssue -Severity Blocker -Code 'ArtifactFileUnavailable' -Message "Artifact file '$($artifactPreview.Id)' for definition '$($node.DefinitionId)' has no feasible source in the selected mode." -NodeKey ([string]$node.NodeKey) -DefinitionId ([string]$node.DefinitionId) -ArtifactFileId ([string]$artifactPreview.Id) -ExpectedDepotPath ([string]$artifactPreview.ExpectedDepotPath))) | Out-Null } if (Test-PackageNpmMaterializedInstallKind -Package $packageResult.Package) { $packageSpec = Get-PackageNpmResolvedPackageSpec -PackageResult $packageResult $npmMaterialization = Find-PackageNpmMaterializationInDepots -PackageResult $packageResult $expectedDirectories = @( foreach ($depotSource in @(Get-PackagePackageDepotSources -PackageConfig $packageResult.PackageConfig)) { if (-not [string]::IsNullOrWhiteSpace([string]$depotSource.basePath)) { [System.IO.Path]::GetFullPath((Join-Path ([string]$depotSource.basePath) ([string]$packageResult.PackageDepotRelativeDirectory))) } } ) $npmPreview = [pscustomobject]@{ PackageSpec = $packageSpec Status = if ($npmMaterialization) { 'Available' } elseif ($Offline.IsPresent) { 'Missing' } else { 'ResolvableOnline' } Reachability = if ($npmMaterialization) { 'Local' } elseif ($Offline.IsPresent) { 'DisabledOffline' } else { 'NotTested' } SourceId = if ($npmMaterialization) { [string]$npmMaterialization.SourceId } else { 'npmRegistry' } SourceDirectory = if ($npmMaterialization) { [string]$npmMaterialization.SourceDirectory } else { $null } ExpectedDepotDirectories = @($expectedDirectories) } if ($Offline.IsPresent -and -not $npmMaterialization) { $nodeBlockers.Add((New-PackageAssignmentPlanIssue -Severity Blocker -Code 'NpmMaterializationUnavailable' -Message "npm materialization '$packageSpec' for definition '$($node.DefinitionId)' is missing from every configured depot." -NodeKey ([string]$node.NodeKey) -DefinitionId ([string]$node.DefinitionId) -ExpectedDepotPath (@($expectedDirectories) -join '; '))) | Out-Null } } if (-not $MaterializeOnly.IsPresent) { $packageResult = Resolve-PackagePreAssignmentSatisfaction -PackageResult $packageResult $packageResult = Find-PackageExistingPackage -PackageResult $packageResult $packageResult = Set-PackageExistingPackage -PackageResult $packageResult $packageResult = Resolve-PackageExistingPackageDecision -PackageResult $packageResult } foreach ($compatibilityCheck in @($packageResult.Compatibility | Where-Object { -not $_.Accepted -and [string]$_.OnFail -eq 'warn' })) { $nodeWarnings.Add((New-PackageAssignmentPlanIssue -Severity Warning -Code 'CompatibilityWarning' -Message "Compatibility check '$($compatibilityCheck.Kind)' warned for definition '$($node.DefinitionId)'." -NodeKey ([string]$node.NodeKey) -DefinitionId ([string]$node.DefinitionId))) | Out-Null } $plannedAction = Get-PackageAssignmentPlannedAction -PackageResult $packageResult -ArtifactFiles $artifactPreviews -NpmMaterialization $npmPreview -MaterializeOnly:$MaterializeOnly } catch { $nodeBlockers.Add((New-PackageAssignmentPlanIssue -Severity Blocker -Code 'NodePreviewFailed' -Message $_.Exception.Message -NodeKey ([string]$node.NodeKey) -DefinitionId ([string]$node.DefinitionId))) | Out-Null } foreach ($issue in @($nodeWarnings.ToArray())) { $warnings.Add($issue) | Out-Null } foreach ($issue in @($nodeBlockers.ToArray())) { $blockers.Add($issue) | Out-Null } $nodeStatus = if ($nodeBlockers.Count -gt 0) { 'Blocked' } elseif ($nodeWarnings.Count -gt 0) { 'ReadyWithWarnings' } else { 'Ready' } $selectedPackage = if ($packageResult -and $packageResult.Package) { $packageResult.Package } else { $node.Package } $install = if ($selectedPackage) { Get-PackageAssignedInstallOperation -Release $selectedPackage } else { $null } $nodePreviews.Add([pscustomobject]@{ NodeKey = [string]$node.NodeKey IsRoot = [bool]$node.IsRoot PublisherId = [string]$node.PublisherId DefinitionId = [string]$node.DefinitionId DefinitionRevision = [int]$node.DefinitionRevision DefinitionHash = [string]$config.DefinitionSourceHash DefinitionPath = [string]$config.DefinitionPath DefinitionEndpointName = [string]$node.DefinitionEndpointName CatalogTrustStatus = $trustStatus SignatureStatus = [string]$config.DefinitionSignatureStatus PackageId = [string]$node.PackageId Version = [string]$node.PackageVersion ArtifactTargetId = if ($selectedPackage) { [string]$selectedPackage.artifactTargetId } else { $null } ReleaseTrack = [string]$node.ReleaseTrack ArtifactDistributionVariant = [string]$node.ArtifactDistributionVariant InstallKind = if ($install) { [string]$install.kind } else { $null } PlannedAction = $plannedAction Status = $nodeStatus ExistingInstall = if ($packageResult) { $packageResult.ExistingPackage } else { $null } Ownership = if ($packageResult) { $packageResult.Ownership } else { $null } ArtifactStagingDirectory = if ($packageResult) { [string]$packageResult.ArtifactStagingDirectory } else { $null } OperationArtifactFilePath = if ($packageResult) { [string]$packageResult.OperationArtifactFilePath } else { $null } ArtifactFiles = @($artifactPreviews) NpmMaterialization = $npmPreview Warnings = @($nodeWarnings.ToArray()) Blockers = @($nodeBlockers.ToArray()) }) | Out-Null } $nodes = @($nodePreviews.ToArray()) $rootPreviews = @( foreach ($root in @(ConvertTo-PackageDependencyPlanArray -Value $dependencyPlan.Roots)) { $nodePreview = @($nodes | Where-Object { [string]::Equals([string]$_.NodeKey, [string]$root.NodeKey, [System.StringComparison]::OrdinalIgnoreCase) }) | Select-Object -First 1 [pscustomobject]@{ RequestedPublisherId = [string]$root.RequestedPublisherId RequestedDefinitionId = [string]$root.RequestedDefinitionId NodeKey = [string]$root.NodeKey PublisherId = if ($nodePreview) { [string]$nodePreview.PublisherId } else { [string]$root.PublisherId } DefinitionId = if ($nodePreview) { [string]$nodePreview.DefinitionId } else { [string]$root.DefinitionId } Version = if ($nodePreview) { [string]$nodePreview.Version } else { $null } PlannedAction = if ($nodePreview) { [string]$nodePreview.PlannedAction } else { 'Blocked' } Status = if ($nodePreview) { [string]$nodePreview.Status } else { 'Blocked' } } } ) $status = if ($blockers.Count -gt 0) { 'Blocked' } elseif ($warnings.Count -gt 0) { 'ReadyWithWarnings' } else { 'Ready' } return [pscustomobject]@{ PSTypeName = 'Eigenverft.Manifested.Package.AssignmentPlan' CreatedAtUtc = [DateTime]::UtcNow.ToString('o') Status = $status Accepted = $blockers.Count -eq 0 Mode = if ($MaterializeOnly.IsPresent) { 'MaterializeOnly' } else { 'Assigned' } Offline = [bool]$Offline MutationFree = $true VerifyDepotContent = [bool]($Offline.IsPresent -or $VerifyDepotContent.IsPresent) Roots = @($rootPreviews) Nodes = @($nodes) Edges = @(ConvertTo-PackageDependencyPlanArray -Value $dependencyPlan.Edges) Warnings = @($warnings.ToArray()) Blockers = @($blockers.ToArray()) TrustActions = @($trustActions.ToArray()) NextCommand = ConvertTo-PackageAssignmentNextCommand -DefinitionId $DefinitionId -PublisherId $PublisherId -PackageVersion $PackageVersion -Offline:$Offline -MaterializeOnly:$MaterializeOnly DependencyPlan = $dependencyPlan } } |