diff --git a/playbook/shopfloor-setup/Run-ShopfloorSetup.ps1 b/playbook/shopfloor-setup/Run-ShopfloorSetup.ps1 index ab0a945..d380230 100644 --- a/playbook/shopfloor-setup/Run-ShopfloorSetup.ps1 +++ b/playbook/shopfloor-setup/Run-ShopfloorSetup.ps1 @@ -61,7 +61,13 @@ if (-not $pcType) { exit 0 } -Write-Host "Shopfloor PC Type: $pcType" +$subtypeFile = Join-Path $enrollDir "pc-subtype.txt" +$pcSubType = '' +if (Test-Path $subtypeFile) { + $pcSubType = (Get-Content $subtypeFile -First 1).Trim() +} + +Write-Host "Shopfloor PC Type: $pcType$(if ($pcSubType) { " / $pcSubType" })" # Scripts to skip in the alphabetical baseline loop. Each is either run # explicitly in the finalization phase below, or invoked internally by @@ -167,8 +173,9 @@ foreach ($tool in @('sync_intune.bat', 'Configure-PC.bat')) { } } -# Standard PCs get the UDC/eDNC machine number helper -if ($pcType -eq "Standard") { +# Standard-Machine PCs get the UDC/eDNC machine number helper. Timeclock +# PCs do not use a machine number, so the helper has nothing to do there. +if ($pcType -eq "Standard" -and $pcSubType -ne "Timeclock") { foreach ($helper in @("Set-MachineNumber.bat", "Set-MachineNumber.ps1")) { $src = Join-Path $setupDir "Standard\$helper" if (Test-Path $src) { @@ -247,6 +254,20 @@ if (Test-Path -LiteralPath $registerAcrobat) { Write-Host "Register-AcrobatEnforce.ps1 not found (optional) - skipping" } +# Standard-Machine gets a machine-apps enforcer (UDC, eDNC, NTLARS) that +# replaced the Intune DSC path (DSC has no sub-type awareness and was +# pushing these to Timeclocks). Timeclocks skip this registration. +if ($pcType -eq "Standard" -and $pcSubType -eq "Machine") { + $registerMachine = Join-Path $setupDir "Standard\Register-MachineEnforce.ps1" + if (Test-Path -LiteralPath $registerMachine) { + Write-Host "" + Write-Host "=== Registering Machine-apps enforcer ===" + try { & $registerMachine } catch { Write-Warning "Machine enforce registration failed: $_" } + } else { + Write-Host "Register-MachineEnforce.ps1 not found (optional) - skipping" + } +} + # --- Run enrollment (PPKG install) --- # Enrollment is the LAST thing we do. Install-ProvisioningPackage triggers # an immediate reboot -- everything after this call is unlikely to execute. diff --git a/playbook/shopfloor-setup/Standard/Machine-Enforce.ps1 b/playbook/shopfloor-setup/Standard/Machine-Enforce.ps1 new file mode 100644 index 0000000..f4cb23b --- /dev/null +++ b/playbook/shopfloor-setup/Standard/Machine-Enforce.ps1 @@ -0,0 +1,145 @@ +# Machine-Enforce.ps1 - On-logon enforcer for Standard-Machine shopfloor apps +# (UDC, eDNC, NTLARS, future additions). +# +# Runs under a SYSTEM scheduled task triggered at user logon on Standard-Machine +# PCs only (Timeclock PCs skip registration). Mirrors CMM-Enforce / Acrobat- +# Enforce: mounts the SFLD share, reads machineapps-manifest.json from the +# share, hands off to Install-FromManifest.ps1 which installs anything whose +# detection fails. +# +# Why this exists: Intune DSC's main-category YAML used to handle UDC/eDNC/ +# NTLARS enforcement, but DSC has no pc-subtype awareness so Timeclocks in +# category=main got Machine-only apps like UDC pushed to them. These apps +# were pulled from the DSC YAML; this enforcer replaces their drift-correction +# behavior while leaving initial install to the imaging preinstall phase. +# +# Graceful degradation mirrors CMM-Enforce: +# - SFLD creds missing (Azure DSC has not run yet) -> log + exit 0 +# - Share unreachable (network, VPN) -> log + exit 0 +# - Install failure on any one app -> log + continue with next +# +# Never returns non-zero to the task scheduler; failures show up in the log. + +$ErrorActionPreference = 'Continue' + +$installRoot = 'C:\Program Files\GE\MachineApps' +$libPath = Join-Path $installRoot 'lib\Install-FromManifest.ps1' +$logDir = 'C:\Logs\MachineApps' +$logFile = Join-Path $logDir ('enforce-{0}.log' -f (Get-Date -Format 'yyyyMMdd')) +# Use a drive letter that does not clash with CMM-Enforce (S:) or +# Acrobat-Enforce (T:) so enforcers can run concurrently at logon. +$driveLetter = 'U:' + +if (-not (Test-Path $logDir)) { + New-Item -Path $logDir -ItemType Directory -Force | Out-Null +} + +function Write-EnforceLog { + param([string]$Message, [string]$Level = 'INFO') + $line = "[{0}] [{1}] {2}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Level, $Message + Write-Host $line + Add-Content -Path $logFile -Value $line -ErrorAction SilentlyContinue +} + +Write-EnforceLog "================================================================" +Write-EnforceLog "=== Machine-Enforce session start (PID $PID, user $env:USERNAME) ===" +Write-EnforceLog "================================================================" + +# --- Gate: this enforcer is Standard-Machine only. --- +# Belt-and-suspenders: registration is already Machine-only, but double-check +# so a manual copy to a Timeclock PC would no-op instead of chewing through +# the manifest on a device that shouldn't run it. +$subtypeFile = 'C:\Enrollment\pc-subtype.txt' +if (Test-Path $subtypeFile) { + $sub = (Get-Content $subtypeFile -First 1 -ErrorAction SilentlyContinue).Trim() + if ($sub -and $sub -ne 'Machine') { + Write-EnforceLog "pc-subtype is '$sub' (not Machine) - exiting" + exit 0 + } +} + +# --- Load site-config for machineappsSharePath --- +$getProfileScript = 'C:\Enrollment\shopfloor-setup\Shopfloor\lib\Get-PCProfile.ps1' +if (-not (Test-Path $getProfileScript)) { + Write-EnforceLog "Get-PCProfile.ps1 not found at $getProfileScript - cannot locate share" "ERROR" + exit 0 +} +. $getProfileScript + +if (-not $pcProfile -or -not $pcProfile.machineappsSharePath) { + Write-EnforceLog "No machineappsSharePath in profile - nothing to enforce" "WARN" + exit 0 +} + +$sharePath = $pcProfile.machineappsSharePath +Write-EnforceLog "Share: $sharePath" + +# --- SFLD credential lookup (written by Azure DSC post-PPKG). Bail +# gracefully if the creds haven't been provisioned yet. --- +function Get-SFLDCredential { + param([string]$ServerName) + $basePath = 'HKLM:\SOFTWARE\GE\SFLD\Credentials' + if (-not (Test-Path $basePath)) { return $null } + + foreach ($entry in Get-ChildItem -Path $basePath -ErrorAction SilentlyContinue) { + $props = Get-ItemProperty -Path $entry.PSPath -ErrorAction SilentlyContinue + if (-not $props -or -not $props.TargetHost) { continue } + if ($props.TargetHost -eq $ServerName -or + $props.TargetHost -like "$ServerName.*" -or + $ServerName -like "$($props.TargetHost).*") { + return @{ + Username = $props.Username + Password = $props.Password + TargetHost = $props.TargetHost + KeyName = $entry.PSChildName + } + } + } + return $null +} + +$serverName = ($sharePath -replace '^\\\\', '') -split '\\' | Select-Object -First 1 +$cred = Get-SFLDCredential -ServerName $serverName + +if (-not $cred -or -not $cred.Username -or -not $cred.Password) { + Write-EnforceLog "No SFLD credential for $serverName yet (Azure DSC has not provisioned it) - will retry at next logon" + exit 0 +} + +Write-EnforceLog "Credential: $($cred.KeyName) (user: $($cred.Username))" + +# --- Mount the share --- +net use $driveLetter /delete /y 2>$null | Out-Null + +$netResult = & net use $driveLetter $sharePath /user:$($cred.Username) $($cred.Password) /persistent:no 2>&1 +if ($LASTEXITCODE -ne 0) { + Write-EnforceLog "net use failed (exit $LASTEXITCODE): $netResult" "WARN" + Write-EnforceLog "Share unreachable - probably off-network. Will retry at next logon." + exit 0 +} +Write-EnforceLog "Mounted $sharePath as $driveLetter" + +try { + $manifestOnShare = Join-Path $driveLetter 'machineapps-manifest.json' + if (-not (Test-Path $manifestOnShare)) { + Write-EnforceLog "machineapps-manifest.json not found on share - nothing to enforce" "WARN" + return + } + + if (-not (Test-Path $libPath)) { + Write-EnforceLog "Install-FromManifest.ps1 not found at $libPath" "ERROR" + return + } + + Write-EnforceLog "Handing off to Install-FromManifest.ps1 (InstallerRoot=$driveLetter)" + & $libPath -ManifestPath $manifestOnShare -InstallerRoot $driveLetter -LogFile $logFile + $rc = $LASTEXITCODE + Write-EnforceLog "Install-FromManifest returned $rc" +} +finally { + net use $driveLetter /delete /y 2>$null | Out-Null + Write-EnforceLog "Unmounted $driveLetter" + Write-EnforceLog "=== Machine-Enforce session end ===" +} + +exit 0 diff --git a/playbook/shopfloor-setup/Standard/Register-MachineEnforce.ps1 b/playbook/shopfloor-setup/Standard/Register-MachineEnforce.ps1 new file mode 100644 index 0000000..2f9cee5 --- /dev/null +++ b/playbook/shopfloor-setup/Standard/Register-MachineEnforce.ps1 @@ -0,0 +1,85 @@ +# Register-MachineEnforce.ps1 - One-time setup for the Standard-Machine +# logon-enforce scheduled task. Called by Run-ShopfloorSetup.ps1 on +# Standard-Machine PCs only (Timeclocks skip). Idempotent: re-running +# refreshes staged scripts and re-registers the task. +# +# Parallel to CMM\09-Setup-CMM.ps1 steps 3-4 (stage Install-FromManifest + +# Machine-Enforce, register the task) with no imaging-time install step - +# initial UDC/eDNC/NTLARS install is already handled by the preinstall +# phase on the PXE server. + +$ErrorActionPreference = 'Continue' + +$installRoot = 'C:\Program Files\GE\MachineApps' +$runtimeLib = Join-Path $installRoot 'lib\Install-FromManifest.ps1' +$runtimeEnforce = Join-Path $installRoot 'Machine-Enforce.ps1' +$logDir = 'C:\Logs\MachineApps' +$setupLog = Join-Path $logDir 'setup.log' + +$sourceLib = Join-Path $PSScriptRoot 'lib\Install-FromManifest.ps1' +$sourceEnforce = Join-Path $PSScriptRoot 'Machine-Enforce.ps1' + +if (-not (Test-Path $logDir)) { New-Item -Path $logDir -ItemType Directory -Force | Out-Null } +if (-not (Test-Path $installRoot)) { New-Item -Path $installRoot -ItemType Directory -Force | Out-Null } +if (-not (Test-Path (Join-Path $installRoot 'lib'))) { + New-Item -Path (Join-Path $installRoot 'lib') -ItemType Directory -Force | Out-Null +} + +function Write-SetupLog { + param([string]$Message, [string]$Level = 'INFO') + $line = "[{0}] [{1}] {2}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Level, $Message + Write-Host $line + Add-Content -Path $setupLog -Value $line -ErrorAction SilentlyContinue +} + +Write-SetupLog "=== Register-MachineEnforce start ===" + +foreach ($pair in @( + @{ Src = $sourceLib; Dst = $runtimeLib }, + @{ Src = $sourceEnforce; Dst = $runtimeEnforce } +)) { + if (-not (Test-Path $pair.Src)) { + Write-SetupLog "Source not found: $($pair.Src) - cannot stage" "ERROR" + continue + } + Copy-Item -Path $pair.Src -Destination $pair.Dst -Force + Write-SetupLog "Staged $($pair.Src) -> $($pair.Dst)" +} + +$taskName = 'GE Shopfloor Machine Apps Enforce' +$existing = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue +if ($existing) { + Write-SetupLog "Removing existing scheduled task '$taskName'" + Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue +} + +Write-SetupLog "Registering scheduled task '$taskName' (logon trigger, SYSTEM)" +try { + $action = New-ScheduledTaskAction ` + -Execute 'powershell.exe' ` + -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$runtimeEnforce`"" + + $trigger = New-ScheduledTaskTrigger -AtLogOn + $principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest + # ExecutionTimeLimit 1 hour; UDC/eDNC/NTLARS combined shouldn't exceed that. + $settings = New-ScheduledTaskSettingsSet ` + -AllowStartIfOnBatteries ` + -DontStopIfGoingOnBatteries ` + -StartWhenAvailable ` + -ExecutionTimeLimit (New-TimeSpan -Hours 1) ` + -MultipleInstances IgnoreNew + + Register-ScheduledTask ` + -TaskName $taskName ` + -Action $action ` + -Trigger $trigger ` + -Principal $principal ` + -Settings $settings ` + -Description 'GE Shopfloor Machine: enforce UDC/eDNC/NTLARS version against tsgwp00525 SFLD share on user logon' | Out-Null + + Write-SetupLog "Scheduled task registered" +} catch { + Write-SetupLog "Failed to register scheduled task: $_" "ERROR" +} + +Write-SetupLog "=== Register-MachineEnforce end ===" diff --git a/playbook/shopfloor-setup/Standard/lib/Install-FromManifest.ps1 b/playbook/shopfloor-setup/Standard/lib/Install-FromManifest.ps1 new file mode 100644 index 0000000..256c530 --- /dev/null +++ b/playbook/shopfloor-setup/Standard/lib/Install-FromManifest.ps1 @@ -0,0 +1,258 @@ +# 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= +# +# 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 diff --git a/playbook/shopfloor-setup/Standard/machineapps-manifest.template.json b/playbook/shopfloor-setup/Standard/machineapps-manifest.template.json new file mode 100644 index 0000000..178b28a --- /dev/null +++ b/playbook/shopfloor-setup/Standard/machineapps-manifest.template.json @@ -0,0 +1,36 @@ +{ + "Version": "1.0", + "_comment": "Standard-Machine shopfloor app enforcement manifest. This is the TEMPLATE kept in the repo; the authoritative copy lives on the SFLD share at \\\\tsgwp00525.wjs.geaerospace.net\\shared\\dt\\shopfloor\\main\\machineapps\\machineapps-manifest.json. Machine-Enforce.ps1 reads the share copy on every user logon via the 'GE Shopfloor Machine Apps Enforce' scheduled task (registered by Register-MachineEnforce.ps1 at imaging time, Standard-Machine only). Initial install still happens during the preinstall phase on the imaging PXE server; this enforcer is the ongoing drift-correction side. On a freshly-imaged PC detection passes immediately and the enforcer no-ops. Replaces DSC-based enforcement of these apps which was pulled because Intune DSC has no pc-subtype awareness and was pushing UDC/eDNC/NTLARS to Standard-Timeclock PCs.", + "Applications": [ + { + "_comment": "UDC. Install args follow the preinstall.json pattern: Site name in quotes, then machine number placeholder (Configure-PC.ps1 re-runs UDC_Setup with the real machine number after imaging, so the placeholder is overwritten in HKLM at that point). KillAfterDetection is only meaningful during preinstall; the enforcer lets Install-FromManifest wait for the process normally.", + "Name": "UDC", + "Installer": "UDC_Setup.exe", + "Type": "EXE", + "InstallArgs": "\"West Jefferson\" 9999", + "DetectionMethod": "Registry", + "DetectionPath": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\UDC", + "DetectionName": "DisplayVersion", + "DetectionValue": "REPLACE_WITH_PINNED_UDC_VERSION" + }, + { + "_comment": "eDNC 6.4.3. SITESELECTED is the property that encodes the site (was a recurring bug in early shopfloor-setup scripts that omitted it). Adjust to your site's value if not West Jefferson.", + "Name": "eDNC", + "Installer": "eDNC-6.4.3.msi", + "Type": "MSI", + "InstallArgs": "/qn /norestart ALLUSERS=1 REBOOT=ReallySuppress SITESELECTED=\"West Jefferson\"", + "DetectionMethod": "Registry", + "DetectionPath": "HKLM:\\SOFTWARE\\WOW6432Node\\GE Aircraft Engines\\DNC\\General", + "DetectionName": "MachineNo" + }, + { + "_comment": "NTLARS. Replace installer filename + args once we know what the vendor ships. Registry detection path guessed from the Defect_Tracker pattern; verify with a real install before relying on it.", + "Name": "NTLARS", + "Installer": "NTLARS_Setup.exe", + "Type": "EXE", + "InstallArgs": "/S", + "DetectionMethod": "File", + "DetectionPath": "C:\\Program Files (x86)\\Dnc\\Common\\NTLARS.exe" + } + ] +} diff --git a/playbook/shopfloor-setup/site-config.json b/playbook/shopfloor-setup/site-config.json index eda037e..f3e5146 100644 --- a/playbook/shopfloor-setup/site-config.json +++ b/playbook/shopfloor-setup/site-config.json @@ -78,6 +78,7 @@ }, "Standard-Machine": { + "machineappsSharePath": "\\\\tsgwp00525.wjs.geaerospace.net\\shared\\dt\\shopfloor\\main\\machineapps", "startupItems": [ { "label": "WJ Shopfloor", "type": "existing", "sourceLnk": "WJ Shopfloor.lnk" }, { "label": "Plant Apps", "type": "url", "urlKey": "plantApps" },