Support/Package/Lifecycle/Eigenverft.Manifested.Sandbox.Package.Remove.ps1

<#
    Eigenverft.Manifested.Sandbox.Package.Remove - DesiredState Removed orchestration.
    Dot-sourced from Eigenverft.Manifested.Sandbox.psm1 after Package.CommandFlow.ps1.
 
    Removal safety: other inventory rows are scanned by loading each row's definition and
    matching dependencies against the target definition (repositoryId + definitionId).
    Persisted dependencyInstallSlotIds on inventory rows (see Update-PackageInventoryRecord)
    documents direct dependency slots for operators; blocking is driven by definition
    metadata so stale slot lists cannot bypass the check.
#>


function Get-PackageInventoryDependentBlockingRecords {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$PackageConfig,

        [Parameter(Mandatory = $true)]
        [string]$ExcludeInstallSlotId,

        [Parameter(Mandatory = $true)]
        [string]$TargetRepositoryId,

        [Parameter(Mandatory = $true)]
        [string]$TargetDefinitionId
    )

    $targetKey = Get-PackageDependencyReferenceKey -RepositoryId $TargetRepositoryId -DefinitionId $TargetDefinitionId
    $index = Get-PackageInventory -PackageConfig $PackageConfig
    $blockers = New-Object System.Collections.Generic.List[object]

    foreach ($record in @($index.Records)) {
        $slot = [string]$record.installSlotId
        if ([string]::Equals($slot, $ExcludeInstallSlotId, [System.StringComparison]::OrdinalIgnoreCase)) {
            continue
        }

        $parentRepositoryId = if ($record.PSObject.Properties['definitionRepositoryId'] -and -not [string]::IsNullOrWhiteSpace([string]$record.definitionRepositoryId)) {
            [string]$record.definitionRepositoryId
        }
        else {
            Get-PackageDefaultRepositoryId
        }
        $parentDefinitionId = [string]$record.definitionId

        try {
            $definitionReference = Resolve-PackageDefinitionReference -RepositoryId $parentRepositoryId -DefinitionId $parentDefinitionId
            $definitionDocumentInfo = Read-PackageJsonDocument -Path $definitionReference.DefinitionPath
            Assert-PackageDefinitionSchema -DefinitionDocumentInfo $definitionDocumentInfo -DefinitionId $parentDefinitionId -DefinitionRepositoryId $parentRepositoryId
        }
        catch {
            throw "Package removal dependency scan failed while reading definition '$parentRepositoryId/$parentDefinitionId' for inventory installSlotId '$slot': $($_.Exception.Message)"
        }

        $definition = $definitionDocumentInfo.Document
        if (-not $definition.PSObject.Properties['dependencies'] -or $null -eq $definition.dependencies) {
            continue
        }

        foreach ($dependency in @($definition.dependencies)) {
            if ($null -eq $dependency) {
                continue
            }

            $depDefinitionId = if ($dependency.PSObject.Properties['definitionId']) { [string]$dependency.definitionId } else { $null }
            if ([string]::IsNullOrWhiteSpace($depDefinitionId)) {
                continue
            }

            $depRepositoryId = if ($dependency.PSObject.Properties['repositoryId'] -and -not [string]::IsNullOrWhiteSpace([string]$dependency.repositoryId)) {
                [string]$dependency.repositoryId
            }
            else {
                $parentRepositoryId
            }

            $depKey = Get-PackageDependencyReferenceKey -RepositoryId $depRepositoryId -DefinitionId $depDefinitionId
            if ([string]::Equals($depKey, $targetKey, [System.StringComparison]::OrdinalIgnoreCase)) {
                $blockers.Add([pscustomobject]@{
                    DependentInstallSlotId = $slot
                    DependentDefinitionId  = $parentDefinitionId
                    DependentRepositoryId  = $parentRepositoryId
                }) | Out-Null
                break
            }
        }
    }

    return @($blockers.ToArray())
}

