Support/Package/Lifecycle/Eigenverft.Manifested.Sandbox.Package.Install.InstallerEngine.ps1

<#
    Eigenverft.Manifested.Sandbox.Package.Install — installer process/NSIS helpers (shared execution mechanics).
    Dot-sourced from Eigenverft.Manifested.Sandbox.psm1 (mirrored in TestImports.ps1) before Package.Install.ps1.
#>


function Format-PackageProcessArgument {
    [CmdletBinding()]
    param(
        [AllowNull()]
        [string]$Value
    )

    if ($null -eq $Value) {
        return ''
    }

    $text = [string]$Value
    if ($text.Length -ge 2 -and $text.StartsWith('"') -and $text.EndsWith('"')) {
        return $text
    }
    if ($text.IndexOfAny([char[]]@(' ', "`t", '"')) -lt 0) {
        return $text
    }

    return '"' + ($text -replace '"', '\"') + '"'
}

function Invoke-PackageInstallerCommand {
<#
.SYNOPSIS
Runs a prepared installer process command.
 
.DESCRIPTION
Owns the common process-launch, elevation, timeout, and exit-code mechanics for
installer adapters. Adapter-specific command path and arguments are resolved by
the caller.
#>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$PackageResult,

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

        [AllowEmptyCollection()]
        [object[]]$CommandArguments,

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

        [Parameter(Mandatory = $true)]
        [int]$TimeoutSec,

        [Parameter(Mandatory = $true)]
        [int[]]$SuccessExitCodes,

        [AllowEmptyCollection()]
        [int[]]$RestartExitCodes,

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

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

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

        [AllowNull()]
        [string]$LogPath
    )

    if ([string]::IsNullOrWhiteSpace($CommandPath)) {
        throw 'Package installer command path is empty.'
    }
    if (-not (Test-Path -LiteralPath $CommandPath -PathType Leaf)) {
        throw "Package installer command '$CommandPath' does not exist."
    }
    if ([string]::IsNullOrWhiteSpace($WorkingDirectory)) {
        throw 'Package installer working directory is empty.'
    }
    $null = New-Item -ItemType Directory -Path $WorkingDirectory -Force

    $elevationPlan = Get-PackageInstallerElevationPlan -PackageResult $PackageResult
    Write-PackageExecutionMessage -Message ("[STATE] Installer execution: targetKind='{0}', installerKind='{1}', uiMode='{2}', elevation='{3}', processIsElevated='{4}', willElevate='{5}'." -f $TargetKind, $InstallerKind, $UiMode, $elevationPlan.Mode, $elevationPlan.ProcessIsElevated, $elevationPlan.ShouldElevate)

    $startProcessParameters = @{
        FilePath         = $CommandPath
        ArgumentList     = @($CommandArguments)
        WorkingDirectory = $WorkingDirectory
        PassThru         = $true
    }
    if ($elevationPlan.ShouldElevate) {
        $startProcessParameters['Verb'] = 'RunAs'
    }

    $process = Start-Process @startProcessParameters
    if (-not $process.WaitForExit($TimeoutSec * 1000)) {
        try {
            Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
        }
        catch {
        }

        throw "Package installer command exceeded the timeout of $TimeoutSec seconds."
    }

    $process.Refresh()
    $exitCode = [int]$process.ExitCode
    $acceptedExitCodes = @($SuccessExitCodes) + @($RestartExitCodes)
    if ($exitCode -notin $acceptedExitCodes) {
        throw "Package installer command failed with exit code $exitCode."
    }

    return [pscustomobject]@{
        ExitCode         = $exitCode
        RestartRequired  = ($exitCode -in $RestartExitCodes)
        LogPath          = $LogPath
        CommandPath      = $CommandPath
        CommandArguments = @($CommandArguments)
        TargetKind       = $TargetKind
        InstallerKind    = $InstallerKind
        UiMode           = $UiMode
        Elevation        = $elevationPlan
    }
}

