Private/Logic/Eigenverft.Manifested.Sandbox.Runtime.Archive.Shared.ps1

<#
    Eigenverft.Manifested.Sandbox.Runtime.Archive.Shared
#>


function Get-ManifestedArchivePersistedPackageDetails {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$CommandName,

        [string]$LocalRoot = (Get-ManifestedLocalRoot)
    )

    $commandState = Get-ManifestedCommandState -CommandName $CommandName -LocalRoot $LocalRoot
    if ($commandState -and $commandState.PSObject.Properties['Details']) {
        return $commandState.Details
    }

    return $null
}

function Get-ManagedManifestedArchiveRuntimeHome {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$ToolsRootPath,

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

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

    return (Join-Path $ToolsRootPath ($Version + '\' + $Flavor))
}

function Get-ManifestedArchivePersistedAssetDetails {
    [CmdletBinding()]
    param(
        [pscustomobject]$PersistedDetails,

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

    if (-not $PersistedDetails) {
        return $null
    }
    if (-not $PersistedDetails.PSObject.Properties['AssetName']) {
        return $null
    }
    if ($PersistedDetails.AssetName -ne $AssetName) {
        return $null
    }

    return [pscustomobject]@{
        TagName     = if ($PersistedDetails.PSObject.Properties['Tag']) { $PersistedDetails.Tag } else { $null }
        DownloadUrl = if ($PersistedDetails.PSObject.Properties['DownloadUrl']) { $PersistedDetails.DownloadUrl } else { $null }
        Sha256      = if ($PersistedDetails.PSObject.Properties['Sha256']) { $PersistedDetails.Sha256 } else { $null }
        ShaSource   = if ($PersistedDetails.PSObject.Properties['ShaSource']) { $PersistedDetails.ShaSource } else { $null }
        Channel     = if ($PersistedDetails.PSObject.Properties['Channel']) { $PersistedDetails.Channel } else { $null }
    }
}

function Get-ManifestedArchiveCachedPackages {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$CacheRootPath,

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

        [Parameter(Mandatory = $true)]
        [scriptblock]$BuildPackageInfo,

        [Parameter(Mandatory = $true)]
        [scriptblock]$SortVersion,

        [string]$FileFilter = '*.zip'
    )

    if (-not (Test-Path -LiteralPath $CacheRootPath)) {
        return @()
    }

    $items = Get-ChildItem -LiteralPath $CacheRootPath -File -Filter $FileFilter -ErrorAction SilentlyContinue |
        Where-Object { $_.Name -match $Pattern } |
        ForEach-Object {
            $matchTable = @{}
            foreach ($matchKey in @($matches.Keys)) {
                $matchTable[$matchKey] = $matches[$matchKey]
            }

            & $BuildPackageInfo $_ $matchTable
        } |
        Sort-Object -Descending -Property @{ Expression = { & $SortVersion $_ } }

    return @($items)
}

function Get-LatestManifestedArchiveRuntimePackage {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [object[]]$CachedPackages
    )

    $trustedPackage = @($CachedPackages | Where-Object { -not [string]::IsNullOrWhiteSpace($_.Sha256) } | Select-Object -First 1)
    if ($trustedPackage) {
        return $trustedPackage[0]
    }

    return ($CachedPackages | Select-Object -First 1)
}

function Get-ManifestedArchiveRuntimePartialPaths {
    [CmdletBinding()]
    param(
        [string]$CacheRootPath,

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

        [string[]]$LegacyRootPaths = @()
    )

    $partialPaths = @()
    if (-not [string]::IsNullOrWhiteSpace($CacheRootPath) -and (Test-Path -LiteralPath $CacheRootPath)) {
        $partialPaths += @(Get-ChildItem -LiteralPath $CacheRootPath -File -Filter '*.download' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName)
    }

    $partialPaths += @(
        Get-ManifestedStageDirectories -Prefix $StagePrefix -Mode TemporaryShort -LegacyRootPaths $LegacyRootPaths |
            Select-Object -ExpandProperty FullName
    )

    return @($partialPaths)
}

function Get-ManifestedArchiveRuntimeSelection {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [pscustomobject]$Installed,

        [pscustomobject]$ExternalRuntime,

        [pscustomobject]$Package,

        [string[]]$PartialPaths = @()
    )

    $managedRuntime = $Installed.Current
    $currentRuntime = if ($managedRuntime) { $managedRuntime } else { $ExternalRuntime }
    $runtimeSource = if ($managedRuntime) { 'Managed' } elseif ($ExternalRuntime) { 'External' } else { $null }
    $invalidRuntimeHomes = @($Installed.Invalid | Select-Object -ExpandProperty RuntimeHome)

    if ($invalidRuntimeHomes.Count -gt 0) {
        $status = 'NeedsRepair'
    }
    elseif (@($PartialPaths).Count -gt 0) {
        $status = 'Partial'
    }
    elseif ($currentRuntime) {
        $status = 'Ready'
    }
    elseif ($Package) {
        $status = 'NeedsInstall'
    }
    else {
        $status = 'Missing'
    }

    [pscustomobject]@{
        Status              = $status
        CurrentRuntime      = $currentRuntime
        RuntimeSource       = $runtimeSource
        InvalidRuntimeHomes = $invalidRuntimeHomes
    }
}