function Assert-PackageRemovalDependencyDependents {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$PackageResult
    )

    if ($PackageResult.PSObject.Properties['InventoryRemovalSkipped'] -and [bool]$PackageResult.InventoryRemovalSkipped) {
        return $PackageResult
    }

    $targetRepositoryId = Get-PackageResultRepositoryId -PackageResult $PackageResult
    $targetDefinitionId = [string]$PackageResult.DefinitionId
    $excludeSlotId = Get-PackageInstallSlotId -PackageResult $PackageResult
    $blockers = @(Get-PackageInventoryDependentBlockingRecords -PackageConfig $PackageResult.PackageConfig -ExcludeInstallSlotId $excludeSlotId -TargetRepositoryId $targetRepositoryId -TargetDefinitionId $targetDefinitionId)
    if ($blockers.Count -gt 0) {
        $summaries = @(
            foreach ($b in @($blockers)) {
                "'$($b.DependentRepositoryId)/$($b.DependentDefinitionId)' (installSlotId=$($b.DependentInstallSlotId))"
            }
        )
        throw ("Package removal blocked: '{0}/{1}' is still declared as a dependency by installed package(s): {2}. Remove those packages first (or implement removeDependencies)." -f $targetRepositoryId, $targetDefinitionId, ($summaries -join '; '))
    }

    return $PackageResult
}

function Get-PackageExistingInstallSearchLocationById {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$Definition,

        [Parameter(Mandatory = $true)]
        [string]$SearchLocationId
    )

    $discovery = $Definition.existingInstallDiscovery
    if (-not $discovery -or -not $discovery.PSObject.Properties['searchLocations']) {
        throw "Package definition '$($Definition.id)' is missing existingInstallDiscovery.searchLocations required for removal uninstall discovery."
    }

    foreach ($searchLocation in @(Get-PackageExistingInstallSearchLocations -SearchLocations @($discovery.searchLocations))) {
        if ($searchLocation.PSObject.Properties['id'] -and
            [string]::Equals([string]$searchLocation.id, $SearchLocationId, [System.StringComparison]::OrdinalIgnoreCase)) {
            return $searchLocation
        }
    }

    throw "Package definition '$($Definition.id)' has no existingInstallDiscovery.searchLocations entry with id '$SearchLocationId'."
}

