CMM/DODA: Install-DODA does the full unattended runtime+app install
The vendor GE_DODA_build5_Installer.exe is a multi-app NSIS bundler whose /S silent mode is broken (nested sub-install -> MSI 1619); VM-confirmed it only works interactively. So Install-DODA now drives the bundled components directly, all silent: python-2.7.16 msi /qn -> C:\Python27 openjdk-1.8.0.232 msi /qn -> C:\Program Files\RedHat gs927w64.exe /S -> Ghostscript 9.27 vc_redist /quiet doda_build*.zip -> C:\Apps\DODA (+ writes .settings the zip lacks) Then sets AFDA_INSTALL_DIR + Java PATH, precompiles .py->.pyc as admin, and grants Users+Auth Users Full on C:\Apps\DODA so the locked-down operator can run DovetailAnalysis.exe (writes .pyc/PreProcess/$TMP). Each step detects-and-skips; idempotent. VM-validated end to end incl a non-admin operator-paths test. Runtime binaries (python/jdk/gs/vc msis + doda zip) are staged on the share, not committed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,59 +1,165 @@
|
||||
# Install-DODA.ps1 - Extract DODA zip to C:\Apps\DODA\.
|
||||
# Install-DODA.ps1 - full unattended DODA install (runtimes + app + config + ACLs).
|
||||
#
|
||||
# Called by Install-FromManifest as a Type=PS1 entry. The zip is staged
|
||||
# alongside this script in C:\CMM-Install\ by startnet.cmd.
|
||||
# The vendor GE_DODA_build5_Installer.exe is a multi-app NSIS bundler. Its /S
|
||||
# silent mode is BROKEN (a nested sub-install gets a bad package path -> MSI
|
||||
# error 1619), so it cannot be used for imaging. Each bundled component installs
|
||||
# cleanly on its own, so we drive them directly here. (VM-validated 2026-06-17.)
|
||||
#
|
||||
# Components staged in C:\CMM-Install (robocopied from the SFLD/enrollment share
|
||||
# by startnet.cmd), all installed SILENTLY:
|
||||
# python-2.7.16.amd64.msi -> C:\Python27 (msiexec /qn)
|
||||
# java-1.8.0-openjdk-*.msi -> C:\Program Files\RedHat (msiexec /qn)
|
||||
# gs927w64.exe -> C:\Program Files\gs\gs9.27 (/S)
|
||||
# vc_redist.x64.exe -> VC++ runtime (/quiet)
|
||||
# doda_build*.zip -> C:\Apps\DODA (app files)
|
||||
#
|
||||
# DODA at runtime is DovetailAnalysis.exe -> JVM (OpenJDK) + C:\Python27 python +
|
||||
# Ghostscript (PS->PDF). .settings (NOT in the zip - the NSIS installer wrote it)
|
||||
# hardcodes those paths; we write it here.
|
||||
#
|
||||
# LOCKDOWN: DovetailAnalysis.exe runs as the locked-down OPERATOR (not admin) and
|
||||
# writes throughout C:\Apps\DODA (.pyc, PreProcess) + $TMP. We (a) grant Users +
|
||||
# Authenticated Users Full on C:\Apps\DODA and (b) PRECOMPILE the .py to .pyc as
|
||||
# admin so the operator never needs to write bytecode into a protected path.
|
||||
#
|
||||
# Run as administrator / SYSTEM (imaging or GE-Enforce). Idempotent: each step
|
||||
# detects-and-skips if already present, and ACL/.settings/env re-assert every run
|
||||
# (so a post-lockdown re-run repairs a stripped ACE).
|
||||
|
||||
$ErrorActionPreference = 'Continue'
|
||||
|
||||
# The doda zip + runtime installers are staged ALONGSIDE this script (same dir).
|
||||
# When the manifest runs it, that dir is C:\CMM-Install (= $PSScriptRoot).
|
||||
$stagingRoot = $PSScriptRoot
|
||||
if (-not $stagingRoot -or -not (Test-Path $stagingRoot)) { $stagingRoot = 'C:\CMM-Install' }
|
||||
$installDir = 'C:\Apps\DODA'
|
||||
$zipPattern = 'doda_build*.zip'
|
||||
$stagingRoot = Split-Path $PSScriptRoot -ErrorAction SilentlyContinue
|
||||
if (-not $stagingRoot) { $stagingRoot = 'C:\CMM-Install' }
|
||||
$python27 = 'C:\Python27\python.exe'
|
||||
$gsExe = 'C:\Program Files\gs\gs9.27\bin\gswin64c.exe'
|
||||
|
||||
$zip = Get-ChildItem -Path $stagingRoot -Filter $zipPattern -File -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||
if (-not $zip) {
|
||||
Write-Host "DODA zip not found in $stagingRoot (pattern: $zipPattern)"
|
||||
exit 1
|
||||
$logDir = 'C:\Logs\CMM'
|
||||
New-Item -ItemType Directory -Path $logDir -Force -EA SilentlyContinue | Out-Null
|
||||
$ts = Get-Date -Format 'yyyyMMdd-HHmmss'
|
||||
$log = Join-Path $logDir "doda-install-$ts.log"
|
||||
function Log($m){ $line = "[{0}] {1}" -f (Get-Date -Format 'HH:mm:ss'), $m; Write-Host $line; Add-Content -Path $log -Value $line -EA SilentlyContinue }
|
||||
|
||||
function Find-One($pattern) {
|
||||
Get-ChildItem -Path $stagingRoot -Filter $pattern -File -EA SilentlyContinue | Select-Object -First 1
|
||||
}
|
||||
|
||||
if (-not (Test-Path $installDir)) {
|
||||
New-Item -Path $installDir -ItemType Directory -Force | Out-Null
|
||||
}
|
||||
Log "==== DODA install on $env:COMPUTERNAME (staging=$stagingRoot) ===="
|
||||
|
||||
Write-Host "Extracting $($zip.Name) to $installDir..."
|
||||
try {
|
||||
Expand-Archive -LiteralPath $zip.FullName -DestinationPath $installDir -Force -ErrorAction Stop
|
||||
Write-Host "DODA extracted to $installDir"
|
||||
} catch {
|
||||
Write-Host "ERROR: Extract failed - $_"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# MergeFiles.exe (cmm-utilities toolchain) reads C:\Apps\DODA\PreProcess\ as
|
||||
# its working dir. The DODA zip extracts flat without it, so create it here -
|
||||
# a missing PreProcess dir is the known cause of MergeFiles.GetDoDAFolder
|
||||
# throwing DirectoryNotFoundException (see cmm-utilities dotNET event.txt).
|
||||
$preProcess = Join-Path $installDir 'PreProcess'
|
||||
if (-not (Test-Path $preProcess)) {
|
||||
New-Item -Path $preProcess -ItemType Directory -Force | Out-Null
|
||||
Write-Host "Created $preProcess"
|
||||
}
|
||||
|
||||
# DODA runs as the LOCKED-DOWN OPERATOR (not admin) and writes throughout its
|
||||
# install folder at runtime - PreProcess\ (MergeFiles GetDoDAFolder) plus
|
||||
# output/temp elsewhere under C:\Apps\DODA. Grant Users + Authenticated Users
|
||||
# Full on the WHOLE folder (object+container inherit; /T applies it to every
|
||||
# existing child incl PreProcess). SIDs, not names, to stay locale-independent.
|
||||
# Applied every run so it re-asserts after any lockdown pass that strips the ACE.
|
||||
foreach ($sid in '*S-1-5-32-545','*S-1-5-11') { # BUILTIN\Users, NT AUTHORITY\Authenticated Users
|
||||
& icacls $installDir /grant "${sid}:(OI)(CI)F" /T /C 2>&1 | Out-Null
|
||||
}
|
||||
Write-Host "Granted Users + Authenticated Users Full on $installDir (recursive)"
|
||||
|
||||
if (Test-Path (Join-Path $installDir 'DovetailAnalysis.exe')) {
|
||||
Write-Host "DovetailAnalysis.exe verified present"
|
||||
exit 0
|
||||
# --- 1. Python 2.7 ----------------------------------------------------------
|
||||
if (Test-Path $python27) {
|
||||
Log "Python 2.7 already present - skip"
|
||||
} else {
|
||||
Write-Host "ERROR: DovetailAnalysis.exe not found after extract"
|
||||
$msi = Find-One 'python-2.7*.msi'
|
||||
if ($msi) {
|
||||
Log "Installing Python: $($msi.Name)"
|
||||
$p = Start-Process msiexec.exe -Wait -PassThru -ArgumentList @('/i', "`"$($msi.FullName)`"", '/qn', '/norestart', 'ADDLOCAL=ALL')
|
||||
Log " Python msiexec exit $($p.ExitCode)"
|
||||
} else { Log "WARN: python-2.7*.msi not found in $stagingRoot" }
|
||||
}
|
||||
|
||||
# --- 2. OpenJDK 8 -----------------------------------------------------------
|
||||
$jdk = Get-ChildItem 'C:\Program Files\RedHat','C:\Program Files\Java' -Directory -EA SilentlyContinue |
|
||||
Where-Object Name -match 'openjdk|jdk|jre' | Select-Object -First 1
|
||||
if ($jdk) {
|
||||
Log "OpenJDK already present ($($jdk.Name)) - skip"
|
||||
} else {
|
||||
$msi = Find-One 'java-*openjdk*.msi'
|
||||
if ($msi) {
|
||||
Log "Installing OpenJDK: $($msi.Name)"
|
||||
$p = Start-Process msiexec.exe -Wait -PassThru -ArgumentList @('/i', "`"$($msi.FullName)`"", '/qn', '/norestart')
|
||||
Log " OpenJDK msiexec exit $($p.ExitCode)"
|
||||
$jdk = Get-ChildItem 'C:\Program Files\RedHat','C:\Program Files\Java' -Directory -EA SilentlyContinue |
|
||||
Where-Object Name -match 'openjdk|jdk|jre' | Select-Object -First 1
|
||||
} else { Log "WARN: java-*openjdk*.msi not found in $stagingRoot" }
|
||||
}
|
||||
|
||||
# --- 3. Ghostscript 9.27 ----------------------------------------------------
|
||||
if (Test-Path $gsExe) {
|
||||
Log "Ghostscript 9.27 already present - skip"
|
||||
} else {
|
||||
$gs = Find-One 'gs927w64.exe'
|
||||
if ($gs) {
|
||||
Log "Installing Ghostscript: $($gs.Name)"
|
||||
$p = Start-Process $gs.FullName -Wait -PassThru -ArgumentList '/S'
|
||||
Log " Ghostscript exit $($p.ExitCode)"
|
||||
} else { Log "WARN: gs927w64.exe not found in $stagingRoot" }
|
||||
}
|
||||
|
||||
# --- 4. VC++ redist (best-effort) ------------------------------------------
|
||||
$vc = Find-One 'vc_redist.x64.exe'
|
||||
if ($vc) {
|
||||
Log "Installing VC++ redist: $($vc.Name)"
|
||||
$p = Start-Process $vc.FullName -Wait -PassThru -ArgumentList @('/quiet', '/norestart')
|
||||
Log " VC++ exit $($p.ExitCode) (1638/3010 = already-present/ok)"
|
||||
} else { Log "VC++ redist not staged - skipping (usually already present)" }
|
||||
|
||||
# --- 5. DODA app files ------------------------------------------------------
|
||||
$zip = Find-One 'doda_build*.zip'
|
||||
if (-not $zip) {
|
||||
Log "ERROR: doda_build*.zip not found in $stagingRoot - cannot place DODA app"
|
||||
} else {
|
||||
New-Item -ItemType Directory -Path $installDir -Force -EA SilentlyContinue | Out-Null
|
||||
Log "Extracting $($zip.Name) -> $installDir"
|
||||
try { Expand-Archive -LiteralPath $zip.FullName -DestinationPath $installDir -Force -EA Stop }
|
||||
catch { Log "ERROR: extract failed - $_" }
|
||||
}
|
||||
|
||||
# --- 6. .settings (the NSIS installer wrote this; the zip does NOT contain it) ---
|
||||
$settings = Join-Path $installDir '.settings'
|
||||
$settingsBody = @'
|
||||
WORKING_DIR=$TMP
|
||||
PYTHONEXE=C:\Python27\python.exe
|
||||
OPTIONS_FILE="$WORKING_DIR/dvt_run_options.py"
|
||||
RPT_GUI=$PYTHONEXE $AFDA_INSTALL_DIR/dvtRptGui.py -file=$OPTIONS_FILE
|
||||
OUTPUT_PROCESSOR=$PYTHONEXE $AFDA_INSTALL_DIR/processOutput.py $OPTIONS_FILE
|
||||
PS2PDF="C:\Program Files\gs\gs9.27\bin\gswin64c.exe" -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=$$out $$in
|
||||
'@
|
||||
if (Test-Path $installDir) {
|
||||
Set-Content -Path $settings -Value $settingsBody -Encoding ascii -Force
|
||||
Log "Wrote $settings"
|
||||
}
|
||||
|
||||
# --- 7. PreProcess dir (MergeFiles GetDoDAFolder reads it) ------------------
|
||||
$preProcess = Join-Path $installDir 'PreProcess'
|
||||
if (-not (Test-Path $preProcess)) { New-Item -ItemType Directory -Path $preProcess -Force | Out-Null; Log "Created $preProcess" }
|
||||
|
||||
# --- 8. Env: AFDA_INSTALL_DIR + Java on PATH (machine scope) ----------------
|
||||
[Environment]::SetEnvironmentVariable('AFDA_INSTALL_DIR', $installDir, 'Machine')
|
||||
Log "Set AFDA_INSTALL_DIR=$installDir (Machine)"
|
||||
if ($jdk) {
|
||||
$machPath = [Environment]::GetEnvironmentVariable('PATH','Machine')
|
||||
foreach ($add in @((Join-Path $jdk.FullName 'bin'), (Join-Path $jdk.FullName 'jre\bin'))) {
|
||||
if (Test-Path $add) {
|
||||
$parts = $machPath -split ';'
|
||||
if ($parts -notcontains $add) { $machPath = "$machPath;$add"; Log "PATH += $add" }
|
||||
}
|
||||
}
|
||||
[Environment]::SetEnvironmentVariable('PATH', $machPath, 'Machine')
|
||||
}
|
||||
|
||||
# --- 9. Precompile .py -> .pyc as admin (so the locked-down operator never
|
||||
# needs write access to compile bytecode at runtime) ------------------
|
||||
if ((Test-Path $python27) -and (Test-Path $installDir)) {
|
||||
Log "Precompiling DODA .py with $python27"
|
||||
$p = Start-Process $python27 -Wait -PassThru -ArgumentList @('-m','compileall', "`"$installDir`"") -WindowStyle Hidden
|
||||
Log " compileall exit $($p.ExitCode)"
|
||||
}
|
||||
|
||||
# --- 10. ACLs: Users + Authenticated Users Full on C:\Apps\DODA so the
|
||||
# locked-down operator can run DODA (writes .pyc/PreProcess/output) ---
|
||||
if (Test-Path $installDir) {
|
||||
foreach ($sid in '*S-1-5-32-545','*S-1-5-11') { # BUILTIN\Users, NT AUTHORITY\Authenticated Users
|
||||
& icacls $installDir /grant "${sid}:(OI)(CI)F" /T /C 2>&1 | Out-Null
|
||||
}
|
||||
Log "Granted Users + Authenticated Users Full on $installDir (recursive)"
|
||||
}
|
||||
|
||||
# --- Verify -----------------------------------------------------------------
|
||||
$ok = (Test-Path (Join-Path $installDir 'DovetailAnalysis.exe')) -and (Test-Path $python27) -and (Test-Path $gsExe) -and $jdk
|
||||
if ($ok) { Log "==== DODA install OK (DovetailAnalysis + Python27 + Ghostscript + OpenJDK present) ===="; exit 0 }
|
||||
else {
|
||||
Log "==== DODA install INCOMPLETE - DovetailAnalysis=$([bool](Test-Path (Join-Path $installDir 'DovetailAnalysis.exe'))) Python=$([bool](Test-Path $python27)) GS=$([bool](Test-Path $gsExe)) JDK=$([bool]$jdk) ===="
|
||||
exit 1
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user