PackageModel/Support/Package/Eigenverft.Manifested.Sandbox.PackageModel.Source.ps1
|
<#
Eigenverft.Manifested.Sandbox.PackageModel.Source #> function Get-PackageModelArtifactIndex { <# .SYNOPSIS Loads the PackageModel artifact index. .DESCRIPTION Returns the configured artifact index document, or an empty record set when the index file does not exist yet. .PARAMETER PackageModelConfig The resolved PackageModel config object. .EXAMPLE Get-PackageModelArtifactIndex -PackageModelConfig $config #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageModelConfig ) $indexPath = $PackageModelConfig.ArtifactIndexFilePath if ([string]::IsNullOrWhiteSpace($indexPath)) { throw 'PackageModel artifact index path is not configured.' } if (-not (Test-Path -LiteralPath $indexPath -PathType Leaf)) { return [pscustomobject]@{ Path = $indexPath Records = @() } } $documentInfo = Read-PackageModelJsonDocument -Path $indexPath $records = if ($documentInfo.Document.PSObject.Properties['records']) { @($documentInfo.Document.records) } else { @() } return [pscustomobject]@{ Path = $documentInfo.Path Records = $records } } function Save-PackageModelArtifactIndex { <# .SYNOPSIS Writes the PackageModel artifact index to disk. .DESCRIPTION Persists the normalized artifact index document to the configured index path. .PARAMETER IndexPath The target index file path. .PARAMETER Records The artifact records to persist. .EXAMPLE Save-PackageModelArtifactIndex -IndexPath $path -Records $records #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$IndexPath, [Parameter(Mandatory = $true)] [object[]]$Records ) $directoryPath = Split-Path -Parent $IndexPath if (-not [string]::IsNullOrWhiteSpace($directoryPath)) { $null = New-Item -ItemType Directory -Path $directoryPath -Force } [ordered]@{ records = @($Records) } | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $IndexPath -Encoding UTF8 } function Update-PackageModelArtifactIndexRecord { <# .SYNOPSIS Updates the PackageModel artifact index for one resolved artifact path. .DESCRIPTION Refreshes the tracked source and package metadata for an artifact path in the central artifact index. .PARAMETER PackageModelResult The current PackageModel result object. .PARAMETER ArtifactPath The artifact path to write into the index. .PARAMETER SourceScope The source scope that produced the artifact. .PARAMETER SourceId The source id that produced the artifact. .EXAMPLE Update-PackageModelArtifactIndexRecord -PackageModelResult $result -ArtifactPath $path -SourceScope environment -SourceId defaultPackageDepot #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageModelResult, [Parameter(Mandatory = $true)] [string]$ArtifactPath, [AllowNull()] [string]$SourceScope, [AllowNull()] [string]$SourceId ) if ([string]::IsNullOrWhiteSpace($ArtifactPath)) { return } $normalizedArtifactPath = [System.IO.Path]::GetFullPath($ArtifactPath) $index = Get-PackageModelArtifactIndex -PackageModelConfig $PackageModelResult.PackageModelConfig $records = @( foreach ($record in @($index.Records)) { if (-not [string]::Equals([string]$record.path, $normalizedArtifactPath, [System.StringComparison]::OrdinalIgnoreCase)) { $record } } ) $records += [pscustomobject]@{ path = $normalizedArtifactPath definitionId = $PackageModelResult.DefinitionId releaseId = $PackageModelResult.PackageId releaseTrack = $PackageModelResult.ReleaseTrack flavor = if ($PackageModelResult.Package -and $PackageModelResult.Package.PSObject.Properties['flavor']) { [string]$PackageModelResult.Package.flavor } else { $null } version = $PackageModelResult.PackageVersion sourceScope = $SourceScope sourceId = $SourceId updatedAtUtc = [DateTime]::UtcNow.ToString('o') } Save-PackageModelArtifactIndex -IndexPath $index.Path -Records $records } function Get-PackageModelSourceDefinition { <# .SYNOPSIS Returns a resolved PackageModel source definition by sourceRef. .DESCRIPTION Looks up an acquisition source from the effective acquisition environment or from definition-local upstream sources and returns the normalized source definition with scope and id metadata. .PARAMETER PackageModelConfig The resolved PackageModel config object. .PARAMETER SourceRef The acquisition-candidate sourceRef object. .EXAMPLE Get-PackageModelSourceDefinition -PackageModelConfig $config -SourceRef $candidate.sourceRef #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageModelConfig, [Parameter(Mandatory = $true)] [psobject]$SourceRef ) $scope = [string]$SourceRef.scope $id = [string]$SourceRef.id $sourceObject = $null switch -Exact ($scope) { 'environment' { foreach ($property in @($PackageModelConfig.EnvironmentSources.PSObject.Properties)) { if ([string]::Equals([string]$property.Name, $id, [System.StringComparison]::OrdinalIgnoreCase)) { $sourceObject = $property.Value $id = $property.Name break } } if (-not $sourceObject) { throw "PackageModel environment source '$($SourceRef.id)' was not found in the effective acquisition environment." } } 'definition' { foreach ($property in @($PackageModelConfig.DefinitionUpstreamSources.PSObject.Properties)) { if ([string]::Equals([string]$property.Name, $id, [System.StringComparison]::OrdinalIgnoreCase)) { $sourceObject = $property.Value $id = $property.Name break } } if (-not $sourceObject) { throw "PackageModel definition source '$($SourceRef.id)' was not found in definition '$($PackageModelConfig.DefinitionId)'." } } default { throw "Unsupported PackageModel sourceRef.scope '$scope'." } } return [pscustomobject]@{ Scope = $scope Id = $id Kind = if ($sourceObject.PSObject.Properties['kind']) { [string]$sourceObject.kind } else { $null } BaseUri = if ($sourceObject.PSObject.Properties['baseUri']) { [string]$sourceObject.baseUri } else { $null } BasePath = if ($sourceObject.PSObject.Properties['basePath']) { [string]$sourceObject.basePath } else { $null } } } function Resolve-PackageModelSource { <# .SYNOPSIS Resolves a concrete source location from a source definition and acquisition candidate. .DESCRIPTION Combines a resolved source definition with one release acquisition candidate and returns the concrete URI or filesystem path that should be used for the package-file save. .PARAMETER SourceDefinition The resolved source definition for the acquisition candidate. .PARAMETER AcquisitionCandidate The release acquisition candidate. .EXAMPLE Resolve-PackageModelSource -SourceDefinition $source -AcquisitionCandidate $candidate #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$SourceDefinition, [Parameter(Mandatory = $true)] [psobject]$AcquisitionCandidate ) switch -Exact ([string]$SourceDefinition.Kind) { 'download' { if ([string]::IsNullOrWhiteSpace([string]$SourceDefinition.BaseUri)) { throw "PackageModel download source '$($SourceDefinition.Id)' does not define baseUri." } if (-not $AcquisitionCandidate.PSObject.Properties['sourcePath'] -or [string]::IsNullOrWhiteSpace([string]$AcquisitionCandidate.sourcePath)) { throw "PackageModel acquisition candidate for '$($SourceDefinition.Id)' does not define sourcePath." } $baseUriText = ([string]$SourceDefinition.BaseUri).TrimEnd('/') + '/' $resolvedUri = [System.Uri]::new([System.Uri]$baseUriText, [string]$AcquisitionCandidate.sourcePath) return [pscustomobject]@{ Kind = 'download' ResolvedSource = $resolvedUri.AbsoluteUri } } 'filesystem' { if (-not $AcquisitionCandidate.PSObject.Properties['sourcePath'] -or [string]::IsNullOrWhiteSpace([string]$AcquisitionCandidate.sourcePath)) { throw "PackageModel acquisition candidate for '$($SourceDefinition.Id)' does not define sourcePath." } $sourcePath = ([string]$AcquisitionCandidate.sourcePath).Trim() -replace '/', '\' if ([System.IO.Path]::IsPathRooted($sourcePath)) { $resolvedPath = Resolve-PackageModelPathValue -PathValue $sourcePath } else { if ([string]::IsNullOrWhiteSpace([string]$SourceDefinition.BasePath)) { throw "PackageModel filesystem source '$($SourceDefinition.Id)' does not define basePath." } $resolvedPath = [System.IO.Path]::GetFullPath((Join-Path $SourceDefinition.BasePath $sourcePath)) } return [pscustomobject]@{ Kind = 'filesystem' ResolvedSource = $resolvedPath } } default { throw "Unsupported PackageModel source kind '$($SourceDefinition.Kind)'." } } } function Test-PackageModelSavedFile { <# .SYNOPSIS Evaluates a package file against a save-time verification policy. .DESCRIPTION Applies the acquisition candidate verification policy to a local file and returns the verification status, whether the file is accepted, and the expected and actual SHA256 values when hashing is performed. .PARAMETER Path The local file path to verify. .PARAMETER Verification The verification policy object from the acquisition candidate. .EXAMPLE Test-PackageModelSavedFile -Path .\package.zip -Verification $verification #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Path, [AllowNull()] [psobject]$Verification ) if ($Verification -is [System.Collections.IDictionary]) { $Verification = [pscustomobject]$Verification } $mode = if ($Verification -and $Verification.PSObject.Properties['mode'] -and -not [string]::IsNullOrWhiteSpace([string]$Verification.mode)) { ([string]$Verification.mode).ToLowerInvariant() } else { 'none' } if (-not (Test-Path -LiteralPath $Path)) { return [pscustomobject]@{ Status = 'FileMissing' Accepted = $false Verified = $false Mode = $mode Algorithm = $null ExpectedHash = $null ActualHash = $null } } if ($mode -eq 'none') { return [pscustomobject]@{ Status = 'VerificationSkipped' Accepted = $true Verified = $false Mode = $mode Algorithm = $null ExpectedHash = $null ActualHash = $null } } $algorithm = if ($Verification -and $Verification.PSObject.Properties['algorithm'] -and -not [string]::IsNullOrWhiteSpace([string]$Verification.algorithm)) { ([string]$Verification.algorithm).ToLowerInvariant() } else { 'sha256' } if ($algorithm -ne 'sha256') { return [pscustomobject]@{ Status = 'VerificationAlgorithmUnsupported' Accepted = $false Verified = $false Mode = $mode Algorithm = $algorithm ExpectedHash = $null ActualHash = $null } } $expectedHash = if ($Verification -and $Verification.PSObject.Properties['sha256'] -and -not [string]::IsNullOrWhiteSpace([string]$Verification.sha256)) { ([string]$Verification.sha256).Trim().ToLowerInvariant() } else { $null } if ([string]::IsNullOrWhiteSpace($expectedHash)) { return [pscustomobject]@{ Status = if ($mode -eq 'required') { 'VerificationHashMissing' } else { 'VerificationHashMissingOptional' } Accepted = ($mode -ne 'required') Verified = $false Mode = $mode Algorithm = $algorithm ExpectedHash = $null ActualHash = $null } } $actualHash = (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant() return [pscustomobject]@{ Status = if ($actualHash -eq $expectedHash) { 'VerificationPassed' } else { 'VerificationFailed' } Accepted = ($actualHash -eq $expectedHash) Verified = $true Mode = $mode Algorithm = $algorithm ExpectedHash = $expectedHash ActualHash = $actualHash } } function Save-PackageModelDownloadFile { <# .SYNOPSIS Downloads a package file to a local path. .DESCRIPTION Uses the module's download helper to fetch a package file from an HTTP or HTTPS source into a staging path for later verification and promotion. .PARAMETER Uri The package download URI. .PARAMETER TargetPath The local staging path that should receive the file. .EXAMPLE Save-PackageModelDownloadFile -Uri https://example.org/package.zip -TargetPath C:\Temp\package.zip #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Uri, [Parameter(Mandatory = $true)] [string]$TargetPath ) Invoke-WebRequestEx -Uri $Uri -OutFile $TargetPath -UseBasicParsing return (Resolve-Path -LiteralPath $TargetPath -ErrorAction Stop).Path } function Save-PackageModelFilesystemFile { <# .SYNOPSIS Copies a package file from a filesystem source. .DESCRIPTION Copies a package file from a local or network filesystem path into a staging path for later verification and promotion. .PARAMETER SourcePath The local or network path that contains the package file. .PARAMETER TargetPath The local staging path that should receive the copy. .EXAMPLE Save-PackageModelFilesystemFile -SourcePath \\server\share\package.zip -TargetPath C:\Temp\package.zip #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$SourcePath, [Parameter(Mandatory = $true)] [string]$TargetPath ) if (-not (Test-Path -LiteralPath $SourcePath)) { throw "PackageModel filesystem source '$SourcePath' does not exist." } Copy-Item -LiteralPath $SourcePath -Destination $TargetPath -Force return (Resolve-Path -LiteralPath $TargetPath -ErrorAction Stop).Path } function Test-PackageModelPackageFileAcquisitionRequired { <# .SYNOPSIS Determines whether the selected release needs an acquired package file. .DESCRIPTION Interprets the current install kind so acquisition is skipped for install flows that do not consume a saved package file. .PARAMETER Package The selected release object. .EXAMPLE Test-PackageModelPackageFileAcquisitionRequired -Package $package #> [CmdletBinding()] [OutputType([bool])] param( [Parameter(Mandatory = $true)] [psobject]$Package ) $installKind = if ($Package.install -and $Package.install.PSObject.Properties['kind']) { [string]$Package.install.kind } else { $null } switch -Exact ($installKind) { 'expandArchive' { return $true } 'runInstaller' { return (-not $Package.install.PSObject.Properties['commandPath'] -or [string]::IsNullOrWhiteSpace([string]$Package.install.commandPath)) } default { return $false } } } function Get-PackageModelPreferredVerification { [CmdletBinding()] param( [AllowNull()] [object[]]$AcquisitionCandidates ) foreach ($candidate in @($AcquisitionCandidates)) { if ($candidate.PSObject.Properties['verification'] -and $null -ne $candidate.verification) { return $candidate.verification } } return [pscustomobject]@{ mode = 'none' } } function Resolve-PackageModelAcquisitionCandidateVerification { <# .SYNOPSIS Builds the effective verification policy for one acquisition candidate. .DESCRIPTION Combines acquisition-candidate verification mode with canonical package-file integrity metadata when present, while remaining compatible with older candidate-local hash definitions. .PARAMETER Package The selected effective release. .PARAMETER AcquisitionCandidate The raw acquisition candidate. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$Package, [AllowNull()] [psobject]$AcquisitionCandidate ) $candidateVerification = if ($AcquisitionCandidate -and $AcquisitionCandidate.PSObject.Properties['verification']) { $AcquisitionCandidate.verification } else { $null } if ($candidateVerification -is [System.Collections.IDictionary]) { $candidateVerification = [pscustomobject]$candidateVerification } $packageIntegrity = if ($Package -and $Package.PSObject.Properties['packageFile'] -and $Package.packageFile -and $Package.packageFile.PSObject.Properties['integrity']) { $Package.packageFile.integrity } else { $null } if ($packageIntegrity -is [System.Collections.IDictionary]) { $packageIntegrity = [pscustomobject]$packageIntegrity } $mode = if ($candidateVerification -and $candidateVerification.PSObject.Properties['mode'] -and -not [string]::IsNullOrWhiteSpace([string]$candidateVerification.mode)) { [string]$candidateVerification.mode } else { 'none' } $algorithm = if ($packageIntegrity -and $packageIntegrity.PSObject.Properties['algorithm'] -and -not [string]::IsNullOrWhiteSpace([string]$packageIntegrity.algorithm)) { [string]$packageIntegrity.algorithm } elseif ($candidateVerification -and $candidateVerification.PSObject.Properties['algorithm'] -and -not [string]::IsNullOrWhiteSpace([string]$candidateVerification.algorithm)) { [string]$candidateVerification.algorithm } else { 'sha256' } $sha256 = if ($packageIntegrity -and $packageIntegrity.PSObject.Properties['sha256'] -and -not [string]::IsNullOrWhiteSpace([string]$packageIntegrity.sha256)) { [string]$packageIntegrity.sha256 } elseif ($candidateVerification -and $candidateVerification.PSObject.Properties['sha256'] -and -not [string]::IsNullOrWhiteSpace([string]$candidateVerification.sha256)) { [string]$candidateVerification.sha256 } else { $null } $verification = [ordered]@{ mode = $mode } if (-not [string]::IsNullOrWhiteSpace($algorithm)) { $verification.algorithm = $algorithm } if (-not [string]::IsNullOrWhiteSpace($sha256)) { $verification.sha256 = $sha256 } return [pscustomobject]$verification } function Get-PackageModelPackageDepotSources { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageModelConfig ) $orderedSources = New-Object System.Collections.Generic.List[object] foreach ($property in @($PackageModelConfig.EnvironmentSources.PSObject.Properties)) { $source = $property.Value if (-not [string]::Equals([string]$source.kind, 'filesystem', [System.StringComparison]::OrdinalIgnoreCase)) { continue } if ([string]::Equals([string]$property.Name, 'defaultPackageDepot', [System.StringComparison]::OrdinalIgnoreCase)) { $orderedSources.Add([pscustomobject]@{ id = $property.Name priority = 0 }) | Out-Null } else { $orderedSources.Add([pscustomobject]@{ id = $property.Name priority = 1000 }) | Out-Null } } return @( $orderedSources.ToArray() | Sort-Object -Property priority, id ) } function Build-PackageModelAcquisitionPlan { <# .SYNOPSIS Builds the internal PackageModel acquisition plan for the selected release. .DESCRIPTION Normalizes the ordered acquisition candidates and captures the install-workspace and default-depot targets so later package-file save steps can execute linearly. .PARAMETER PackageModelResult The PackageModel result object to enrich. .EXAMPLE Build-PackageModelAcquisitionPlan -PackageModelResult $result #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageModelResult ) $package = $PackageModelResult.Package if (-not $package) { throw 'Build-PackageModelAcquisitionPlan requires a selected release.' } $requiresPackageFile = Test-PackageModelPackageFileAcquisitionRequired -Package $package $orderedCandidates = New-Object System.Collections.Generic.List[object] if ($requiresPackageFile -and $package.PSObject.Properties['acquisitionCandidates']) { foreach ($candidate in @($package.acquisitionCandidates | Sort-Object -Property @{ Expression = { if ($_.PSObject.Properties['priority']) { [int]$_.priority } else { [int]::MaxValue } } })) { $resolvedVerification = Resolve-PackageModelAcquisitionCandidateVerification -Package $package -AcquisitionCandidate $candidate switch -Exact ([string]$candidate.kind) { 'packageDepot' { $resolvedDepotSourcePath = Join-Path $PackageModelResult.PackageFileRelativeDirectory ([string]$package.packageFile.fileName) foreach ($depotSource in @(Get-PackageModelPackageDepotSources -PackageModelConfig $PackageModelResult.PackageModelConfig)) { $orderedCandidates.Add([pscustomobject]@{ kind = 'packageDepot' priority = if ($candidate.PSObject.Properties['priority']) { [int]$candidate.priority } else { [int]::MaxValue } sourceRef = [pscustomobject]@{ scope = 'environment' id = $depotSource.id } sourcePath = $resolvedDepotSourcePath verification = $resolvedVerification }) | Out-Null } } 'download' { $orderedCandidates.Add([pscustomobject]@{ kind = 'download' priority = if ($candidate.PSObject.Properties['priority']) { [int]$candidate.priority } else { [int]::MaxValue } sourceRef = [pscustomobject]@{ scope = 'definition' id = [string]$candidate.sourceId } sourcePath = [string]$candidate.sourcePath verification = $resolvedVerification }) | Out-Null } 'filesystem' { $orderedCandidates.Add([pscustomobject]@{ kind = 'filesystem' priority = if ($candidate.PSObject.Properties['priority']) { [int]$candidate.priority } else { [int]::MaxValue } sourceRef = if ($candidate.PSObject.Properties['sourceId'] -and -not [string]::IsNullOrWhiteSpace([string]$candidate.sourceId)) { [pscustomobject]@{ scope = 'environment' id = [string]$candidate.sourceId } } else { $null } sourcePath = [string]$candidate.sourcePath verification = $resolvedVerification }) | Out-Null } } } } $PackageModelResult.AcquisitionPlan = [pscustomobject]@{ PackageFileRequired = $requiresPackageFile InstallWorkspaceFilePath = $PackageModelResult.PackageFilePath DefaultPackageDepotFilePath = $PackageModelResult.DefaultPackageDepotFilePath Candidates = @( $orderedCandidates.ToArray() | Sort-Object -Property priority, @{ Expression = { if ($_.sourceRef) { [string]$_.sourceRef.id } else { [string]::Empty } } } ) } $candidateSummary = @( foreach ($candidate in @($PackageModelResult.AcquisitionPlan.Candidates)) { $sourceSummary = if ($candidate.sourceRef) { '{0}:{1}' -f [string]$candidate.sourceRef.scope, [string]$candidate.sourceRef.id } else { 'direct' } '{0}@{1}->{2}' -f [string]$candidate.kind, [string]$candidate.priority, $sourceSummary } ) -join ', ' if ([string]::IsNullOrWhiteSpace($candidateSummary)) { $candidateSummary = '<none>' } Write-PackageModelExecutionMessage -Message ("[STATE] Acquisition plan packageFileRequired='{0}' with {1} candidate(s): {2}." -f $requiresPackageFile, @($PackageModelResult.AcquisitionPlan.Candidates).Count, $candidateSummary) return $PackageModelResult } function Save-PackageModelPackageFile { <# .SYNOPSIS Ensures the selected package file is present in the install workspace. .DESCRIPTION Reuses an already-present verified package file when possible, then checks the default package depot, and otherwise attempts each configured acquisition candidate in priority order until one succeeds or all candidates fail. .PARAMETER PackageModelResult The PackageModel result object to enrich. .EXAMPLE Save-PackageModelPackageFile -PackageModelResult $result #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageModelResult ) $package = $PackageModelResult.Package $packageModelConfig = $PackageModelResult.PackageModelConfig if (-not $package -or -not $package.PSObject.Properties['install'] -or -not $package.install) { throw 'Save-PackageModelPackageFile requires a selected release with install settings.' } if ($PackageModelResult.ExistingPackage -and $PackageModelResult.ExistingPackage.PSObject.Properties['Decision'] -and $PackageModelResult.ExistingPackage.Decision -in @('ReusePackageModelOwned', 'AdoptExternal')) { $PackageModelResult.PackageFileSave = [pscustomobject]@{ Success = $true Status = 'Skipped' PackageFilePath = $PackageModelResult.PackageFilePath SelectedSource = $null Verification = $null Attempts = @() FailureReason = $null ErrorMessage = $null } Write-PackageModelExecutionMessage -Message ("[STATE] Package file step skipped because existing install decision is '{0}'." -f [string]$PackageModelResult.ExistingPackage.Decision) return $PackageModelResult } if (-not $PackageModelResult.AcquisitionPlan) { $PackageModelResult = Build-PackageModelAcquisitionPlan -PackageModelResult $PackageModelResult } if (-not $PackageModelResult.AcquisitionPlan.PackageFileRequired) { $PackageModelResult.PackageFileSave = [pscustomobject]@{ Success = $true Status = 'Skipped' PackageFilePath = $PackageModelResult.PackageFilePath SelectedSource = $null Verification = $null Attempts = @() FailureReason = $null ErrorMessage = $null } Write-PackageModelExecutionMessage -Message "[STATE] Package file step skipped because the selected install kind does not require a saved package file." return $PackageModelResult } if ([string]::IsNullOrWhiteSpace($PackageModelResult.PackageFilePath)) { throw "PackageModel release '$($package.id)' does not define packageFile.fileName." } $orderedCandidates = @($PackageModelResult.AcquisitionPlan.Candidates) if (-not $orderedCandidates) { throw "PackageModel release '$($package.id)' does not define any acquisition candidates." } $attempts = New-Object System.Collections.Generic.List[object] $preferredVerification = Get-PackageModelPreferredVerification -AcquisitionCandidates $orderedCandidates if (Test-Path -LiteralPath $PackageModelResult.PackageFilePath) { $verification = Test-PackageModelSavedFile -Path $PackageModelResult.PackageFilePath -Verification $preferredVerification $attempts.Add([pscustomobject]@{ AttemptType = 'ReuseCheck' Status = if ($verification.Accepted) { 'ReusedPackageFile' } else { 'ReuseRejected' } SourceScope = 'installWorkspace' SourceId = 'installWorkspace' SourceKind = 'filesystem' ResolvedSource = $PackageModelResult.PackageFilePath VerificationStatus = $verification.Status ErrorMessage = if ($verification.Accepted) { $null } else { 'Existing install-workspace file did not satisfy verification.' } }) | Out-Null if ($verification.Accepted) { Update-PackageModelArtifactIndexRecord -PackageModelResult $PackageModelResult -ArtifactPath $PackageModelResult.PackageFilePath -SourceScope 'installWorkspace' -SourceId 'installWorkspace' $PackageModelResult.PackageFileSave = [pscustomobject]@{ Success = $true Status = 'ReusedPackageFile' PackageFilePath = $PackageModelResult.PackageFilePath SelectedSource = [pscustomobject]@{ SourceScope = 'installWorkspace' SourceId = 'installWorkspace' SourceKind = 'filesystem' ResolvedSource = $PackageModelResult.PackageFilePath } Verification = $verification Attempts = @($attempts.ToArray()) FailureReason = $null ErrorMessage = $null } Write-PackageModelExecutionMessage -Message ("[ACTION] Reused install workspace package file '{0}'." -f $PackageModelResult.PackageFilePath) return $PackageModelResult } } if (-not [string]::IsNullOrWhiteSpace($PackageModelResult.DefaultPackageDepotFilePath) -and (Test-Path -LiteralPath $PackageModelResult.DefaultPackageDepotFilePath)) { $verification = Test-PackageModelSavedFile -Path $PackageModelResult.DefaultPackageDepotFilePath -Verification $preferredVerification $attempts.Add([pscustomobject]@{ AttemptType = 'DepotReuseCheck' Status = if ($verification.Accepted) { 'HydratedFromDefaultPackageDepot' } else { 'DefaultPackageDepotRejected' } SourceScope = 'environment' SourceId = 'defaultPackageDepot' SourceKind = 'filesystem' ResolvedSource = $PackageModelResult.DefaultPackageDepotFilePath VerificationStatus = $verification.Status ErrorMessage = if ($verification.Accepted) { $null } else { 'Default package-depot artifact did not satisfy verification.' } }) | Out-Null if ($verification.Accepted) { $null = New-Item -ItemType Directory -Path $PackageModelResult.InstallWorkspaceDirectory -Force Copy-Item -LiteralPath $PackageModelResult.DefaultPackageDepotFilePath -Destination $PackageModelResult.PackageFilePath -Force Update-PackageModelArtifactIndexRecord -PackageModelResult $PackageModelResult -ArtifactPath $PackageModelResult.DefaultPackageDepotFilePath -SourceScope 'environment' -SourceId 'defaultPackageDepot' Update-PackageModelArtifactIndexRecord -PackageModelResult $PackageModelResult -ArtifactPath $PackageModelResult.PackageFilePath -SourceScope 'environment' -SourceId 'defaultPackageDepot' $PackageModelResult.PackageFileSave = [pscustomobject]@{ Success = $true Status = 'HydratedFromDefaultPackageDepot' PackageFilePath = $PackageModelResult.PackageFilePath SelectedSource = [pscustomobject]@{ SourceScope = 'environment' SourceId = 'defaultPackageDepot' SourceKind = 'filesystem' ResolvedSource = $PackageModelResult.DefaultPackageDepotFilePath } Verification = $verification Attempts = @($attempts.ToArray()) FailureReason = $null ErrorMessage = $null } Write-PackageModelExecutionMessage -Message ("[ACTION] Hydrated install workspace package file from default package depot '{0}'." -f $PackageModelResult.DefaultPackageDepotFilePath) return $PackageModelResult } } $null = New-Item -ItemType Directory -Path $PackageModelResult.InstallWorkspaceDirectory -Force foreach ($candidate in $orderedCandidates) { $sourceDefinition = $null $resolvedSource = $null $verification = $null $stagingPath = '{0}.{1}.partial' -f $PackageModelResult.PackageFilePath, ([guid]::NewGuid().ToString('N')) try { if ($candidate.sourceRef) { $sourceDefinition = Get-PackageModelSourceDefinition -PackageModelConfig $packageModelConfig -SourceRef $candidate.sourceRef } elseif ([string]::Equals([string]$candidate.kind, 'filesystem', [System.StringComparison]::OrdinalIgnoreCase)) { $sourceDefinition = [pscustomobject]@{ Scope = 'direct' Id = 'directFilesystem' Kind = 'filesystem' BaseUri = $null BasePath = $null } } else { throw "PackageModel acquisition candidate kind '$($candidate.kind)' could not be resolved to a source definition." } $resolvedSource = Resolve-PackageModelSource -SourceDefinition $sourceDefinition -AcquisitionCandidate $candidate switch -Exact ([string]$resolvedSource.Kind) { 'download' { $null = Save-PackageModelDownloadFile -Uri $resolvedSource.ResolvedSource -TargetPath $stagingPath } 'filesystem' { $null = Save-PackageModelFilesystemFile -SourcePath $resolvedSource.ResolvedSource -TargetPath $stagingPath } default { throw "Unsupported package-file source kind '$($resolvedSource.Kind)'." } } $verification = Test-PackageModelSavedFile -Path $stagingPath -Verification $candidate.verification if (-not $verification.Accepted) { if (Test-Path -LiteralPath $stagingPath) { Remove-Item -LiteralPath $stagingPath -Force -ErrorAction SilentlyContinue } $attempts.Add([pscustomobject]@{ AttemptType = 'Save' Status = 'Failed' SourceScope = $sourceDefinition.Scope SourceId = $sourceDefinition.Id SourceKind = $resolvedSource.Kind ResolvedSource = $resolvedSource.ResolvedSource VerificationStatus = $verification.Status ErrorMessage = 'Saved package file did not satisfy verification.' }) | Out-Null if (-not $packageModelConfig.AllowAcquisitionFallback) { break } continue } if (Test-Path -LiteralPath $PackageModelResult.PackageFilePath) { Remove-Item -LiteralPath $PackageModelResult.PackageFilePath -Force } Move-Item -LiteralPath $stagingPath -Destination $PackageModelResult.PackageFilePath -Force Update-PackageModelArtifactIndexRecord -PackageModelResult $PackageModelResult -ArtifactPath $PackageModelResult.PackageFilePath -SourceScope $sourceDefinition.Scope -SourceId $sourceDefinition.Id if ([string]::Equals([string]$resolvedSource.Kind, 'download', [System.StringComparison]::OrdinalIgnoreCase) -and $packageModelConfig.MirrorDownloadedArtifactsToDefaultPackageDepot -and -not [string]::IsNullOrWhiteSpace($PackageModelResult.DefaultPackageDepotFilePath)) { $null = New-Item -ItemType Directory -Path (Split-Path -Parent $PackageModelResult.DefaultPackageDepotFilePath) -Force Copy-Item -LiteralPath $PackageModelResult.PackageFilePath -Destination $PackageModelResult.DefaultPackageDepotFilePath -Force Update-PackageModelArtifactIndexRecord -PackageModelResult $PackageModelResult -ArtifactPath $PackageModelResult.DefaultPackageDepotFilePath -SourceScope $sourceDefinition.Scope -SourceId $sourceDefinition.Id } elseif ([string]::Equals([string]$sourceDefinition.Scope, 'environment', [System.StringComparison]::OrdinalIgnoreCase) -and [string]::Equals([string]$sourceDefinition.Id, 'defaultPackageDepot', [System.StringComparison]::OrdinalIgnoreCase) -and -not [string]::IsNullOrWhiteSpace($PackageModelResult.DefaultPackageDepotFilePath)) { Update-PackageModelArtifactIndexRecord -PackageModelResult $PackageModelResult -ArtifactPath $PackageModelResult.DefaultPackageDepotFilePath -SourceScope 'environment' -SourceId 'defaultPackageDepot' } $attempts.Add([pscustomobject]@{ AttemptType = 'Save' Status = 'SavedPackageFile' SourceScope = $sourceDefinition.Scope SourceId = $sourceDefinition.Id SourceKind = $resolvedSource.Kind ResolvedSource = $resolvedSource.ResolvedSource VerificationStatus = $verification.Status ErrorMessage = $null }) | Out-Null $PackageModelResult.PackageFileSave = [pscustomobject]@{ Success = $true Status = 'SavedPackageFile' PackageFilePath = $PackageModelResult.PackageFilePath SelectedSource = [pscustomobject]@{ SourceScope = $sourceDefinition.Scope SourceId = $sourceDefinition.Id SourceKind = $resolvedSource.Kind ResolvedSource = $resolvedSource.ResolvedSource } Verification = $verification Attempts = @($attempts.ToArray()) FailureReason = $null ErrorMessage = $null } Write-PackageModelExecutionMessage -Message ("[ACTION] Saved package file from '{0}:{1}'." -f $sourceDefinition.Scope, $sourceDefinition.Id) return $PackageModelResult } catch { if (Test-Path -LiteralPath $stagingPath) { Remove-Item -LiteralPath $stagingPath -Force -ErrorAction SilentlyContinue } $attempts.Add([pscustomobject]@{ AttemptType = 'Save' Status = 'Failed' SourceScope = if ($sourceDefinition) { $sourceDefinition.Scope } elseif ($candidate.sourceRef) { [string]$candidate.sourceRef.scope } else { $null } SourceId = if ($sourceDefinition) { $sourceDefinition.Id } elseif ($candidate.sourceRef) { [string]$candidate.sourceRef.id } else { $null } SourceKind = if ($resolvedSource) { $resolvedSource.Kind } else { $null } ResolvedSource = if ($resolvedSource) { $resolvedSource.ResolvedSource } else { $null } VerificationStatus = if ($verification) { $verification.Status } else { $null } ErrorMessage = $_.Exception.Message }) | Out-Null if (-not $packageModelConfig.AllowAcquisitionFallback) { break } } } $PackageModelResult.PackageFileSave = [pscustomobject]@{ Success = $false Status = 'Failed' PackageFilePath = $PackageModelResult.PackageFilePath SelectedSource = $null Verification = $null Attempts = @($attempts.ToArray()) FailureReason = 'AllSourcesFailed' ErrorMessage = "All acquisition candidates failed for PackageModel release '$($package.id)'." } Write-PackageModelExecutionMessage -Level 'ERR' -Message ("[ACTION] All acquisition candidates failed for release '{0}'." -f $package.id) return $PackageModelResult } |