Private/Invoke-LocalPackageInstall.ps1

#Requires -Version 5.1
<#!
.SYNOPSIS
    Downloads/stages installer content and executes local install command.
 
.DESCRIPTION
    Uses the same package definition source model as Intune packaging but runs
    Program.InstallCommand locally instead of creating/importing .intunewin.
 
.PARAMETER DefinitionPath
    Full path to App.json.
 
.PARAMETER DefinitionObject
    Parsed App.json object.
 
.PARAMETER WorkingPath
    Root working directory for staging content.
 
.PARAMETER LatestVersionResult
    Result object from Get-InstallPackageLatestVersion.
 
.PARAMETER SyncHash
    Shared UI synchronized hashtable for Write-UILog.
 
.OUTPUTS
    PSCustomObject with:
        Succeeded : bool
        ExitCode : int
        WorkingDirectory : string
        DownloadedVersion : string
        InstallCommand : string
        Error : string
!#>

function Invoke-LocalPackageInstall {
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory)]
        [string]$DefinitionPath,

        [Parameter(Mandatory)]
        [PSCustomObject]$DefinitionObject,

        [Parameter(Mandatory)]
        [string]$WorkingPath,

        [Parameter(Mandatory)]
        [PSCustomObject]$LatestVersionResult,

        [Parameter(Mandatory)]
        [System.Collections.Hashtable]$SyncHash
    )

    $fail = {
        param([string]$Message)
        return [PSCustomObject]@{
            Succeeded         = $false
            ExitCode          = -1
            WorkingDirectory  = ''
            DownloadedVersion = ''
            InstallCommand    = ''
            Error             = $Message
        }
    }

    if ([string]::IsNullOrWhiteSpace($DefinitionPath) -or -not (Test-Path -LiteralPath $DefinitionPath -PathType Leaf)) {
        return (& $fail "Definition path not found: $DefinitionPath")
    }

    if ($null -eq $DefinitionObject) {
        return (& $fail 'Definition object is null.')
    }

    if ([string]::IsNullOrWhiteSpace($WorkingPath)) {
        return (& $fail 'WorkingPath is empty.')
    }

    if ($null -eq $LatestVersionResult -or -not [bool]$LatestVersionResult.Succeeded) {
        $err = if ($null -eq $LatestVersionResult) { 'Unknown latest version error.' } else { [string]$LatestVersionResult.Error }
        return (& $fail "Latest version resolution failed: $err")
    }

    $appFolderName = [System.IO.Path]::GetFileName([System.IO.Path]::GetDirectoryName($DefinitionPath))
    if ([string]::IsNullOrWhiteSpace($appFolderName)) {
        $appFolderName = [string]$DefinitionObject.Application.Name
    }
    $appFolderName = [System.Text.RegularExpressions.Regex]::Replace($appFolderName, '[^\w\-\.]', '_').Trim('_')
    if ([string]::IsNullOrWhiteSpace($appFolderName)) {
        $appFolderName = 'InstallPackage'
    }

    # Working directory for this app: <WorkingPath>\<AppFolderName>
    $appWorkingDir = Join-Path -Path $WorkingPath -ChildPath $appFolderName
    if (-not (Test-Path -LiteralPath $appWorkingDir -PathType Container)) {
        try {
            $null = New-Item -Path $appWorkingDir -ItemType Directory -Force -ErrorAction Stop
        }
        catch {
            return (& $fail "Failed to create working directory '$appWorkingDir': $($_.Exception.Message)")
        }
    }

    $definitionDir = [System.IO.Path]::GetDirectoryName($DefinitionPath)
    $definitionParentDir = [System.IO.Path]::GetDirectoryName($definitionDir)
    $definitionSourceDir = Join-Path -Path $definitionDir -ChildPath 'Source'
    $sharedInstallPs1Source = if ([string]::IsNullOrWhiteSpace($definitionParentDir)) {
        ''
    }
    else {
        Join-Path -Path $definitionParentDir -ChildPath 'Install.ps1'
    }

    # Copy Install.ps1 to the working directory. Prefer the package root,
    # then the Source folder, then the shared intune template used by
    # evergreen-packages package definitions.
    $installPs1Source = @(
        (Join-Path -Path $definitionDir -ChildPath 'Install.ps1')
        (Join-Path -Path $definitionSourceDir -ChildPath 'Install.ps1')
        $sharedInstallPs1Source
    ) | Where-Object {
        -not [string]::IsNullOrWhiteSpace([string]$_) -and (Test-Path -LiteralPath $_ -PathType Leaf)
    } | Select-Object -First 1

    $installPs1Target = Join-Path -Path $appWorkingDir -ChildPath 'Install.ps1'
    if (-not [string]::IsNullOrWhiteSpace([string]$installPs1Source)) {
        try {
            Write-UILog -SyncHash $SyncHash -Message "Install: copying Install.ps1 from '$installPs1Source' to '$installPs1Target'." -Level Info
            Copy-Item -LiteralPath $installPs1Source -Destination $installPs1Target -Force -ErrorAction Stop
        }
        catch {
            return (& $fail "Failed to copy Install.ps1: $($_.Exception.Message)")
        }
    }

    # Copy support files from <Package>\Source\ into the working directory
    if (Test-Path -LiteralPath $definitionSourceDir -PathType Container) {
        try {
            Write-UILog -SyncHash $SyncHash -Message "Install: copying support files from '$definitionSourceDir'." -Level Info
            Copy-Item -Path "$definitionSourceDir\*" -Destination $appWorkingDir -Recurse -Force -ErrorAction Stop
        }
        catch {
            return (& $fail "Failed to copy source content: $($_.Exception.Message)")
        }
    }

    # Download the latest installer to the working directory
    if (-not [string]::IsNullOrWhiteSpace([string]$LatestVersionResult.URI)) {
        try {
            Write-UILog -SyncHash $SyncHash -Message "Install: downloading latest installer to '$appWorkingDir'." -Level Info
            [void]@($LatestVersionResult.ResolvedArtifact | Save-EvergreenApp -LiteralPath $appWorkingDir -ErrorAction Stop)
        }
        catch {
            return (& $fail "Failed to download installer: $($_.Exception.Message)")
        }
    }

    # Determine install command: prefer Install.ps1, fall back to Program.InstallCommand
    $useInstallPs1 = Test-Path -LiteralPath $installPs1Target -PathType Leaf
    $fallbackCommand = [string]$DefinitionObject.Program.InstallCommand

    if (-not $useInstallPs1 -and [string]::IsNullOrWhiteSpace($fallbackCommand)) {
        return (& $fail 'No Install.ps1 found in the package directory, Source directory, or shared package template location, and Program.InstallCommand is missing from App.json.')
    }

    $process = $null
    try {
        if ($useInstallPs1) {
            $escapedWorkingDir = $appWorkingDir.Replace("'", "''")
            $installScriptCommand = "Set-Location -LiteralPath '$escapedWorkingDir'; & '.\\Install.ps1'"
            Write-UILog -SyncHash $SyncHash -Message "Install: running Install.ps1 from '$appWorkingDir' with current directory '$appWorkingDir'." -Level Cmd
            $process = Start-Process -FilePath 'powershell.exe' `
                -ArgumentList '-NoProfile', '-ExecutionPolicy', 'Bypass', '-NonInteractive', '-Command', $installScriptCommand `
                -WorkingDirectory $appWorkingDir -Wait -PassThru -ErrorAction Stop
        }
        else {
            Write-UILog -SyncHash $SyncHash -Message "Install: executing command '$fallbackCommand'." -Level Cmd
            $process = Start-Process -FilePath 'cmd.exe' -ArgumentList '/c', $fallbackCommand `
                -WorkingDirectory $appWorkingDir -Wait -PassThru -ErrorAction Stop
        }
    }
    catch {
        return (& $fail "Failed to execute install: $($_.Exception.Message)")
    }

    $exitCode = if ($null -eq $process) { -1 } else { [int]$process.ExitCode }
    $succeeded = $exitCode -eq 0 -or $exitCode -eq 3010

    if (-not $succeeded) {
        return (& $fail "Installer exited with code $exitCode.")
    }

    $resolvedCommand = if ($useInstallPs1) { "powershell.exe -Command Set-Location -LiteralPath '$appWorkingDir'; & '.\\Install.ps1'" } else { $fallbackCommand }

    return [PSCustomObject]@{
        Succeeded         = $true
        ExitCode          = $exitCode
        WorkingDirectory  = $appWorkingDir
        DownloadedVersion = [string]$LatestVersionResult.Version
        InstallCommand    = $resolvedCommand
        Error             = ''
    }
}