manifest engine: resolve installer paths without the PS provider
Join-Path routes a drive-qualified path through the PowerShell provider, so
it returns null when that drive has gone away mid-cycle, and every consumer
then bound the null straight into Test-Path -LiteralPath and crashed the
entry with a message that names neither the entry nor the path.
Replace the six Join-Path $InstallerRoot sites with Join-InstallerPath, which
does plain string math, and have all six callers treat a null resolve as
"not found" and log it. Only the PS1 branch had a guard before, and it
covered a null Script value, not a null resolved path.
The helper deliberately avoids [IO.Path]::Combine: its separator and
rooted-path rules follow the host platform, so behaviour cannot be verified
off-Windows. It also rejects rooted values ("\x", "D:\x", "\\server\share"),
which Combine would have resolved outside the share root. Checked all 36
path values across the share manifests: none are rooted today.
Behaviour verified against the real manifest values (forward slashes,
backslashes, trailing-slash root, null, whitespace, rooted, UNC).
This commit is contained in:
@@ -241,6 +241,24 @@ function Test-AppInstalled {
|
||||
# ---------------------------------------------------------------------------
|
||||
# Action dispatch
|
||||
# ---------------------------------------------------------------------------
|
||||
function Join-InstallerPath {
|
||||
# Plain string math, NOT Join-Path. Join-Path resolves the drive qualifier
|
||||
# through the PS provider and emits NOTHING (null) if that drive vanished
|
||||
# mid-cycle - the null then binds into Test-Path -LiteralPath and throws
|
||||
# "Cannot bind argument to parameter 'LiteralPath' because it is null".
|
||||
# [IO.Path]::Combine is no good either: its separator and rooted-path rules
|
||||
# follow the host platform, so it cannot be validated off-Windows.
|
||||
# Returns $null when the entry is unusable; every caller treats null as
|
||||
# "not found" and logs, instead of crashing the entry.
|
||||
param([string]$Root, [string]$Rel)
|
||||
if ([string]::IsNullOrWhiteSpace($Root) -or [string]::IsNullOrWhiteSpace($Rel)) { return $null }
|
||||
$clean = ($Rel -replace '/', '\').Trim()
|
||||
# Reject rooted values ('\x', 'D:\x', '\\server\share'): a manifest entry
|
||||
# must resolve UNDER the share root, never escape it.
|
||||
if ($clean -match '^(\\|[A-Za-z]:)') { return $null }
|
||||
return ($Root.TrimEnd('\') + '\' + $clean)
|
||||
}
|
||||
|
||||
function Invoke-InstallerAction {
|
||||
param($App)
|
||||
|
||||
@@ -251,8 +269,8 @@ function Invoke-InstallerAction {
|
||||
|
||||
switch ($App.Type) {
|
||||
'MSI' {
|
||||
$installerPath = Join-Path $InstallerRoot $App.Installer
|
||||
if (-not (Test-Path -LiteralPath $installerPath)) {
|
||||
$installerPath = Join-InstallerPath $InstallerRoot $App.Installer
|
||||
if ([string]::IsNullOrWhiteSpace($installerPath) -or -not (Test-Path -LiteralPath $installerPath)) {
|
||||
Write-InstallLog " MSI not found: $installerPath" 'ERROR'
|
||||
return [pscustomobject]@{ ExitCode = -1; LogRef = $null }
|
||||
}
|
||||
@@ -270,8 +288,8 @@ function Invoke-InstallerAction {
|
||||
return [pscustomobject]@{ ExitCode = $proc.ExitCode; LogRef = $msiLog }
|
||||
}
|
||||
'EXE' {
|
||||
$installerPath = Join-Path $InstallerRoot $App.Installer
|
||||
if (-not (Test-Path -LiteralPath $installerPath)) {
|
||||
$installerPath = Join-InstallerPath $InstallerRoot $App.Installer
|
||||
if ([string]::IsNullOrWhiteSpace($installerPath) -or -not (Test-Path -LiteralPath $installerPath)) {
|
||||
Write-InstallLog " EXE not found: $installerPath" 'ERROR'
|
||||
return [pscustomobject]@{ ExitCode = -1; LogRef = $null }
|
||||
}
|
||||
@@ -346,8 +364,8 @@ function Invoke-InstallerAction {
|
||||
return [pscustomobject]@{ ExitCode = $exitCode; LogRef = $App.LogFile }
|
||||
}
|
||||
{ $_ -eq 'CMD' -or $_ -eq 'BAT' } {
|
||||
$installerPath = Join-Path $InstallerRoot $App.Installer
|
||||
if (-not (Test-Path -LiteralPath $installerPath)) {
|
||||
$installerPath = Join-InstallerPath $InstallerRoot $App.Installer
|
||||
if ([string]::IsNullOrWhiteSpace($installerPath) -or -not (Test-Path -LiteralPath $installerPath)) {
|
||||
Write-InstallLog " CMD/BAT not found: $installerPath" 'ERROR'
|
||||
return [pscustomobject]@{ ExitCode = -1; LogRef = $null }
|
||||
}
|
||||
@@ -369,8 +387,8 @@ function Invoke-InstallerAction {
|
||||
Write-InstallLog (" PS1 entry '{0}' has no Script/Installer value (Script={1}, Installer={2}) - skipping" -f $App.Name, $App.Script, $App.Installer) 'ERROR'
|
||||
return [pscustomobject]@{ ExitCode = -1; LogRef = $null }
|
||||
}
|
||||
$scriptPath = Join-Path $InstallerRoot $rel
|
||||
if (-not (Test-Path -LiteralPath $scriptPath)) {
|
||||
$scriptPath = Join-InstallerPath $InstallerRoot $rel
|
||||
if ([string]::IsNullOrWhiteSpace($scriptPath) -or -not (Test-Path -LiteralPath $scriptPath)) {
|
||||
Write-InstallLog " PS1 not found: $scriptPath (from rel '$rel')" 'ERROR'
|
||||
return [pscustomobject]@{ ExitCode = -1; LogRef = $null }
|
||||
}
|
||||
@@ -384,8 +402,8 @@ function Invoke-InstallerAction {
|
||||
return [pscustomobject]@{ ExitCode = $proc.ExitCode; LogRef = $null }
|
||||
}
|
||||
'INF' {
|
||||
$infPath = Join-Path $InstallerRoot $App.Installer
|
||||
if (-not (Test-Path -LiteralPath $infPath)) {
|
||||
$infPath = Join-InstallerPath $InstallerRoot $App.Installer
|
||||
if ([string]::IsNullOrWhiteSpace($infPath) -or -not (Test-Path -LiteralPath $infPath)) {
|
||||
Write-InstallLog " INF not found: $infPath" 'ERROR'
|
||||
return [pscustomobject]@{ ExitCode = -1; LogRef = $null }
|
||||
}
|
||||
@@ -397,8 +415,8 @@ function Invoke-InstallerAction {
|
||||
}
|
||||
'File' {
|
||||
# Copy a file from the share (configs/*) to an absolute on-PC path.
|
||||
$source = Join-Path $InstallerRoot $App.Source
|
||||
if (-not (Test-Path -LiteralPath $source)) {
|
||||
$source = Join-InstallerPath $InstallerRoot $App.Source
|
||||
if ([string]::IsNullOrWhiteSpace($source) -or -not (Test-Path -LiteralPath $source)) {
|
||||
Write-InstallLog " File source not found: $source" 'ERROR'
|
||||
return [pscustomobject]@{ ExitCode = -1; LogRef = $null }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user