PackageModel/Support/Package/Eigenverft.Manifested.Sandbox.PackageModel.Install.ps1
|
<#
Eigenverft.Manifested.Sandbox.PackageModel.Install #> function Resolve-PackageModelExistingInstallRoot { <# .SYNOPSIS Resolves an install directory from a discovered existing-install candidate path. .DESCRIPTION Uses the existing-install root rules to turn a discovered file path such as `code.cmd` into the install directory that owns that file. .PARAMETER ExistingInstallDiscovery The existing-install discovery definition object. .PARAMETER CandidatePath The discovered file or directory path. .EXAMPLE Resolve-PackageModelExistingInstallRoot -ExistingInstallDiscovery $package.existingInstallDiscovery -CandidatePath $candidatePath #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$ExistingInstallDiscovery, [Parameter(Mandatory = $true)] [string]$CandidatePath ) if (Test-Path -LiteralPath $CandidatePath -PathType Container) { return (Resolve-Path -LiteralPath $CandidatePath -ErrorAction Stop).Path } $leafName = Split-Path -Leaf $CandidatePath foreach ($rule in @($ExistingInstallDiscovery.installRootRules)) { if (-not $rule.PSObject.Properties['match'] -or $null -eq $rule.match) { continue } $matchKind = if ($rule.match.PSObject.Properties['kind']) { [string]$rule.match.kind } else { $null } $matchValue = if ($rule.match.PSObject.Properties['value']) { [string]$rule.match.value } else { $null } if ([string]::Equals($matchKind, 'fileName', [System.StringComparison]::OrdinalIgnoreCase) -and [string]::Equals($matchValue, $leafName, [System.StringComparison]::OrdinalIgnoreCase)) { $candidateDirectory = Split-Path -Parent $CandidatePath $installRootRelativePath = if ($rule.PSObject.Properties['installRootRelativePath']) { [string]$rule.installRootRelativePath } else { '.' } return [System.IO.Path]::GetFullPath((Join-Path $candidateDirectory $installRootRelativePath)) } } return (Split-Path -Parent $CandidatePath) } function Find-PackageModelExistingPackage { <# .SYNOPSIS Finds an existing package install that may be reused or adopted. .DESCRIPTION Searches command, path, and directory candidates from the release existingInstallDiscovery block and attaches the first matching install directory to the PackageModel result. .PARAMETER PackageModelResult The PackageModel result object to enrich. .EXAMPLE Find-PackageModelExistingPackage -PackageModelResult $result #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageModelResult ) if (-not [string]::IsNullOrWhiteSpace([string]$PackageModelResult.InstallDirectory) -and (Test-Path -LiteralPath $PackageModelResult.InstallDirectory -PathType Container)) { $resolvedPackageModelOwnedInstallDirectory = [System.IO.Path]::GetFullPath([string]$PackageModelResult.InstallDirectory) $PackageModelResult.ExistingPackage = [pscustomobject]@{ SearchKind = 'packageModelTargetInstallPath' CandidatePath = $resolvedPackageModelOwnedInstallDirectory InstallDirectory = $resolvedPackageModelOwnedInstallDirectory Decision = 'Pending' Validation = $null Classification = $null OwnershipRecord = $null } Write-PackageModelExecutionMessage -Message ("[DISCOVERY] Found PackageModel target install directory '{0}'." -f $resolvedPackageModelOwnedInstallDirectory) return $PackageModelResult } $package = $PackageModelResult.Package if (-not $package -or -not $package.PSObject.Properties['existingInstallDiscovery'] -or $null -eq $package.existingInstallDiscovery) { return $PackageModelResult } $existingInstallDiscovery = $package.existingInstallDiscovery if ($existingInstallDiscovery.PSObject.Properties['enableDetection'] -and (-not [bool]$existingInstallDiscovery.enableDetection)) { return $PackageModelResult } foreach ($searchLocation in @($existingInstallDiscovery.searchLocations)) { $candidatePath = $null switch -Exact ([string]$searchLocation.kind) { 'command' { if (-not $searchLocation.PSObject.Properties['name'] -or [string]::IsNullOrWhiteSpace([string]$searchLocation.name)) { throw "PackageModel existingInstallDiscovery search for release '$($package.id)' is missing command name." } $candidatePath = Get-ResolvedApplicationPath -CommandName ([string]$searchLocation.name) } 'path' { if (-not $searchLocation.PSObject.Properties['path'] -or [string]::IsNullOrWhiteSpace([string]$searchLocation.path)) { throw "PackageModel existingInstallDiscovery search for release '$($package.id)' is missing path." } $resolvedPath = Resolve-PackageModelPathValue -PathValue ([string]$searchLocation.path) if (Test-Path -LiteralPath $resolvedPath) { $candidatePath = $resolvedPath } } 'directory' { if (-not $searchLocation.PSObject.Properties['path'] -or [string]::IsNullOrWhiteSpace([string]$searchLocation.path)) { throw "PackageModel existingInstallDiscovery search for release '$($package.id)' is missing directory path." } $resolvedPath = Resolve-PackageModelPathValue -PathValue ([string]$searchLocation.path) if (Test-Path -LiteralPath $resolvedPath -PathType Container) { $candidatePath = $resolvedPath } } default { throw "Unsupported PackageModel existingInstallDiscovery search kind '$($searchLocation.kind)'." } } if ([string]::IsNullOrWhiteSpace($candidatePath)) { continue } $installDirectory = Resolve-PackageModelExistingInstallRoot -ExistingInstallDiscovery $existingInstallDiscovery -CandidatePath $candidatePath if (-not (Test-Path -LiteralPath $installDirectory -PathType Container)) { continue } $PackageModelResult.ExistingPackage = [pscustomobject]@{ SearchKind = $searchLocation.kind CandidatePath = $candidatePath InstallDirectory = $installDirectory Decision = 'Pending' Validation = $null Classification = $null OwnershipRecord = $null } Write-PackageModelExecutionMessage -Message ("[DISCOVERY] Found existing package candidate '{0}' via '{1}'." -f $candidatePath, $searchLocation.kind) return $PackageModelResult } return $PackageModelResult } function Resolve-PackageModelExistingPackageDecision { <# .SYNOPSIS Evaluates how PackageModel should react to a discovered existing install. .DESCRIPTION Validates the discovered install, combines the result with ownership classification and release-specific policy switches, and records whether the current run should reuse, adopt, ignore, or replace the install. .PARAMETER PackageModelResult The PackageModel result object to enrich. .EXAMPLE Resolve-PackageModelExistingPackageDecision -PackageModelResult $result #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageModelResult ) if (-not $PackageModelResult.ExistingPackage) { return $PackageModelResult } $package = $PackageModelResult.Package $existingInstallPolicy = if ($package.PSObject.Properties['existingInstallPolicy']) { $package.existingInstallPolicy } else { [pscustomobject]@{} } $originalInstallDirectory = $PackageModelResult.InstallDirectory $PackageModelResult.InstallDirectory = $PackageModelResult.ExistingPackage.InstallDirectory $PackageModelResult = Test-PackageModelInstalledPackage -PackageModelResult $PackageModelResult $PackageModelResult.ExistingPackage.Validation = $PackageModelResult.Validation if (-not $PackageModelResult.Validation.Accepted) { $PackageModelResult.ExistingPackage.Decision = 'ExistingInstallValidationFailed' $PackageModelResult.InstallDirectory = $originalInstallDirectory $PackageModelResult.Validation = $null return $PackageModelResult } $ownershipRecord = if ($PackageModelResult.Ownership -and $PackageModelResult.Ownership.OwnershipRecord) { $PackageModelResult.Ownership.OwnershipRecord } else { $null } $classification = if ($PackageModelResult.Ownership -and $PackageModelResult.Ownership.Classification) { [string]$PackageModelResult.Ownership.Classification } else { 'ExternalInstall' } $allowAdoptExternal = $false if ($existingInstallPolicy.PSObject.Properties['allowAdoptExternal']) { $allowAdoptExternal = [bool]$existingInstallPolicy.allowAdoptExternal } $upgradeAdoptedInstall = $false if ($existingInstallPolicy.PSObject.Properties['upgradeAdoptedInstall']) { $upgradeAdoptedInstall = [bool]$existingInstallPolicy.upgradeAdoptedInstall } $requirePackageModelOwnership = $false if ($existingInstallPolicy.PSObject.Properties['requirePackageModelOwnership']) { $requirePackageModelOwnership = [bool]$existingInstallPolicy.requirePackageModelOwnership } $sameRelease = $false if ($ownershipRecord) { $sameRelease = [string]::Equals([string]$ownershipRecord.currentReleaseId, [string]$PackageModelResult.PackageId, [System.StringComparison]::OrdinalIgnoreCase) -and [string]::Equals([string]$ownershipRecord.currentVersion, [string]$PackageModelResult.PackageVersion, [System.StringComparison]::OrdinalIgnoreCase) } if ([string]::Equals($classification, 'PackageModelOwned', [System.StringComparison]::OrdinalIgnoreCase) -and -not $ownershipRecord) { $PackageModelResult.ExistingPackage.Decision = 'ReusePackageModelOwned' $PackageModelResult.InstallOrigin = 'PackageModelReused' Write-PackageModelExecutionMessage -Message ("[DECISION] Reusing PackageModel-owned target install '{0}'." -f $PackageModelResult.ExistingPackage.InstallDirectory) Write-PackageModelExecutionMessage -Message ("[STATE] Existing install decision resolved to '{0}' with installOrigin='{1}'." -f $PackageModelResult.ExistingPackage.Decision, $PackageModelResult.InstallOrigin) return $PackageModelResult } if ([string]::Equals($classification, 'PackageModelOwned', [System.StringComparison]::OrdinalIgnoreCase) -and $ownershipRecord) { if ([string]::Equals([string]$ownershipRecord.ownershipKind, 'AdoptedExternal', [System.StringComparison]::OrdinalIgnoreCase)) { if ($sameRelease -or (-not $upgradeAdoptedInstall)) { $PackageModelResult.ExistingPackage.Decision = 'AdoptExternal' $PackageModelResult.InstallOrigin = 'AdoptedExternal' Write-PackageModelExecutionMessage -Message ("[DECISION] Reusing adopted external install '{0}'." -f $PackageModelResult.ExistingPackage.InstallDirectory) Write-PackageModelExecutionMessage -Message ("[STATE] Existing install decision resolved to '{0}' with installOrigin='{1}'." -f $PackageModelResult.ExistingPackage.Decision, $PackageModelResult.InstallOrigin) return $PackageModelResult } $PackageModelResult.ExistingPackage.Decision = 'UpgradeAdoptedInstall' $PackageModelResult.InstallDirectory = $originalInstallDirectory $PackageModelResult.Validation = $null Write-PackageModelExecutionMessage -Level 'WRN' -Message ("[DECISION] Replacing adopted install at '{0}' with a PackageModel-owned install." -f $PackageModelResult.ExistingPackage.InstallDirectory) Write-PackageModelExecutionMessage -Message ("[STATE] Existing install decision resolved to '{0}'." -f $PackageModelResult.ExistingPackage.Decision) return $PackageModelResult } if ($sameRelease) { $PackageModelResult.ExistingPackage.Decision = 'ReusePackageModelOwned' $PackageModelResult.InstallOrigin = 'PackageModelReused' Write-PackageModelExecutionMessage -Message ("[DECISION] Reusing PackageModel-owned install '{0}'." -f $PackageModelResult.ExistingPackage.InstallDirectory) Write-PackageModelExecutionMessage -Message ("[STATE] Existing install decision resolved to '{0}' with installOrigin='{1}'." -f $PackageModelResult.ExistingPackage.Decision, $PackageModelResult.InstallOrigin) return $PackageModelResult } $PackageModelResult.ExistingPackage.Decision = 'ReplacePackageModelOwnedInstall' $PackageModelResult.InstallDirectory = $originalInstallDirectory $PackageModelResult.Validation = $null Write-PackageModelExecutionMessage -Level 'WRN' -Message ("[DECISION] Replacing outdated PackageModel-owned install at '{0}'." -f $PackageModelResult.ExistingPackage.InstallDirectory) Write-PackageModelExecutionMessage -Message ("[STATE] Existing install decision resolved to '{0}'." -f $PackageModelResult.ExistingPackage.Decision) return $PackageModelResult } if ($requirePackageModelOwnership) { $PackageModelResult.ExistingPackage.Decision = 'ExternalIgnored' $PackageModelResult.InstallDirectory = $originalInstallDirectory $PackageModelResult.Validation = $null Write-PackageModelExecutionMessage -Level 'WRN' -Message ("[DECISION] Ignoring external install '{0}' because PackageModel ownership is required." -f $PackageModelResult.ExistingPackage.InstallDirectory) Write-PackageModelExecutionMessage -Message ("[STATE] Existing install decision resolved to '{0}'." -f $PackageModelResult.ExistingPackage.Decision) return $PackageModelResult } if ($allowAdoptExternal) { $PackageModelResult.ExistingPackage.Decision = 'AdoptExternal' $PackageModelResult.InstallOrigin = 'AdoptedExternal' Write-PackageModelExecutionMessage -Message ("[DECISION] Adopting external install '{0}'." -f $PackageModelResult.ExistingPackage.InstallDirectory) Write-PackageModelExecutionMessage -Message ("[STATE] Existing install decision resolved to '{0}' with installOrigin='{1}'." -f $PackageModelResult.ExistingPackage.Decision, $PackageModelResult.InstallOrigin) return $PackageModelResult } $PackageModelResult.ExistingPackage.Decision = 'ExternalIgnored' $PackageModelResult.InstallDirectory = $originalInstallDirectory $PackageModelResult.Validation = $null Write-PackageModelExecutionMessage -Level 'WRN' -Message ("[DECISION] Ignoring external install '{0}'." -f $PackageModelResult.ExistingPackage.InstallDirectory) Write-PackageModelExecutionMessage -Message ("[STATE] Existing install decision resolved to '{0}'." -f $PackageModelResult.ExistingPackage.Decision) return $PackageModelResult } function Get-PackageModelOwnedInstallStatus { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageModelResult ) if ($PackageModelResult.ExistingPackage) { switch -Exact ([string]$PackageModelResult.ExistingPackage.Decision) { 'ExistingInstallValidationFailed' { if ([string]::Equals([string]$PackageModelResult.ExistingPackage.SearchKind, 'packageModelTargetInstallPath', [System.StringComparison]::OrdinalIgnoreCase)) { return 'RepairedPackageModelOwnedInstall' } } 'ReplacePackageModelOwnedInstall' { return 'ReplacedPackageModelOwnedInstall' } 'UpgradeAdoptedInstall' { return 'ReplacedAdoptedInstall' } } } return 'Installed' } function Get-PackageModelInstallTargetKind { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$Package ) if ($Package.install -and $Package.install.PSObject.Properties['targetKind'] -and -not [string]::IsNullOrWhiteSpace([string]$Package.install.targetKind)) { return [string]$Package.install.targetKind } return 'directory' } function Get-PackageModelInstallerElevationPlan { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageModelResult ) $install = $PackageModelResult.Package.install $mode = if ($install.PSObject.Properties['elevation'] -and -not [string]::IsNullOrWhiteSpace([string]$install.elevation)) { ([string]$install.elevation).ToLowerInvariant() } else { 'none' } $processIsElevated = Test-ProcessElevation $shouldElevate = ($mode -in @('required', 'auto')) -and (-not $processIsElevated) return [pscustomobject]@{ Mode = $mode ProcessIsElevated = $processIsElevated ShouldElevate = $shouldElevate } } function Resolve-PackageModelPreInstallSatisfaction { <# .SYNOPSIS Checks whether a machine-prerequisite package is already satisfied. .DESCRIPTION Runs validation before acquisition/install for prerequisite-style packages that do not own an install directory. When validation succeeds, later acquisition and installer steps are skipped. .PARAMETER PackageModelResult The PackageModel result object to enrich. .EXAMPLE Resolve-PackageModelPreInstallSatisfaction -PackageModelResult $result #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageModelResult ) $package = $PackageModelResult.Package if (-not $package -or -not $package.install) { return $PackageModelResult } $installKind = if ($package.install.PSObject.Properties['kind']) { [string]$package.install.kind } else { $null } $targetKind = Get-PackageModelInstallTargetKind -Package $package if (-not [string]::Equals($installKind, 'runInstaller', [System.StringComparison]::OrdinalIgnoreCase) -or -not [string]::Equals($targetKind, 'machinePrerequisite', [System.StringComparison]::OrdinalIgnoreCase)) { return $PackageModelResult } $PackageModelResult = Test-PackageModelInstalledPackage -PackageModelResult $PackageModelResult if ($PackageModelResult.Validation -and $PackageModelResult.Validation.Accepted) { $PackageModelResult.InstallOrigin = 'AlreadySatisfied' $PackageModelResult.Install = [pscustomobject]@{ Status = 'AlreadySatisfied' InstallKind = 'runInstaller' TargetKind = 'machinePrerequisite' InstallDirectory = $null ReusedExisting = $true } Write-PackageModelExecutionMessage -Message "[DECISION] Machine prerequisite is already satisfied; skipping acquisition and installer execution." } else { $failedCount = if ($PackageModelResult.Validation) { @( @($PackageModelResult.Validation.Files) + @($PackageModelResult.Validation.Directories) + @($PackageModelResult.Validation.Commands) + @($PackageModelResult.Validation.MetadataFiles) + @($PackageModelResult.Validation.Signatures) + @($PackageModelResult.Validation.FileDetails) + @($PackageModelResult.Validation.Registry) | Where-Object { $_.Status -ne 'Ready' } ).Count } else { 0 } Write-PackageModelExecutionMessage -Message ("[STATE] Machine prerequisite is not satisfied yet; failedChecks={0}." -f $failedCount) $PackageModelResult.Validation = $null } return $PackageModelResult } function Install-PackageModelArchive { <# .SYNOPSIS Installs a package by expanding an archive into the install directory. .DESCRIPTION Expands the saved package file into a stage directory, promotes the expanded root into the final install directory, and creates any extra directories that the install block requests. .PARAMETER PackageModelResult The PackageModel result object to install. .EXAMPLE Install-PackageModelArchive -PackageModelResult $result #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageModelResult ) if ([string]::IsNullOrWhiteSpace($PackageModelResult.PackageFilePath) -or -not (Test-Path -LiteralPath $PackageModelResult.PackageFilePath)) { throw "PackageModel archive install for '$($PackageModelResult.PackageId)' requires a saved package file." } $install = $PackageModelResult.Package.install $stageInfo = $null try { $stageInfo = Expand-ArchiveToStage -ArchivePath $PackageModelResult.PackageFilePath -Prefix 'packagemodel' $expandedRoot = $stageInfo.ExpandedRoot if ($install.PSObject.Properties['expandedRoot'] -and -not [string]::IsNullOrWhiteSpace([string]$install.expandedRoot) -and [string]$install.expandedRoot -ne 'auto') { $expandedRoot = Join-Path $stageInfo.StagePath (([string]$install.expandedRoot) -replace '/', '\') } if (-not (Test-Path -LiteralPath $expandedRoot -PathType Container)) { throw "Expanded package root '$expandedRoot' was not found for '$($PackageModelResult.PackageId)'." } $null = New-Item -ItemType Directory -Path (Split-Path -Parent $PackageModelResult.InstallDirectory) -Force if (Test-Path -LiteralPath $PackageModelResult.InstallDirectory) { Remove-Item -LiteralPath $PackageModelResult.InstallDirectory -Recurse -Force } New-Item -ItemType Directory -Path $PackageModelResult.InstallDirectory -Force | Out-Null Get-ChildItem -LiteralPath $expandedRoot -Force | ForEach-Object { Move-Item -LiteralPath $_.FullName -Destination $PackageModelResult.InstallDirectory -Force } foreach ($relativePath in @($install.createDirectories)) { $targetDirectory = Join-Path $PackageModelResult.InstallDirectory (([string]$relativePath) -replace '/', '\') New-Item -ItemType Directory -Path $targetDirectory -Force | Out-Null } } finally { if ($stageInfo) { Remove-PathIfExists -Path $stageInfo.StagePath | Out-Null } } return [pscustomobject]@{ Status = Get-PackageModelOwnedInstallStatus -PackageModelResult $PackageModelResult InstallKind = 'expandArchive' InstallDirectory = $PackageModelResult.InstallDirectory ReusedExisting = $false } } function Get-PackageModelInstalledFilePath { <# .SYNOPSIS Resolves the final installed file path for a single-file package install. .DESCRIPTION Uses install.targetRelativePath when present and otherwise falls back to the canonical packageFile.fileName so single-file resource packages can share one simple install model. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageModelResult ) $install = $PackageModelResult.Package.install $targetRelativePath = $null if ($install.PSObject.Properties['targetRelativePath'] -and -not [string]::IsNullOrWhiteSpace([string]$install.targetRelativePath)) { $targetRelativePath = ([string]$install.targetRelativePath) -replace '/', '\' } elseif ($PackageModelResult.Package -and $PackageModelResult.Package.PSObject.Properties['packageFile'] -and $PackageModelResult.Package.packageFile -and $PackageModelResult.Package.packageFile.PSObject.Properties['fileName'] -and -not [string]::IsNullOrWhiteSpace([string]$PackageModelResult.Package.packageFile.fileName)) { $targetRelativePath = [string]$PackageModelResult.Package.packageFile.fileName } else { throw "PackageModel single-file install for '$($PackageModelResult.PackageId)' requires install.targetRelativePath or packageFile.fileName." } return (Join-Path $PackageModelResult.InstallDirectory $targetRelativePath) } function Install-PackageModelPackageFile { <# .SYNOPSIS Installs a package by placing one saved package file into the install directory. .DESCRIPTION Creates or replaces the target install directory, then copies the verified saved package file into the configured target-relative path. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageModelResult ) if ([string]::IsNullOrWhiteSpace($PackageModelResult.PackageFilePath) -or -not (Test-Path -LiteralPath $PackageModelResult.PackageFilePath -PathType Leaf)) { throw "PackageModel single-file install for '$($PackageModelResult.PackageId)' requires a saved package file." } $installedFilePath = Get-PackageModelInstalledFilePath -PackageModelResult $PackageModelResult $targetDirectory = Split-Path -Parent $installedFilePath $null = New-Item -ItemType Directory -Path (Split-Path -Parent $PackageModelResult.InstallDirectory) -Force if (Test-Path -LiteralPath $PackageModelResult.InstallDirectory) { Remove-PathIfExists -Path $PackageModelResult.InstallDirectory | Out-Null } $null = New-Item -ItemType Directory -Path $PackageModelResult.InstallDirectory -Force if (-not [string]::IsNullOrWhiteSpace($targetDirectory)) { $null = New-Item -ItemType Directory -Path $targetDirectory -Force } $null = Copy-FileToPath -SourcePath $PackageModelResult.PackageFilePath -TargetPath $installedFilePath -Overwrite return [pscustomobject]@{ Status = Get-PackageModelOwnedInstallStatus -PackageModelResult $PackageModelResult InstallKind = 'placePackageFile' InstallDirectory = $PackageModelResult.InstallDirectory InstalledFilePath = $installedFilePath ReusedExisting = $false } } function Format-PackageModelProcessArgument { [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-PackageModelInstallerProcess { <# .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 PackageModelResult The PackageModel result object that owns the install. .EXAMPLE Invoke-PackageModelInstallerProcess -PackageModelResult $result #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageModelResult ) $install = $PackageModelResult.Package.install $commandPath = if ($install.PSObject.Properties['commandPath'] -and -not [string]::IsNullOrWhiteSpace([string]$install.commandPath)) { Resolve-PackageModelTemplateText -Text ([string]$install.commandPath) -PackageModelConfig $PackageModelResult.PackageModelConfig -Package $PackageModelResult.Package } else { $PackageModelResult.PackageFilePath } $timestamp = (Get-Date -Format 'yyyyMMdd-HHmmss') $logPath = $null if ($install.PSObject.Properties['logRelativePath'] -and -not [string]::IsNullOrWhiteSpace([string]$install.logRelativePath)) { $logRelativePath = Resolve-PackageModelTemplateText -Text ([string]$install.logRelativePath) -PackageModelConfig $PackageModelResult.PackageModelConfig -Package $PackageModelResult.Package -ExtraTokens @{ timestamp = $timestamp } $logPath = [System.IO.Path]::GetFullPath((Join-Path $PackageModelResult.PackageModelConfig.PreferredTargetInstallRootDirectory ($logRelativePath -replace '/', '\'))) $null = New-Item -ItemType Directory -Path (Split-Path -Parent $logPath) -Force } $commandArguments = @() foreach ($argument in @($install.commandArguments)) { $resolvedArgument = Resolve-PackageModelTemplateText -Text ([string]$argument) -PackageModelConfig $PackageModelResult.PackageModelConfig -Package $PackageModelResult.Package -ExtraTokens @{ packageFilePath = $PackageModelResult.PackageFilePath installDirectory = $PackageModelResult.InstallDirectory installWorkspaceDirectory = $PackageModelResult.InstallWorkspaceDirectory downloadDirectory = $PackageModelResult.InstallWorkspaceDirectory logPath = $logPath timestamp = $timestamp } $commandArguments += (Format-PackageModelProcessArgument -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-PackageModelInstallTargetKind -Package $PackageModelResult.Package $workingDirectory = if (-not [string]::IsNullOrWhiteSpace([string]$PackageModelResult.InstallDirectory)) { $null = New-Item -ItemType Directory -Path $PackageModelResult.InstallDirectory -Force $PackageModelResult.InstallDirectory } else { $PackageModelResult.InstallWorkspaceDirectory } if (-not [string]::IsNullOrWhiteSpace([string]$workingDirectory)) { $null = New-Item -ItemType Directory -Path $workingDirectory -Force } $elevationPlan = Get-PackageModelInstallerElevationPlan -PackageModelResult $PackageModelResult $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>' } Write-PackageModelExecutionMessage -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 "PackageModel installer command exceeded the timeout of $timeoutSec seconds." } $process.Refresh() $exitCode = [int]$process.ExitCode $acceptedExitCodes = @($successExitCodes) + @($restartExitCodes) if ($exitCode -notin $acceptedExitCodes) { throw "PackageModel 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 Install-PackageModelPackageManagerPackage { <# .SYNOPSIS Installs a package through a package-manager command. .DESCRIPTION Runs the configured package-manager command with tokenized arguments and uses the install directory as the default working directory. .PARAMETER PackageModelResult The PackageModel result object to install. .EXAMPLE Install-PackageModelPackageManagerPackage -PackageModelResult $result #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageModelResult ) $install = $PackageModelResult.Package.install if (-not $install.PSObject.Properties['managerCommandPath'] -or [string]::IsNullOrWhiteSpace([string]$install.managerCommandPath)) { throw "PackageModel package-manager install for '$($PackageModelResult.PackageId)' requires install.managerCommandPath." } $managerCommandPath = Resolve-PackageModelTemplateText -Text ([string]$install.managerCommandPath) -PackageModelConfig $PackageModelResult.PackageModelConfig -Package $PackageModelResult.Package $commandArguments = @() foreach ($argument in @($install.commandArguments)) { $commandArguments += (Resolve-PackageModelTemplateText -Text ([string]$argument) -PackageModelConfig $PackageModelResult.PackageModelConfig -Package $PackageModelResult.Package -ExtraTokens @{ packageSpec = if ($install.PSObject.Properties['packageSpec']) { [string]$install.packageSpec } else { $null } packageFilePath = $PackageModelResult.PackageFilePath installDirectory = $PackageModelResult.InstallDirectory installWorkspaceDirectory = $PackageModelResult.InstallWorkspaceDirectory downloadDirectory = $PackageModelResult.InstallWorkspaceDirectory }) } $null = New-Item -ItemType Directory -Path $PackageModelResult.InstallDirectory -Force Push-Location $PackageModelResult.InstallDirectory try { & $managerCommandPath @commandArguments $exitCode = $LASTEXITCODE if ($null -eq $exitCode) { $exitCode = 0 } } finally { Pop-Location } $successExitCodes = if ($install.PSObject.Properties['successExitCodes']) { @($install.successExitCodes | ForEach-Object { [int]$_ }) } else { @(0) } if ($exitCode -notin $successExitCodes) { throw "PackageModel package-manager install failed with exit code $exitCode." } return [pscustomobject]@{ Status = Get-PackageModelOwnedInstallStatus -PackageModelResult $PackageModelResult InstallKind = 'packageManagerInstall' InstallDirectory = $PackageModelResult.InstallDirectory ReusedExisting = $false CommandPath = $managerCommandPath CommandArguments = @($commandArguments) ExitCode = $exitCode } } function Install-PackageModelPackage { <# .SYNOPSIS Installs or reuses the selected package. .DESCRIPTION Reuses or adopts a valid existing install when the earlier ownership/policy decision allows it, otherwise executes the configured install kind and attaches the install result to the PackageModel result object. .PARAMETER PackageModelResult The PackageModel result object to enrich. .EXAMPLE Install-PackageModelPackage -PackageModelResult $result #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$PackageModelResult ) $package = $PackageModelResult.Package $install = $package.install if (-not $install -or -not $install.PSObject.Properties['kind']) { throw "PackageModel release '$($package.id)' does not define install.kind." } if ([string]::Equals([string]$PackageModelResult.InstallOrigin, 'AlreadySatisfied', [System.StringComparison]::OrdinalIgnoreCase)) { Write-PackageModelExecutionMessage -Message "[ACTION] Skipped installer because machine prerequisite is already satisfied." return $PackageModelResult } if ($PackageModelResult.ExistingPackage -and $PackageModelResult.ExistingPackage.Decision -eq 'ReusePackageModelOwned') { $PackageModelResult.InstallDirectory = $PackageModelResult.ExistingPackage.InstallDirectory $PackageModelResult.InstallOrigin = 'PackageModelReused' $PackageModelResult.Install = [pscustomobject]@{ Status = 'ReusedPackageModelOwned' InstallKind = 'existingInstall' InstallDirectory = $PackageModelResult.ExistingPackage.InstallDirectory ReusedExisting = $true CandidatePath = $PackageModelResult.ExistingPackage.CandidatePath } $PackageModelResult.Validation = $PackageModelResult.ExistingPackage.Validation Write-PackageModelExecutionMessage -Message ("[ACTION] Reused PackageModel-owned install '{0}'." -f $PackageModelResult.ExistingPackage.InstallDirectory) return $PackageModelResult } if ($PackageModelResult.ExistingPackage -and $PackageModelResult.ExistingPackage.Decision -eq 'AdoptExternal') { $PackageModelResult.InstallDirectory = $PackageModelResult.ExistingPackage.InstallDirectory $PackageModelResult.InstallOrigin = 'AdoptedExternal' $PackageModelResult.Install = [pscustomobject]@{ Status = 'AdoptedExternal' InstallKind = 'existingInstall' InstallDirectory = $PackageModelResult.ExistingPackage.InstallDirectory ReusedExisting = $true CandidatePath = $PackageModelResult.ExistingPackage.CandidatePath } $PackageModelResult.Validation = $PackageModelResult.ExistingPackage.Validation Write-PackageModelExecutionMessage -Message ("[ACTION] Adopted external install '{0}'." -f $PackageModelResult.ExistingPackage.InstallDirectory) return $PackageModelResult } if ([string]::Equals([string]$install.kind, 'reuseExisting', [System.StringComparison]::OrdinalIgnoreCase)) { throw "PackageModel release '$($package.id)' requires an existing install, but no reusable install passed validation." } if ($PackageModelResult.PackageFileSave -and -not $PackageModelResult.PackageFileSave.Success) { throw $PackageModelResult.PackageFileSave.ErrorMessage } switch -Exact ([string]$install.kind) { 'expandArchive' { Write-PackageModelExecutionMessage -Message ("[ACTION] Installing package archive into '{0}'." -f $PackageModelResult.InstallDirectory) $PackageModelResult.Install = Install-PackageModelArchive -PackageModelResult $PackageModelResult } 'placePackageFile' { Write-PackageModelExecutionMessage -Message ("[ACTION] Placing package file into '{0}'." -f $PackageModelResult.InstallDirectory) $PackageModelResult.Install = Install-PackageModelPackageFile -PackageModelResult $PackageModelResult } 'runInstaller' { $targetKind = Get-PackageModelInstallTargetKind -Package $package $targetText = if ([string]::IsNullOrWhiteSpace([string]$PackageModelResult.InstallDirectory)) { '<machine prerequisite>' } else { [string]$PackageModelResult.InstallDirectory } Write-PackageModelExecutionMessage -Message ("[ACTION] Running installer for target '{0}'." -f $targetText) $installerResult = Invoke-PackageModelInstallerProcess -PackageModelResult $PackageModelResult $PackageModelResult.Install = [pscustomobject]@{ Status = Get-PackageModelOwnedInstallStatus -PackageModelResult $PackageModelResult InstallKind = 'runInstaller' TargetKind = $targetKind InstallDirectory = $PackageModelResult.InstallDirectory ReusedExisting = $false Installer = $installerResult } } 'packageManagerInstall' { Write-PackageModelExecutionMessage -Message ("[ACTION] Running package-manager install into '{0}'." -f $PackageModelResult.InstallDirectory) $PackageModelResult.Install = Install-PackageModelPackageManagerPackage -PackageModelResult $PackageModelResult } default { throw "Unsupported PackageModel install kind '$($install.kind)'." } } $PackageModelResult.InstallOrigin = if ([string]::Equals((Get-PackageModelInstallTargetKind -Package $package), 'machinePrerequisite', [System.StringComparison]::OrdinalIgnoreCase)) { 'PackageModelApplied' } else { 'PackageModelInstalled' } Write-PackageModelExecutionMessage -Message ("[ACTION] Completed PackageModel-owned install with status '{0}'." -f $PackageModelResult.Install.Status) return $PackageModelResult } |