Acrobat Reader enforcement:
- playbook/shopfloor-setup/common/ is the cross-PC-type staging dir. Mirrors
CMM/ structure (enforce script + its Install-FromManifest copy + manifest
template + register script).
- Acrobat-Enforce.ps1 runs as SYSTEM on every logon, reads
acrobatSharePath from site-config.common, mounts the SFLD share with
the same HKLM-backed credential lookup CMM-Enforce uses, hands the
acrobat-manifest.json from the share to Install-FromManifest.
- Install-FromManifest extended with Type=CMD so it can invoke vendor-
supplied .cmd wrappers (Install-AcroReader.cmd does a two-step MSI+MSP
install that does not fit MSI/EXE types cleanly). cmd.exe /c wraps it
because UseShellExecute=false cannot launch .cmd directly.
- Register-AcrobatEnforce.ps1 stages scripts to C:\Program Files\GE\Acrobat
and registers "GE Acrobat Enforce" scheduled task. Called from
Run-ShopfloorSetup.ps1 right before the enrollment (PPKG) step so it
applies to every PC type, not just CMM.
- acrobat-manifest.template.json is the repo reference; the authoritative
copy lives on the SFLD share at
\\tsgwp00525.wjs.geaerospace.net\shared\dt\shopfloor\common\acrobat\
Bumping Acrobat updates = drop new MSP on share, bump DetectionValue in
manifest; enforcer catches every PC on next logon.
- site-config.json: add "common": { "acrobatSharePath": ... }. Uses a
new top-level block rather than a PC-type-specific one since Acrobat
applies everywhere.
Initial install still happens via the preinstall flow
(Install-AcroReader.cmd during WinPE). The enforcer is the ongoing-
updates side; on a freshly-imaged PC detection passes and it no-ops.
Also in this commit:
- run-enrollment.ps1: provtool.exe argument syntax fix. First test
returned 0x80004005 E_FAIL in 1s because /ppkg: and /log: are not
valid provtool flags; the cmdlet's internal call used positional
path + /quiet + /source. Switched to that syntax.
259 lines
8.9 KiB
PowerShell
259 lines
8.9 KiB
PowerShell
# Install-FromManifest.ps1 - Generic JSON-manifest installer for cross-PC-type
|
|
# apps enforced from the SFLD share (Acrobat Reader DC today; others later).
|
|
#
|
|
# Duplicated from CMM\lib\Install-FromManifest.ps1 with a few differences:
|
|
# - adds Type=CMD (cmd.exe /c wrapper, needed for Acrobat's two-step
|
|
# MSI + MSP install that the vendor ships as Install-AcroReader.cmd)
|
|
# - unchanged otherwise; a future pass will unify both libraries.
|
|
#
|
|
# Called from:
|
|
# - Acrobat-Enforce.ps1 on logon with InstallerRoot=<mounted tsgwp00525 share>
|
|
#
|
|
# Returns via exit code: 0 if every required app is either already installed
|
|
# or installed successfully; non-zero if any install failed.
|
|
|
|
param(
|
|
[Parameter(Mandatory=$true)]
|
|
[string]$ManifestPath,
|
|
|
|
[Parameter(Mandatory=$true)]
|
|
[string]$InstallerRoot,
|
|
|
|
[Parameter(Mandatory=$true)]
|
|
[string]$LogFile
|
|
)
|
|
|
|
$ErrorActionPreference = 'Continue'
|
|
|
|
$logDir = Split-Path -Parent $LogFile
|
|
if (-not (Test-Path $logDir)) {
|
|
New-Item -Path $logDir -ItemType Directory -Force | Out-Null
|
|
}
|
|
|
|
function Write-InstallLog {
|
|
param(
|
|
[Parameter(Mandatory=$true, Position=0)]
|
|
[string]$Message,
|
|
[Parameter(Position=1)]
|
|
[ValidateSet('INFO','WARN','ERROR')]
|
|
[string]$Level = 'INFO'
|
|
)
|
|
$stamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
|
$line = "[$stamp] [$Level] $Message"
|
|
Write-Host $line
|
|
try {
|
|
$fs = New-Object System.IO.FileStream(
|
|
$LogFile,
|
|
[System.IO.FileMode]::Append,
|
|
[System.IO.FileAccess]::Write,
|
|
[System.IO.FileShare]::Read,
|
|
4096,
|
|
[System.IO.FileOptions]::WriteThrough
|
|
)
|
|
$bytes = [System.Text.Encoding]::UTF8.GetBytes($line + "`r`n")
|
|
$fs.Write($bytes, 0, $bytes.Length)
|
|
$fs.Flush()
|
|
$fs.Dispose()
|
|
} catch {
|
|
Add-Content -Path $LogFile -Value $line -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
Write-InstallLog "================================================================"
|
|
Write-InstallLog "=== Install-FromManifest session start (PID $PID) ==="
|
|
Write-InstallLog "Manifest: $ManifestPath"
|
|
Write-InstallLog "InstallerRoot: $InstallerRoot"
|
|
Write-InstallLog "================================================================"
|
|
|
|
if (-not (Test-Path -LiteralPath $ManifestPath)) {
|
|
Write-InstallLog "Manifest not found: $ManifestPath" "ERROR"
|
|
exit 2
|
|
}
|
|
|
|
if (-not (Test-Path -LiteralPath $InstallerRoot)) {
|
|
Write-InstallLog "InstallerRoot not found: $InstallerRoot" "ERROR"
|
|
exit 2
|
|
}
|
|
|
|
try {
|
|
$config = Get-Content -LiteralPath $ManifestPath -Raw | ConvertFrom-Json
|
|
} catch {
|
|
Write-InstallLog "Failed to parse manifest: $_" "ERROR"
|
|
exit 2
|
|
}
|
|
|
|
if (-not $config.Applications) {
|
|
Write-InstallLog "No Applications in manifest - nothing to do"
|
|
exit 0
|
|
}
|
|
|
|
Write-InstallLog "Manifest lists $($config.Applications.Count) app(s)"
|
|
|
|
function Test-AppInstalled {
|
|
param($App)
|
|
|
|
if (-not $App.DetectionMethod) { return $false }
|
|
|
|
try {
|
|
switch ($App.DetectionMethod) {
|
|
"Registry" {
|
|
if (-not (Test-Path $App.DetectionPath)) { return $false }
|
|
if ($App.DetectionName) {
|
|
$value = Get-ItemProperty -Path $App.DetectionPath -Name $App.DetectionName -ErrorAction SilentlyContinue
|
|
if (-not $value) { return $false }
|
|
if ($App.DetectionValue) {
|
|
return ($value.$($App.DetectionName) -eq $App.DetectionValue)
|
|
}
|
|
return $true
|
|
}
|
|
return $true
|
|
}
|
|
"File" {
|
|
return Test-Path $App.DetectionPath
|
|
}
|
|
default {
|
|
Write-InstallLog " Unknown detection method: $($App.DetectionMethod)" "WARN"
|
|
return $false
|
|
}
|
|
}
|
|
} catch {
|
|
Write-InstallLog " Detection check threw: $_" "WARN"
|
|
return $false
|
|
}
|
|
}
|
|
|
|
$installed = 0
|
|
$skipped = 0
|
|
$failed = 0
|
|
|
|
foreach ($app in $config.Applications) {
|
|
cmd /c "shutdown /a 2>nul" *>$null
|
|
|
|
Write-InstallLog "==> $($app.Name)"
|
|
|
|
if (Test-AppInstalled -App $app) {
|
|
Write-InstallLog " Already installed at expected version - skipping"
|
|
$skipped++
|
|
continue
|
|
}
|
|
|
|
$installerPath = Join-Path $InstallerRoot $app.Installer
|
|
if (-not (Test-Path -LiteralPath $installerPath)) {
|
|
Write-InstallLog " Installer file not found: $installerPath" "ERROR"
|
|
$failed++
|
|
continue
|
|
}
|
|
|
|
Write-InstallLog " Installing from $installerPath"
|
|
if ($app.InstallArgs) {
|
|
Write-InstallLog " InstallArgs: $($app.InstallArgs)"
|
|
}
|
|
|
|
try {
|
|
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
|
$psi.UseShellExecute = $false
|
|
$psi.CreateNoWindow = $true
|
|
$psi.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
|
|
|
|
$msiLog = $null
|
|
|
|
if ($app.Type -eq "MSI") {
|
|
$safeName = $app.Name -replace '[^a-zA-Z0-9]','_'
|
|
$msiLog = Join-Path $logDir "msi-$safeName.log"
|
|
if (Test-Path $msiLog) { Remove-Item $msiLog -Force -ErrorAction SilentlyContinue }
|
|
|
|
$psi.FileName = "msiexec.exe"
|
|
$psi.Arguments = "/i `"$installerPath`""
|
|
if ($app.InstallArgs) { $psi.Arguments += " " + $app.InstallArgs }
|
|
$psi.Arguments += " /L*v `"$msiLog`""
|
|
Write-InstallLog " msiexec verbose log: $msiLog"
|
|
}
|
|
elseif ($app.Type -eq "EXE") {
|
|
$psi.FileName = $installerPath
|
|
if ($app.InstallArgs) { $psi.Arguments = $app.InstallArgs }
|
|
if ($app.LogFile) {
|
|
Write-InstallLog " Installer log: $($app.LogFile)"
|
|
}
|
|
}
|
|
elseif ($app.Type -eq "CMD") {
|
|
# .cmd/.bat scripts cannot be executed directly via
|
|
# ProcessStartInfo with UseShellExecute=false; route through
|
|
# cmd.exe /c. Vendor-provided two-step install wrappers
|
|
# (Install-AcroReader.cmd) fit here naturally.
|
|
$psi.FileName = "cmd.exe"
|
|
$psi.Arguments = "/c `"$installerPath`""
|
|
if ($app.InstallArgs) { $psi.Arguments += " " + $app.InstallArgs }
|
|
if ($app.LogFile) {
|
|
Write-InstallLog " Installer log: $($app.LogFile)"
|
|
}
|
|
}
|
|
else {
|
|
Write-InstallLog " Unsupported Type: $($app.Type) - skipping" "ERROR"
|
|
$failed++
|
|
continue
|
|
}
|
|
|
|
$proc = [System.Diagnostics.Process]::Start($psi)
|
|
$proc.WaitForExit()
|
|
$exitCode = $proc.ExitCode
|
|
|
|
if ($exitCode -eq 0 -or $exitCode -eq 1641 -or $exitCode -eq 3010) {
|
|
Write-InstallLog " Exit code $exitCode - SUCCESS"
|
|
if ($exitCode -eq 3010) { Write-InstallLog " (Reboot pending for $($app.Name))" }
|
|
if ($exitCode -eq 1641) { Write-InstallLog " (Installer initiated a reboot for $($app.Name))" }
|
|
$installed++
|
|
}
|
|
else {
|
|
Write-InstallLog " Exit code $exitCode - FAILED" "ERROR"
|
|
|
|
if (($app.Type -eq "EXE" -or $app.Type -eq "CMD") -and $app.LogFile -and (Test-Path $app.LogFile)) {
|
|
Write-InstallLog " --- last 30 lines of $($app.LogFile) ---"
|
|
Get-Content $app.LogFile -Tail 30 -ErrorAction SilentlyContinue | ForEach-Object {
|
|
Write-InstallLog " $_"
|
|
}
|
|
Write-InstallLog " --- end installer log tail ---"
|
|
}
|
|
|
|
if ($app.Type -eq "MSI" -and $msiLog -and (Test-Path $msiLog)) {
|
|
Write-InstallLog " --- meaningful lines from $msiLog ---"
|
|
$patterns = @(
|
|
'Note: 1: ',
|
|
'return value 3',
|
|
'Error \d+\.',
|
|
'CustomAction .* returned actual error',
|
|
'Failed to ',
|
|
'Installation failed',
|
|
'1: 2262',
|
|
'1: 2203',
|
|
'1: 2330'
|
|
)
|
|
$regex = ($patterns -join '|')
|
|
$matches = Select-String -Path $msiLog -Pattern $regex -ErrorAction SilentlyContinue |
|
|
Select-Object -First 30
|
|
if ($matches) {
|
|
foreach ($m in $matches) { Write-InstallLog " $($m.Line.Trim())" }
|
|
} else {
|
|
Get-Content $msiLog -Tail 25 -ErrorAction SilentlyContinue | ForEach-Object {
|
|
Write-InstallLog " $_"
|
|
}
|
|
}
|
|
Write-InstallLog " --- end MSI log scan ---"
|
|
}
|
|
|
|
$failed++
|
|
}
|
|
} catch {
|
|
Write-InstallLog " Install threw: $_" "ERROR"
|
|
$failed++
|
|
}
|
|
}
|
|
|
|
Write-InstallLog "============================================"
|
|
Write-InstallLog "Install-FromManifest complete: $installed installed, $skipped skipped, $failed failed"
|
|
Write-InstallLog "============================================"
|
|
|
|
cmd /c "shutdown /a 2>nul" *>$null
|
|
|
|
if ($failed -gt 0) { exit 1 }
|
|
exit 0
|