function Invoke-PackageInstallerProcess {
<#
.SYNOPSIS
Runs an installer-style package command and waits for completion.
 
.DESCRIPTION
Starts the configured installer command, applies timeout and exit-code rules,
and returns the install log path and restart flag when configured.
 
.PARAMETER PackageResult
The Package result object that owns the install.
 
.EXAMPLE
Invoke-PackageInstallerProcess -PackageResult $result
#>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$PackageResult
    )

    $install = $PackageResult.Package.install
    $commandPath = if ($install.PSObject.Properties['commandPath'] -and -not [string]::IsNullOrWhiteSpace([string]$install.commandPath)) {
        Resolve-PackageTemplateText -Text ([string]$install.commandPath) -PackageConfig $PackageResult.PackageConfig -Package $PackageResult.Package
    }
    else {
        $PackageResult.PackageFilePath
    }

    $timestamp = (Get-Date -Format 'yyyyMMdd-HHmmss')
    $logPath = $null
    if ($install.PSObject.Properties['logRelativePath'] -and -not [string]::IsNullOrWhiteSpace([string]$install.logRelativePath)) {
        $logRelativePath = Resolve-PackageTemplateText -Text ([string]$install.logRelativePath) -PackageConfig $PackageResult.PackageConfig -Package $PackageResult.Package -ExtraTokens @{ timestamp = $timestamp }
        $packageRoot = Get-PackageRootFromInventoryPath -PackageInventoryFilePath ([string]$PackageResult.PackageConfig.PackageInventoryFilePath)
        $logRoot = [System.IO.Path]::GetFullPath((Join-Path $packageRoot 'Logs'))
        $logPath = [System.IO.Path]::GetFullPath((Join-Path $logRoot ($logRelativePath -replace '/', '\')))
        $null = New-Item -ItemType Directory -Path (Split-Path -Parent $logPath) -Force
    }

    $commandArguments = @()
    foreach ($argument in @($install.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
                logPath           = $logPath
                timestamp         = $timestamp
            }
        $commandArguments += (Format-PackageProcessArgument -Value $resolvedArgument)
    }

    $timeoutSec = if ($install.PSObject.Properties['timeoutSec']) { [int]$install.timeoutSec } else { 300 }
    $successExitCodes = if ($install.PSObject.Properties['successExitCodes']) { @($install.successExitCodes | ForEach-Object { [int]$_ }) } else { @(0) }
    $restartExitCodes = if ($install.PSObject.Properties['restartExitCodes']) { @($install.restartExitCodes | ForEach-Object { [int]$_ }) } else { @() }
    $targetKind = Get-PackageInstallTargetKind -Package $PackageResult.Package
    $workingDirectory = if (-not [string]::IsNullOrWhiteSpace([string]$PackageResult.InstallDirectory)) {
        $null = New-Item -ItemType Directory -Path $PackageResult.InstallDirectory -Force
        $PackageResult.InstallDirectory
    }
    else {
        $PackageResult.PackageInstallStageDirectory
    }
    if (-not [string]::IsNullOrWhiteSpace([string]$workingDirectory)) {
        $null = New-Item -ItemType Directory -Path $workingDirectory -Force
    }

    $installerKind = if ($install.PSObject.Properties['installerKind'] -and -not [string]::IsNullOrWhiteSpace([string]$install.installerKind)) { [string]$install.installerKind } else { '<unspecified>' }
    $uiMode = if ($install.PSObject.Properties['uiMode'] -and -not [string]::IsNullOrWhiteSpace([string]$install.uiMode)) { [string]$install.uiMode } else { '<unspecified>' }

    return (Invoke-PackageInstallerCommand -PackageResult $PackageResult -CommandPath $commandPath -CommandArguments @($commandArguments) -WorkingDirectory $workingDirectory -TimeoutSec $timeoutSec -SuccessExitCodes @($successExitCodes) -RestartExitCodes @($restartExitCodes) -TargetKind $targetKind -InstallerKind $installerKind -UiMode $uiMode -LogPath $logPath)
}

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

    if ([string]::IsNullOrWhiteSpace($PackageResult.PackageFilePath) -or -not (Test-Path -LiteralPath $PackageResult.PackageFilePath -PathType Leaf)) {
        throw "Package installer for '$($PackageResult.PackageId)' requires a saved package file."
    }
    if ([string]::IsNullOrWhiteSpace($PackageResult.PackageInstallStageDirectory)) {
        throw "Package installer for '$($PackageResult.PackageId)' requires a package install stage directory."
    }

    $stageDirectory = [System.IO.Path]::GetFullPath([string]$PackageResult.PackageInstallStageDirectory)
    $null = New-Item -ItemType Directory -Path $stageDirectory -Force
    $installerFileName = Split-Path -Leaf $PackageResult.PackageFilePath
    $stagedInstallerPath = Join-Path $stageDirectory $installerFileName
    return (Copy-FileToPath -SourcePath $PackageResult.PackageFilePath -TargetPath $stagedInstallerPath -Overwrite)
}

function Invoke-PackageNsisInstallerProcess {
<#
.SYNOPSIS
Runs an NSIS installer package through the isolated package install stage.
#>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$PackageResult
    )

    $install = $PackageResult.Package.install
    if ([string]::IsNullOrWhiteSpace([string]$PackageResult.InstallDirectory)) {
        throw "Package nsisInstaller for '$($PackageResult.PackageId)' requires an install directory."
    }

    $commandPath = Copy-PackageInstallerToInstallStage -PackageResult $PackageResult
    $commandArguments = @()
    foreach ($argument in @($install.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
            }
        $commandArguments += (Format-PackageProcessArgument -Value $resolvedArgument)
    }

    if ($install.PSObject.Properties['targetDirectoryArgument'] -and $null -ne $install.targetDirectoryArgument) {
        $targetDirectoryArgument = $install.targetDirectoryArgument
        $targetDirectoryArgumentEnabled = (-not $targetDirectoryArgument.PSObject.Properties['enabled']) -or [bool]$targetDirectoryArgument.enabled
        if ($targetDirectoryArgumentEnabled) {
            $prefix = if ($targetDirectoryArgument.PSObject.Properties['prefix'] -and -not [string]::IsNullOrWhiteSpace([string]$targetDirectoryArgument.prefix)) {
                [string]$targetDirectoryArgument.prefix
            }
            else {
                '/D='
            }
            $commandArguments += ($prefix + [string]$PackageResult.InstallDirectory)
        }
    }

    $timeoutSec = if ($install.PSObject.Properties['timeoutSec']) { [int]$install.timeoutSec } else { 300 }
    $successExitCodes = if ($install.PSObject.Properties['successExitCodes']) { @($install.successExitCodes | ForEach-Object { [int]$_ }) } else { @(0) }
    $restartExitCodes = if ($install.PSObject.Properties['restartExitCodes']) { @($install.restartExitCodes | ForEach-Object { [int]$_ }) } else { @() }
    $targetKind = Get-PackageInstallTargetKind -Package $PackageResult.Package
    $installerKind = if ($install.PSObject.Properties['installerKind'] -and -not [string]::IsNullOrWhiteSpace([string]$install.installerKind)) { [string]$install.installerKind } else { 'nsis' }
    $uiMode = if ($install.PSObject.Properties['uiMode'] -and -not [string]::IsNullOrWhiteSpace([string]$install.uiMode)) { [string]$install.uiMode } else { 'silent' }

    return (Invoke-PackageInstallerCommand -PackageResult $PackageResult -CommandPath $commandPath -CommandArguments @($commandArguments) -WorkingDirectory $PackageResult.PackageInstallStageDirectory -TimeoutSec $timeoutSec -SuccessExitCodes @($successExitCodes) -RestartExitCodes @($restartExitCodes) -TargetKind $targetKind -InstallerKind $installerKind -UiMode $uiMode -LogPath $null)
}