function Get-PackageUninstallExecutableAndArgumentTail {
    [CmdletBinding()]
    param(
        [AllowNull()]
        [string]$RawText
    )

    if ([string]::IsNullOrWhiteSpace($RawText)) {
        return [pscustomobject]@{
            Executable     = $null
            ArgumentTokens = @()
        }
    }

    $expanded = [Environment]::ExpandEnvironmentVariables(([string]$RawText).Trim())
    $exe = Get-WindowsRegistryExecutablePathFromText -Text $expanded
    if ([string]::IsNullOrWhiteSpace($exe)) {
        return [pscustomobject]@{
            Executable     = $null
            ArgumentTokens = @()
        }
    }

    $idx = $expanded.IndexOf($exe, [System.StringComparison]::OrdinalIgnoreCase)
    $tailStart = if ($idx -lt 0) { -1 } else { $idx + $exe.Length }
    if ($tailStart -ge 0 -and
        $idx -gt 0 -and
        $expanded[$idx - 1] -eq '"' -and
        $expanded.Length -gt $tailStart -and
        $expanded[$tailStart] -eq '"') {
        $tailStart++
    }

    $after = if ($tailStart -lt 0) { '' } else { $expanded.Substring($tailStart).Trim() }
    $tokens = if ([string]::IsNullOrWhiteSpace($after)) {
        @()
    }
    else {
        @($after -split '\s+' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
    }

    return [pscustomobject]@{
        Executable     = $exe
        ArgumentTokens = @($tokens)
    }
}

function ConvertFrom-PackageInventoryPathRegistrationRecord {
    [CmdletBinding()]
    param(
        [AllowNull()]
        [psobject]$PathRegistrationRecord
    )

    if ($null -eq $PathRegistrationRecord) {
        return $null
    }

    $sourceValues = if ($PathRegistrationRecord.PSObject.Properties['sourceValues'] -and $null -ne $PathRegistrationRecord.sourceValues) {
        @($PathRegistrationRecord.sourceValues | ForEach-Object { [string]$_ })
    }
    else {
        @()
    }

    $cleanupDirectories = if ($PathRegistrationRecord.PSObject.Properties['cleanupDirectories'] -and $null -ne $PathRegistrationRecord.cleanupDirectories) {
        @($PathRegistrationRecord.cleanupDirectories | ForEach-Object { [string]$_ })
    }
    else {
        @()
    }

    return [pscustomobject]@{
        Status             = if ($PathRegistrationRecord.PSObject.Properties['status']) { [string]$PathRegistrationRecord.status } else { $null }
        Mode               = if ($PathRegistrationRecord.PSObject.Properties['mode']) { [string]$PathRegistrationRecord.mode } else { $null }
        SourceKind         = if ($PathRegistrationRecord.PSObject.Properties['sourceKind']) { [string]$PathRegistrationRecord.sourceKind } else { $null }
        SourceValue        = if ($PathRegistrationRecord.PSObject.Properties['sourceValue']) { [string]$PathRegistrationRecord.sourceValue } else { $null }
        SourceValues       = @($sourceValues)
        SourcePath         = if ($PathRegistrationRecord.PSObject.Properties['sourcePath']) { [string]$PathRegistrationRecord.sourcePath } else { $null }
        RegisteredPath     = if ($PathRegistrationRecord.PSObject.Properties['registeredPath']) { [string]$PathRegistrationRecord.registeredPath } else { $null }
        CleanupDirectories = @($cleanupDirectories)
        CleanedTargets     = @()
        UpdatedTargets     = @()
    }
}

function Resolve-PackageRemovalInstallContext {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$PackageResult
    )

    $definition = $PackageResult.PackageConfig.Definition
    $removed = $definition.packageOperations.removed
    $policy = $removed.policy
    $index = Get-PackageInventory -PackageConfig $PackageResult.PackageConfig
    $installSlotId = Get-PackageInstallSlotId -PackageResult $PackageResult

    $record = $null
    foreach ($candidate in @($index.Records)) {
        if ([string]::Equals([string]$candidate.installSlotId, $installSlotId, [System.StringComparison]::OrdinalIgnoreCase)) {
            $record = $candidate
            break
        }
    }

    if ($null -eq $record) {
        if ([string]::Equals([string]$policy.whenNotInInventory, 'fail', [System.StringComparison]::OrdinalIgnoreCase)) {
            throw "Package removal failed for '$($PackageResult.DefinitionId)': no inventory record for install slot '$installSlotId' and policy.whenNotInInventory is 'fail'."
        }

        $PackageResult | Add-Member -Force -MemberType NoteProperty -Name 'InventoryRemovalSkipped' -Value $true
        Write-PackageExecutionMessage -Message ("[STATE] Removal skipped destructive work: no inventory record for install slot '{0}' (whenNotInInventory='succeed')." -f $installSlotId)
        return $PackageResult
    }

    $PackageResult | Add-Member -Force -MemberType NoteProperty -Name 'InventoryRemovalSkipped' -Value $false

    if ([string]::IsNullOrWhiteSpace([string]$record.installDirectory)) {
        throw "Package removal failed for '$($PackageResult.DefinitionId)': inventory record is missing installDirectory."
    }

    $normalizedInstallDirectory = [System.IO.Path]::GetFullPath([string]$record.installDirectory)
    $PackageResult.InstallDirectory = $normalizedInstallDirectory

    $ownershipKind = if ($record.PSObject.Properties['ownershipKind']) { [string]$record.ownershipKind } else { $null }
    $installOrigin = switch -Exact ($ownershipKind) {
        'PackageInstalled' { 'PackageInstalled'; break }
        'PackageApplied' { 'PackageApplied'; break }
        'AdoptedExternal' { 'AdoptedExternal'; break }
        default { $ownershipKind }
    }
    $PackageResult.InstallOrigin = $installOrigin

    $PackageResult.Ownership = [pscustomobject]@{
        InventoryPath   = $index.Path
        InstallSlotId   = $installSlotId
        Classification  = 'PackageTarget'
        OwnershipRecord = $record
    }

    $PackageResult.ExistingPackage = [pscustomobject]@{
        SearchKind       = 'packageTargetInstallPath'
        CandidatePath    = $normalizedInstallDirectory
        InstallDirectory = $normalizedInstallDirectory
        Decision         = 'Pending'
        Readiness       = $null
        Classification   = 'PackageTarget'
        OwnershipRecord  = $record
    }

    $pathRegistration = if ($record.PSObject.Properties['pathRegistration'] -and $null -ne $record.pathRegistration) {
        ConvertFrom-PackageInventoryPathRegistrationRecord -PathRegistrationRecord $record.pathRegistration
    }
    else {
        $null
    }

    if ($pathRegistration) {
        if ($PackageResult.PSObject.Properties['PathRegistration']) {
            $PackageResult.PathRegistration = $pathRegistration
        }
        else {
            $PackageResult | Add-Member -MemberType NoteProperty -Name PathRegistration -Value $pathRegistration
        }
    }

    Write-PackageExecutionMessage -Message ("[STATE] Removal inventory context: installSlotId='{0}', installDirectory='{1}', ownershipKind='{2}'." -f $installSlotId, $normalizedInstallDirectory, $ownershipKind)

    return $PackageResult
}

