diff --git a/playbook/shopfloor-setup/gea-shopfloor-cmm/Install-DODA.ps1 b/playbook/shopfloor-setup/gea-shopfloor-cmm/Install-DODA.ps1 index f3f9fcb..95d1bf1 100644 --- a/playbook/shopfloor-setup/gea-shopfloor-cmm/Install-DODA.ps1 +++ b/playbook/shopfloor-setup/gea-shopfloor-cmm/Install-DODA.ps1 @@ -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 }