# Install-DODA.ps1 - full unattended DODA install (runtimes + app + config + ACLs). # # 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' $python27 = 'C:\Python27\python.exe' $gsExe = 'C:\Program Files\gs\gs9.27\bin\gswin64c.exe' $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 } Log "==== DODA install on $env:COMPUTERNAME (staging=$stagingRoot) ====" # --- 1. Python 2.7 ---------------------------------------------------------- if (Test-Path $python27) { Log "Python 2.7 already present - skip" } else { $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 }