function Assert-PackageRemovalPolicy {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$PackageResult
    )

    $definition = $PackageResult.PackageConfig.Definition
    $policy = $definition.packageOperations.removed.policy

    if ($policy.removeDependencies -is [bool] -and [bool]$policy.removeDependencies) {
        throw "Package removal for '$($PackageResult.DefinitionId)' requested policy.removeDependencies=true, which is not implemented yet. Set removeDependencies to false for v1 removal."
    }

    if ($PackageResult.PSObject.Properties['InventoryRemovalSkipped'] -and [bool]$PackageResult.InventoryRemovalSkipped) {
        return $PackageResult
    }

    $record = $PackageResult.Ownership.OwnershipRecord
    $allowedKinds = @($policy.allowedInventoryOwnershipKinds | ForEach-Object { [string]$_ })
    $kind = if ($record -and $record.PSObject.Properties['ownershipKind']) { [string]$record.ownershipKind } else { $null }
    if ([string]::IsNullOrWhiteSpace($kind)) {
        throw "Package removal failed for '$($PackageResult.DefinitionId)': inventory record is missing ownershipKind."
    }

    $allowed = $false
    foreach ($allowedKind in @($allowedKinds)) {
        if ([string]::Equals($allowedKind, $kind, [System.StringComparison]::OrdinalIgnoreCase)) {
            $allowed = $true
            break
        }
    }

    if (-not $allowed) {
        throw "Package removal failed for '$($PackageResult.DefinitionId)': inventory ownershipKind '$kind' is not allowed by removed.policy.allowedInventoryOwnershipKinds."
    }

    return $PackageResult
}

function Test-PackageProcessArgumentPresent {
    [CmdletBinding()]
    param(
        [AllowNull()]
        [string[]]$Arguments,

        [AllowNull()]
        [string]$Argument
    )

    if ([string]::IsNullOrWhiteSpace($Argument)) {
        return $true
    }

    $normalizedArgument = ([string]$Argument).Trim().Trim('"')
    foreach ($existingArgument in @($Arguments)) {
        if ([string]::Equals(([string]$existingArgument).Trim().Trim('"'), $normalizedArgument, [System.StringComparison]::OrdinalIgnoreCase)) {
            return $true
        }
    }

    return $false
}

function Invoke-PackageTrackedInstallDirectoryRemoval {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$PackageResult,

        [AllowNull()]
        [string]$Reason
    )

    $target = [string]$PackageResult.InstallDirectory
    if ([string]::IsNullOrWhiteSpace($target)) {
        throw "Package removed operation 'deleteInstallDirectory' requires a resolved inventory installDirectory."
    }

    if (Test-Path -LiteralPath $target) {
        Remove-PathIfExists -Path $target | Out-Null
        $reasonText = if ([string]::IsNullOrWhiteSpace($Reason)) { '' } else { " ($Reason)" }
        Write-PackageExecutionMessage -Message ("[ACTION] Deleted tracked install directory '{0}'{1}." -f $target, $reasonText)
    }
    else {
        Write-PackageExecutionMessage -Message ("[STATE] tracked install directory removal skipped; path does not exist: '{0}'." -f $target)
    }

    $ceiling = Get-EmptyParentPruneCeilingDirectory -InstallLeafPath $target -PreferredInstallRootDirectory ([string]$PackageResult.PackageConfig.PreferredTargetInstallRootDirectory)
    if (-not [string]::IsNullOrWhiteSpace($ceiling)) {
        Remove-EmptyParentDirectoryChain -DeletedLeafPath $target -AncestorCeilingDirectory $ceiling
        Write-PackageExecutionMessage -Message '[ACTION] Pruned empty parent directories after tracked install directory removal (up to configured install root when under it, otherwise up to volume or share root).'
    }
    else {
        Write-PackageExecutionMessage -Level 'WRN' -Message '[WARN] Could not resolve empty-parent prune ceiling; skipping prune after tracked install directory removal.'
    }

    return $PackageResult
}

