Support/Package/State/Eigenverft.Manifested.Sandbox.Package.Ownership.ps1
|
<#
Eigenverft.Manifested.Sandbox.Package.Ownership #> function Get-PackageInstallSlotId { <# .SYNOPSIS Builds the logical Package install-slot id for a result. .DESCRIPTION Combines the definition id, release track, and artifact distribution variant into the stable install slot identity used by the package inventory. .PARAMETER PackageResult The current Package result object. .EXAMPLE Get-PackageInstallSlotId -PackageResult $result #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageResult ) $definitionId = [string]$PackageResult.DefinitionId $releaseTrack = if ($PackageResult.Package -and $PackageResult.Package.PSObject.Properties['releaseTrack']) { [string]$PackageResult.Package.releaseTrack } else { [string]$PackageResult.ReleaseTrack } $artifactDistributionVariant = if ($PackageResult.Package -and $PackageResult.Package.PSObject.Properties['artifactDistributionVariant']) { [string]$PackageResult.Package.artifactDistributionVariant } else { 'default' } return ('{0}:{1}:{2}' -f $definitionId, $releaseTrack, $artifactDistributionVariant) } function Assert-PackageInventoryRecordOptionalExtensions { [CmdletBinding()] param( [AllowNull()] [object]$Record ) if ($null -eq $Record) { return } if ($Record.PSObject.Properties['dependencyInstallSlotIds'] -and $null -ne $Record.dependencyInstallSlotIds) { $raw = $Record.dependencyInstallSlotIds if ($raw -is [string]) { throw "Package inventory record for installSlotId '$([string]$Record.installSlotId)' has invalid dependencyInstallSlotIds (expected a JSON array of strings, not a single string)." } foreach ($entry in @($raw)) { if ($null -eq $entry) { continue } if ($entry -isnot [string]) { throw "Package inventory record for installSlotId '$([string]$Record.installSlotId)' has invalid dependencyInstallSlotIds entry (expected string)." } } } } function Get-PackageInventory { <# .SYNOPSIS Loads the Package inventory. .DESCRIPTION Returns the configured package inventory document, or an empty record set when the index file does not exist yet. .PARAMETER PackageConfig The resolved Package config object. .EXAMPLE Get-PackageInventory -PackageConfig $config #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageConfig ) $indexPath = $PackageConfig.PackageAssignmentInventoryFilePath if ([string]::IsNullOrWhiteSpace($indexPath)) { throw 'Package inventory path is not configured.' } if (-not (Test-Path -LiteralPath $indexPath -PathType Leaf)) { return [pscustomobject]@{ Path = $indexPath Records = @() } } $documentInfo = Read-PackageJsonDocument -Path $indexPath $records = if ($documentInfo.Document.PSObject.Properties['records']) { @($documentInfo.Document.records) } else { @() } foreach ($record in @($records)) { Assert-PackageInventoryRecordOptionalExtensions -Record $record } return [pscustomobject]@{ Path = $documentInfo.Path Records = $records } } function Save-PackageInventory { <# .SYNOPSIS Writes the Package inventory to disk. .DESCRIPTION Persists the normalized package inventory document to the configured inventory path. .PARAMETER InventoryPath The target inventory file path. .PARAMETER Records The package inventory records to persist. .EXAMPLE Save-PackageInventory -InventoryPath $path -Records $records #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$InventoryPath, [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [object[]]$Records ) $directoryPath = Split-Path -Parent $InventoryPath if (-not [string]::IsNullOrWhiteSpace($directoryPath)) { $null = New-Item -ItemType Directory -Path $directoryPath -Force } [ordered]@{ schemaVersion = 1 records = @($Records) } | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $InventoryPath -Encoding UTF8 } function Copy-PackageDefinitionToAssignedSnapshot { <# .SYNOPSIS Copies the materialized Candidate Package definition into the Assigned snapshot store. .DESCRIPTION Stores the exact definition used by a successful Assigned operation under the configured local repository root. The Assigned snapshot is the durable definition material used later by Removed and audit flows. .PARAMETER PackageResult The current Package result object. .EXAMPLE Copy-PackageDefinitionToAssignedSnapshot -PackageResult $result #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageResult ) $config = $PackageResult.PackageConfig $sourcePath = if ($config.PSObject.Properties['DefinitionCandidatePath'] -and -not [string]::IsNullOrWhiteSpace([string]$config.DefinitionCandidatePath)) { [string]$config.DefinitionCandidatePath } else { [string]$config.DefinitionPath } if ([string]::IsNullOrWhiteSpace($sourcePath) -or -not (Test-Path -LiteralPath $sourcePath -PathType Leaf)) { throw "Package definition Candidate path '$sourcePath' is not available for Assigned snapshot copy." } $localRepositoryRoot = if ($config.PSObject.Properties['LocalRepositoryRoot'] -and -not [string]::IsNullOrWhiteSpace([string]$config.LocalRepositoryRoot)) { [string]$config.LocalRepositoryRoot } else { Get-PackageDefaultLocalRepositoryRoot } $publisherId = if ($config.PSObject.Properties['DefinitionPublisherId'] -and -not [string]::IsNullOrWhiteSpace([string]$config.DefinitionPublisherId)) { [string]$config.DefinitionPublisherId } else { 'Eigenverft' } $publisherName = if ($config.PSObject.Properties['DefinitionPublisherName']) { [string]$config.DefinitionPublisherName } else { $null } $definitionRevision = if ($config.PSObject.Properties['DefinitionRevision']) { [int]$config.DefinitionRevision } else { 0 } $assignedCopy = Copy-PackageDefinitionToLocalDefinitionStore -Role 'Assigned' -SourcePath $sourcePath -LocalRepositoryRoot $localRepositoryRoot -PublisherId $publisherId -DefinitionId ([string]$PackageResult.DefinitionId) -DefinitionRevision $definitionRevision return [pscustomobject]@{ RepositorySourceId = if ($config.PSObject.Properties['DefinitionRepositorySourceId']) { [string]$config.DefinitionRepositorySourceId } else { [string]$config.DefinitionRepositoryId } PublisherId = $publisherId PublisherName = $publisherName DefinitionRevision = $definitionRevision PublishedAtUtc = if ($config.PSObject.Properties['DefinitionPublishedAtUtc']) { [string]$config.DefinitionPublishedAtUtc } else { $null } SourceKind = if ($config.PSObject.Properties['DefinitionSourceKind']) { [string]$config.DefinitionSourceKind } else { $null } SourcePath = if ($config.PSObject.Properties['DefinitionSourcePath'] -and -not [string]::IsNullOrWhiteSpace([string]$config.DefinitionSourcePath)) { [System.IO.Path]::GetFullPath([string]$config.DefinitionSourcePath) } else { $null } SourceHash = if ($config.PSObject.Properties['DefinitionSourceHash']) { [string]$config.DefinitionSourceHash } else { $null } CandidatePath = [System.IO.Path]::GetFullPath($sourcePath) CandidateHash = Get-PackageFileSha256 -Path $sourcePath AssignedSnapshotPath = [System.IO.Path]::GetFullPath([string]$assignedCopy.Path) AssignedSnapshotHash = [string]$assignedCopy.Hash ResolvedAtUtc = if ($config.PSObject.Properties['DefinitionResolvedAtUtc'] -and -not [string]::IsNullOrWhiteSpace([string]$config.DefinitionResolvedAtUtc)) { [string]$config.DefinitionResolvedAtUtc } else { [DateTime]::UtcNow.ToString('o') } } } function Get-PackageOwnershipRecord { <# .SYNOPSIS Returns the ownership record for the current install slot and install directory. .DESCRIPTION Finds the best matching ownership record for a Package result by using the logical install slot together with the discovered install directory. .PARAMETER PackageResult The current Package result object. .EXAMPLE Get-PackageOwnershipRecord -PackageResult $result #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageResult ) $existingPackage = $PackageResult.ExistingPackage if (-not $existingPackage -or [string]::IsNullOrWhiteSpace($existingPackage.InstallDirectory)) { return $null } $index = Get-PackageInventory -PackageConfig $PackageResult.PackageConfig $installSlotId = Get-PackageInstallSlotId -PackageResult $PackageResult $normalizedInstallDirectory = [System.IO.Path]::GetFullPath($existingPackage.InstallDirectory) foreach ($record in @($index.Records)) { if ([string]::Equals([string]$record.installSlotId, $installSlotId, [System.StringComparison]::OrdinalIgnoreCase) -and [string]::Equals([string]$record.installDirectory, $normalizedInstallDirectory, [System.StringComparison]::OrdinalIgnoreCase)) { return $record } } return $null } function Resolve-PackageOwnershipKindText { [CmdletBinding()] param( [AllowNull()] [string]$OwnershipKind ) switch -Exact ([string]$OwnershipKind) { 'ManagedInstalled' { return 'PackageInstalled' } 'ManagedReused' { return 'PackageInstalled' } default { return $OwnershipKind } } } function Set-PackageExistingPackage { <# .SYNOPSIS Classifies a discovered existing install against the package inventory. .DESCRIPTION Attaches ownership classification data from the package inventory to the current existing install so later helpers can decide whether the install is Package-owned, adopted, or external. .PARAMETER PackageResult The Package result object to enrich. .EXAMPLE Set-PackageExistingPackage -PackageResult $result #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageResult ) if (-not $PackageResult.ExistingPackage) { $PackageResult.Ownership = [pscustomobject]@{ InventoryPath = $PackageResult.PackageConfig.PackageAssignmentInventoryFilePath InstallSlotId = Get-PackageInstallSlotId -PackageResult $PackageResult Classification = 'NotFound' OwnershipRecord = $null } return $PackageResult } $record = Get-PackageOwnershipRecord -PackageResult $PackageResult $classification = if ($record -or [string]::Equals([string]$PackageResult.ExistingPackage.SearchKind, 'packageTargetInstallPath', [System.StringComparison]::OrdinalIgnoreCase)) { 'PackageTarget' } else { 'ExternalInstall' } $installSlotId = Get-PackageInstallSlotId -PackageResult $PackageResult $PackageResult.Ownership = [pscustomobject]@{ InventoryPath = $PackageResult.PackageConfig.PackageAssignmentInventoryFilePath InstallSlotId = $installSlotId Classification = $classification OwnershipRecord = $record } $PackageResult.ExistingPackage.Classification = $classification $PackageResult.ExistingPackage.OwnershipRecord = $record $ownershipKindText = if ($record -and $record.PSObject.Properties['ownershipKind']) { Resolve-PackageOwnershipKindText -OwnershipKind ([string]$record.ownershipKind) } else { '<none>' } Write-PackageExecutionMessage -Message ("[STATE] Ownership classification for installSlotId '{0}' is '{1}' (ownershipKind='{2}')." -f $installSlotId, $classification, $ownershipKindText) return $PackageResult } function Update-PackageInventoryRecord { <# .SYNOPSIS Updates the package inventory record after a Package run. .DESCRIPTION Writes or refreshes the package inventory record for Package-owned installs, Package-owned reuse, and adopted external installs. External installs that were ignored are not written to the package inventory. .PARAMETER PackageResult The finalized Package result object. .EXAMPLE Update-PackageInventoryRecord -PackageResult $result #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageResult ) if (-not $PackageResult.Readiness -or -not $PackageResult.Readiness.Accepted) { return $PackageResult } $ownershipKind = switch -Exact ([string]$PackageResult.InstallOrigin) { 'PackageInstalled' { 'PackageInstalled'; break } 'PackageReused' { 'PackageInstalled'; break } 'PackageApplied' { 'PackageApplied'; break } 'AdoptedExternal' { 'AdoptedExternal'; break } default { $null } } if ([string]::IsNullOrWhiteSpace($ownershipKind)) { return $PackageResult } $index = Get-PackageInventory -PackageConfig $PackageResult.PackageConfig $normalizedInstallDirectory = if ([string]::IsNullOrWhiteSpace([string]$PackageResult.InstallDirectory)) { $null } else { [System.IO.Path]::GetFullPath($PackageResult.InstallDirectory) } $installSlotId = Get-PackageInstallSlotId -PackageResult $PackageResult $definitionCopy = Copy-PackageDefinitionToAssignedSnapshot -PackageResult $PackageResult $pathRegistrationRecord = $null if ($PackageResult.PSObject.Properties['PathRegistration'] -and $null -ne $PackageResult.PathRegistration) { $pathRegistrationRecord = [pscustomobject]@{ mode = [string]$PackageResult.PathRegistration.Mode sourceKind = [string]$PackageResult.PathRegistration.SourceKind sourceValue = [string]$PackageResult.PathRegistration.SourceValue sourceValues = @($PackageResult.PathRegistration.SourceValues) sourcePath = [string]$PackageResult.PathRegistration.SourcePath registeredPath = [string]$PackageResult.PathRegistration.RegisteredPath status = [string]$PackageResult.PathRegistration.Status } } $records = @( foreach ($record in @($index.Records)) { $sameInstallSlot = [string]::Equals([string]$record.installSlotId, $installSlotId, [System.StringComparison]::OrdinalIgnoreCase) $sameInstallDirectory = (-not [string]::IsNullOrWhiteSpace([string]$normalizedInstallDirectory)) -and [string]::Equals([string]$record.installDirectory, $normalizedInstallDirectory, [System.StringComparison]::OrdinalIgnoreCase) if (-not $sameInstallSlot -and -not $sameInstallDirectory) { $record } } ) $dependencyInstallSlotIds = @( foreach ($dependencyRow in @($PackageResult.Dependencies)) { $dependencyResult = if ($dependencyRow.PSObject.Properties['Result']) { $dependencyRow.Result } else { $null } if (-not $dependencyResult) { continue } Get-PackageInstallSlotId -PackageResult $dependencyResult } ) | Select-Object -Unique $newRecord = [pscustomobject]@{ installSlotId = $installSlotId definitionId = $PackageResult.DefinitionId definitionPublisherId = $definitionCopy.PublisherId definitionPublisherName = $definitionCopy.PublisherName definitionRevision = $definitionCopy.DefinitionRevision definitionPublishedAtUtc = $definitionCopy.PublishedAtUtc definitionRepositorySourceId = $definitionCopy.RepositorySourceId definitionSourceKind = $definitionCopy.SourceKind definitionSourcePath = $definitionCopy.SourcePath definitionSourceHash = $definitionCopy.SourceHash definitionCandidatePath = $definitionCopy.CandidatePath definitionCandidateHash = $definitionCopy.CandidateHash definitionAssignedSnapshotPath = $definitionCopy.AssignedSnapshotPath definitionAssignedSnapshotHash = $definitionCopy.AssignedSnapshotHash definitionResolvedAtUtc = $definitionCopy.ResolvedAtUtc releaseTrack = if ($PackageResult.Package -and $PackageResult.Package.PSObject.Properties['releaseTrack']) { [string]$PackageResult.Package.releaseTrack } else { [string]$PackageResult.ReleaseTrack } artifactDistributionVariant = if ($PackageResult.Package -and $PackageResult.Package.PSObject.Properties['artifactDistributionVariant']) { [string]$PackageResult.Package.artifactDistributionVariant } else { $null } currentReleaseId = $PackageResult.PackageId currentVersion = $PackageResult.PackageVersion installDirectory = $normalizedInstallDirectory ownershipKind = $ownershipKind pathRegistration = $pathRegistrationRecord dependencyInstallSlotIds = @($dependencyInstallSlotIds) updatedAtUtc = [DateTime]::UtcNow.ToString('o') } $records += $newRecord Save-PackageInventory -InventoryPath $index.Path -Records $records $PackageResult.Ownership = [pscustomobject]@{ InventoryPath = $index.Path InstallSlotId = $installSlotId Classification = if ($ownershipKind -eq 'AdoptedExternal') { 'AdoptedExternal' } else { 'PackageTarget' } OwnershipRecord = $newRecord } Write-PackageExecutionMessage -Message ("[STATE] Updated package inventory record for installSlotId '{0}' with ownershipKind='{1}' at '{2}'." -f $installSlotId, $ownershipKind, $index.Path) return $PackageResult } function Remove-PackageInventoryRecordForInstallSlot { <# .SYNOPSIS Removes the inventory record for the current Package install slot. .DESCRIPTION Loads the inventory, drops records matching installSlotId (or the same normalized install directory when present), and persists the document. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageResult ) $index = Get-PackageInventory -PackageConfig $PackageResult.PackageConfig $installSlotId = Get-PackageInstallSlotId -PackageResult $PackageResult $normalizedInstallDirectory = if ([string]::IsNullOrWhiteSpace([string]$PackageResult.InstallDirectory)) { $null } else { [System.IO.Path]::GetFullPath([string]$PackageResult.InstallDirectory) } $records = @( foreach ($record in @($index.Records)) { $sameSlot = [string]::Equals([string]$record.installSlotId, $installSlotId, [System.StringComparison]::OrdinalIgnoreCase) $sameDir = (-not [string]::IsNullOrWhiteSpace($normalizedInstallDirectory)) -and [string]::Equals([string]$record.installDirectory, $normalizedInstallDirectory, [System.StringComparison]::OrdinalIgnoreCase) if (-not $sameSlot -and -not $sameDir) { $record } } ) Save-PackageInventory -InventoryPath $index.Path -Records $records Write-PackageExecutionMessage -Message ("[ACTION] Removed package inventory record for installSlotId '{0}'." -f $installSlotId) return $PackageResult } |