function Repair-ManifestedArchiveRuntimeArtifacts {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [pscustomobject]$State,

        [string[]]$CorruptPackagePaths = @()
    )

    $pathsToRemove = New-Object System.Collections.Generic.List[string]
    foreach ($path in @($State.PartialPaths)) {
        if (-not [string]::IsNullOrWhiteSpace($path)) {
            $pathsToRemove.Add($path) | Out-Null
        }
    }
    foreach ($path in @($State.InvalidRuntimeHomes)) {
        if (-not [string]::IsNullOrWhiteSpace($path)) {
            $pathsToRemove.Add($path) | Out-Null
        }
    }
    foreach ($path in @($CorruptPackagePaths)) {
        if (-not [string]::IsNullOrWhiteSpace($path)) {
            $pathsToRemove.Add($path) | Out-Null
        }
    }

    $removedPaths = New-Object System.Collections.Generic.List[string]
    foreach ($path in @($pathsToRemove | Select-Object -Unique)) {
        if (Remove-ManifestedPath -Path $path) {
            $removedPaths.Add($path) | Out-Null
        }
    }

    [pscustomobject]@{
        Action       = if ($removedPaths.Count -gt 0) { 'Repaired' } else { 'Skipped' }
        RemovedPaths = @($removedPaths)
        LocalRoot    = $State.LocalRoot
        Layout       = $State.Layout
    }
}

function Save-ManifestedArchiveRuntimePackage {
    [CmdletBinding()]
    param(
        [pscustomobject]$Release,

        [switch]$Refresh,

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

        [Parameter(Mandatory = $true)]
        [scriptblock]$GetCachedPackage,

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

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

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

    New-ManifestedDirectory -Path $CacheRootPath | Out-Null

    if ($Release) {
        $packagePath = Join-Path $CacheRootPath $Release.FileName
        $downloadPath = Get-ManifestedDownloadPath -TargetPath $packagePath
        $action = 'ReusedCache'

        if ($Refresh -or -not (Test-Path -LiteralPath $packagePath)) {
            Remove-ManifestedPath -Path $downloadPath | Out-Null

            try {
                Write-Host ("Downloading {0} {1} ({2})..." -f $DownloadLabel, $Release.Version, $Release.Flavor)
                Enable-ManifestedTls12Support
                Invoke-WebRequestEx -Uri $Release.DownloadUrl -Headers @{ 'User-Agent' = 'Eigenverft.Manifested.Sandbox' } -OutFile $downloadPath -UseBasicParsing
                Move-Item -LiteralPath $downloadPath -Destination $packagePath -Force
                $action = 'Downloaded'
            }
            catch {
                Remove-ManifestedPath -Path $downloadPath | Out-Null
                if (-not (Test-Path -LiteralPath $packagePath)) {
                    throw
                }

                Write-Warning ($RefreshWarningPrefix + $_.Exception.Message)
                $action = 'ReusedCache'
            }
        }

        $packageProperties = [ordered]@{}
        foreach ($property in @($Release.PSObject.Properties)) {
            $packageProperties[$property.Name] = $property.Value
        }
        $packageProperties['Path'] = $packagePath
        $packageProperties['Source'] = if ($action -eq 'Downloaded') { 'online' } else { 'cache' }
        $packageProperties['Action'] = $action
        return [pscustomobject]$packageProperties
    }

    $cachedPackage = & $GetCachedPackage
    if (-not $cachedPackage) {
        throw $OfflineErrorMessage
    }

    $packageProperties = [ordered]@{}
    foreach ($property in @($cachedPackage.PSObject.Properties)) {
        $packageProperties[$property.Name] = $property.Value
    }
    $packageProperties['Source'] = 'cache'
    $packageProperties['Action'] = 'SelectedCache'
    return [pscustomobject]$packageProperties
}

function Install-ManifestedArchiveRuntimeFromPackage {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [pscustomobject]$PackageInfo,

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

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

        [Parameter(Mandatory = $true)]
        [scriptblock]$TestRuntime,

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

        [scriptblock]$PostInstall
    )

    $currentValidation = & $TestRuntime $RuntimeHome

    if ($currentValidation.Status -ne 'Ready') {
        New-ManifestedDirectory -Path (Split-Path -Parent $RuntimeHome) | Out-Null

        $stageInfo = $null
        try {
            $stageInfo = Expand-ManifestedArchiveToStage -PackagePath $PackageInfo.Path -Prefix $StagePrefix

            if (Test-Path -LiteralPath $RuntimeHome) {
                Remove-Item -LiteralPath $RuntimeHome -Recurse -Force
            }

            New-ManifestedDirectory -Path $RuntimeHome | Out-Null
            Get-ChildItem -LiteralPath $stageInfo.ExpandedRoot -Force | ForEach-Object {
                Move-Item -LiteralPath $_.FullName -Destination $RuntimeHome -Force
            }

            if ($PostInstall) {
                & $PostInstall $RuntimeHome
            }
        }
        finally {
            if ($stageInfo) {
                Remove-ManifestedPath -Path $stageInfo.StagePath | Out-Null
            }
        }
    }

    $validation = & $TestRuntime $RuntimeHome
    if ($validation.Status -ne 'Ready') {
        throw ("{0} runtime validation failed after install at {1}." -f $DisplayName, $RuntimeHome)
    }

    [pscustomobject]@{
        Action            = if ($currentValidation.Status -eq 'Ready') { 'Skipped' } else { 'Installed' }
        RuntimeHome       = $RuntimeHome
        CurrentValidation = $currentValidation
        Validation        = $validation
    }
}