function Invoke-PackageRegistryUninstaller {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$PackageResult,

        [Parameter(Mandatory = $true)]
        [psobject]$Operation,

        [Parameter(Mandatory = $true)]
        [string]$InstallerKind
    )

    $definition = $PackageResult.PackageConfig.Definition
    $searchLocation = Get-PackageExistingInstallSearchLocationById -Definition $definition -SearchLocationId ([string]$Operation.commandSource.searchLocationId)
    $resolved = Resolve-PackageExistingUninstallRegistryCandidate -SearchLocation $searchLocation
    if (-not $resolved -or -not $resolved.RegistryEntry) {
        return [pscustomobject]@{
            Status        = 'CommandNotFound'
            InstallerKind = $InstallerKind
            CommandPath   = $null
        }
    }

    $entry = $resolved.RegistryEntry
    $chosenText = $null
    foreach ($registryValueName in @($Operation.commandSource.registryValueOrder)) {
        $prop = if ([string]::Equals([string]$registryValueName, 'QuietUninstallString', [System.StringComparison]::OrdinalIgnoreCase)) {
            'QuietUninstallString'
        }
        else {
            'UninstallString'
        }
        if (-not $entry.PSObject.Properties[$prop]) {
            continue
        }
        $text = [string]$entry.$prop
        if (-not [string]::IsNullOrWhiteSpace($text)) {
            $chosenText = $text
            break
        }
    }

    if ([string]::IsNullOrWhiteSpace($chosenText)) {
        return [pscustomobject]@{
            Status        = 'CommandNotFound'
            InstallerKind = $InstallerKind
            CommandPath   = $null
        }
    }

    $parsed = Get-PackageUninstallExecutableAndArgumentTail -RawText $chosenText
    if ([string]::IsNullOrWhiteSpace($parsed.Executable) -or -not (Test-Path -LiteralPath $parsed.Executable -PathType Leaf)) {
        return [pscustomobject]@{
            Status        = 'CommandNotFound'
            InstallerKind = $InstallerKind
            CommandPath   = $parsed.Executable
        }
    }

    $argumentTexts = New-Object System.Collections.Generic.List[string]
    $commandArguments = New-Object System.Collections.Generic.List[string]
    foreach ($token in @($parsed.ArgumentTokens)) {
        $argumentTexts.Add([string]$token) | Out-Null
        $commandArguments.Add((Format-PackageProcessArgument -Value $token)) | Out-Null
    }

    foreach ($argument in @($Operation.commandArguments)) {
        $resolvedArgument = Resolve-PackageTemplateText -Text ([string]$argument) -PackageConfig $PackageResult.PackageConfig -Package $PackageResult.Package -ExtraTokens @{
            packageFilePath                = $PackageResult.PackageFilePath
            installDirectory               = $PackageResult.InstallDirectory
            packageFileStagingDirectory    = $PackageResult.PackageFileStagingDirectory
            packageInstallStageDirectory   = $PackageResult.PackageInstallStageDirectory
            downloadDirectory              = $PackageResult.PackageFileStagingDirectory
        }
        if (Test-PackageProcessArgumentPresent -Arguments @($argumentTexts.ToArray()) -Argument $resolvedArgument) {
            continue
        }
        $argumentTexts.Add([string]$resolvedArgument) | Out-Null
        $commandArguments.Add((Format-PackageProcessArgument -Value $resolvedArgument)) | Out-Null
    }

    $timeoutSec = [int]$Operation.timeoutSec
    $successExitCodes = @($Operation.successExitCodes | ForEach-Object { [int]$_ })
    $restartExitCodes = @($Operation.restartExitCodes | ForEach-Object { [int]$_ })
    $uiMode = [string]$Operation.uiMode
    $workingDirectory = if (-not [string]::IsNullOrWhiteSpace([string]$PackageResult.InstallDirectory) -and (Test-Path -LiteralPath $PackageResult.InstallDirectory -PathType Container)) {
        [string]$PackageResult.InstallDirectory
    }
    elseif (-not [string]::IsNullOrWhiteSpace([string]$PackageResult.PackageInstallStageDirectory)) {
        [string]$PackageResult.PackageInstallStageDirectory
    }
    else {
        [System.IO.Path]::GetTempPath()
    }

    $elevationMode = if ($Operation.PSObject.Properties['elevation'] -and -not [string]::IsNullOrWhiteSpace([string]$Operation.elevation)) {
        [string]$Operation.elevation
    }
    else {
        $null
    }

    $null = Invoke-PackageInstallerCommand -PackageResult $PackageResult -CommandPath $parsed.Executable -CommandArguments @($commandArguments.ToArray()) -WorkingDirectory $workingDirectory -TimeoutSec $timeoutSec -SuccessExitCodes @($successExitCodes) -RestartExitCodes @($restartExitCodes) -TargetKind 'directory' -InstallerKind $InstallerKind -UiMode $uiMode -LogPath $null -ElevationMode $elevationMode
    Write-PackageExecutionMessage -Message ("[ACTION] Completed {0} uninstall command '{1}'." -f $InstallerKind, $parsed.Executable)

    return [pscustomobject]@{
        Status        = 'Invoked'
        InstallerKind = $InstallerKind
        CommandPath   = $parsed.Executable
    }
}

function Invoke-PackageRemovedOperation {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$PackageResult
    )

    $definition = $PackageResult.PackageConfig.Definition
    $operation = $definition.packageOperations.removed.operation
    $kind = [string]$operation.kind

    if ($PackageResult.PSObject.Properties['InventoryRemovalSkipped'] -and [bool]$PackageResult.InventoryRemovalSkipped) {
        Write-PackageExecutionMessage -Message '[STATE] Removed operation skipped because no inventory record was found.'
        return $PackageResult
    }

    switch -Exact ($kind) {
        'none' {
            Write-PackageExecutionMessage -Message '[STATE] Removed operation kind is none; nothing to execute.'
        }
        'deleteInstallDirectory' {
            $PackageResult = Invoke-PackageTrackedInstallDirectoryRemoval -PackageResult $PackageResult -Reason 'removed.operation.kind=deleteInstallDirectory'
        }
        { $_ -in @('nsisUninstaller', 'innoSetupUninstaller') } {
            $installerKind = if ([string]::Equals($kind, 'innoSetupUninstaller', [System.StringComparison]::OrdinalIgnoreCase)) { 'innoSetup' } else { 'nsis' }
            $uninstallResult = Invoke-PackageRegistryUninstaller -PackageResult $PackageResult -Operation $operation -InstallerKind $installerKind
            if ([string]::Equals([string]$uninstallResult.Status, 'CommandNotFound', [System.StringComparison]::OrdinalIgnoreCase)) {
                Write-PackageExecutionMessage -Level 'WRN' -Message ("[WARN] Original installer uninstall command was not found for '{0}'; falling back to tracked install directory removal." -f $PackageResult.DefinitionId)
                $PackageResult = Invoke-PackageTrackedInstallDirectoryRemoval -PackageResult $PackageResult -Reason 'installer uninstall command not found'
            }
        }
        default {
            throw "Unsupported removed.operation.kind '$kind'."
        }
    }

    return $PackageResult
}

function Invoke-PackagePostRemoveCleanup {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$PackageResult
    )

    $flags = $PackageResult.PackageConfig.Definition.packageOperations.removed.postRemoveCleanup

    if ($flags.generatedShims) {
        $null = Remove-PackageCommandShimsForDefinition -PackageResult $PackageResult
    }
    else {
        Write-PackageExecutionMessage -Message '[STATE] postRemoveCleanup.generatedShims is false; skipping shim removal.'
    }

    if ($flags.pathEntries) {
        $null = Unregister-PackagePathForRemoval -PackageResult $PackageResult
    }
    else {
        Write-PackageExecutionMessage -Message '[STATE] postRemoveCleanup.pathEntries is false; skipping PATH cleanup.'
    }

    if ($flags.packageInventoryRecord) {
        if ($PackageResult.PSObject.Properties['InventoryRemovalSkipped'] -and [bool]$PackageResult.InventoryRemovalSkipped) {
            Write-PackageExecutionMessage -Message '[STATE] postRemoveCleanup.packageInventoryRecord skipped because there was no inventory record.'
        }
        else {
            $null = Remove-PackageInventoryRecordForInstallSlot -PackageResult $PackageResult
        }
    }
    else {
        Write-PackageExecutionMessage -Message '[STATE] postRemoveCleanup.packageInventoryRecord is false; skipping inventory record removal.'
    }

    if ($flags.workDirectories) {
        $null = Clear-PackageWorkDirectories -PackageResult $PackageResult
    }
    else {
        Write-PackageExecutionMessage -Message '[STATE] postRemoveCleanup.workDirectories is false; skipping staging directory cleanup.'
    }

    return $PackageResult
}

function Invoke-PackageRemovedFlow {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$PackageResult
    )

    try {
        Write-PackageExecutionMessage -Message ("[START] Invoke-Package repository='{0}' definition='{1}' desiredState='{2}'." -f $PackageResult.RepositoryId, $PackageResult.DefinitionId, $PackageResult.DesiredState)

        $PackageResult.CurrentStep = 'InitializeLocalEnvironment'
        Write-PackageExecutionMessage -Message '[STEP] Initializing local package environment.'
        $PackageResult.LocalEnvironment = Initialize-PackageLocalEnvironment -PackageConfig $PackageResult.PackageConfig
        if ($PackageResult.LocalEnvironment.InitializedNow) {
            Write-PackageExecutionMessage -Message ("[STATE] Local package environment initialized: created={0}, existing={1}, skippedSources={2}." -f @($PackageResult.LocalEnvironment.CreatedDirectories).Count, @($PackageResult.LocalEnvironment.ExistingDirectories).Count, @($PackageResult.LocalEnvironment.SkippedSources).Count)
        }
        else {
            Write-PackageExecutionMessage -Message '[STATE] Local package environment already initialized.'
        }

        $PackageResult.CurrentStep = 'ResolvePackage'
        Write-PackageExecutionMessage -Message '[STEP] Resolving package selection.'
        $PackageResult = Resolve-PackagePackage -PackageResult $PackageResult

        $PackageResult.CurrentStep = 'ResolvePaths'
        Write-PackageExecutionMessage -Message '[STEP] Resolving package paths.'
        $PackageResult = Resolve-PackagePaths -PackageResult $PackageResult

        $PackageResult.CurrentStep = 'ResolveRemovalInstallContext'
        Write-PackageExecutionMessage -Message '[STEP] Resolving removal inventory context.'
        $PackageResult = Resolve-PackageRemovalInstallContext -PackageResult $PackageResult

        $PackageResult.CurrentStep = 'AssertRemovalPolicy'
        Write-PackageExecutionMessage -Message '[STEP] Evaluating removal policy.'
        $PackageResult = Assert-PackageRemovalPolicy -PackageResult $PackageResult

        $PackageResult.CurrentStep = 'AssertRemovalDependencyDependents'
        Write-PackageExecutionMessage -Message '[STEP] Checking for installed packages that still declare this package as a dependency.'
        $PackageResult = Assert-PackageRemovalDependencyDependents -PackageResult $PackageResult

        $PackageResult.CurrentStep = 'ExecuteRemovedOperation'
        Write-PackageExecutionMessage -Message '[STEP] Executing removed.operation.'
        $PackageResult = Invoke-PackageRemovedOperation -PackageResult $PackageResult

        $PackageResult.CurrentStep = 'VerifyRemovedAbsence'
        Write-PackageExecutionMessage -Message '[STEP] Verifying removed absence.'
        $PackageResult = Test-PackageRemovedAbsence -PackageResult $PackageResult

        $PackageResult.CurrentStep = 'PostRemoveCleanup'
        Write-PackageExecutionMessage -Message '[STEP] Running post-remove cleanup.'
        $PackageResult = Invoke-PackagePostRemoveCleanup -PackageResult $PackageResult

        Write-PackageExecutionMessage -Message ("[OK] Package removal completed for definition '{0}'." -f $PackageResult.DefinitionId)
    }
    catch {
        $PackageResult.Status = 'Failed'
        $PackageResult.ErrorMessage = $_.Exception.Message
        Write-PackageExecutionMessage -Level 'ERR' -Message ("[FAIL] Step '{0}' failed: {1}" -f $PackageResult.CurrentStep, $_.Exception.Message)
        $PackageResult.FailureReason = Get-PackageCommandFailureReason -CurrentStep ([string]$PackageResult.CurrentStep)
    }

    return $PackageResult
}