<# .SYNOPSIS Remote installer for Dashboard and Lobby Display kiosk applications. .DESCRIPTION Deploys GE Aerospace Dashboard or Lobby Display kiosk installers to remote PCs via WinRM. Pushes the installer file and runs it silently. .PARAMETER ComputerName Single computer name, IP address, or array of computers to target. .PARAMETER ComputerListFile Path to text file containing computer names/IPs (one per line). .PARAMETER App Which application to install: Dashboard or LobbyDisplay .PARAMETER InstallerPath Path to the installer .exe file. If not specified, looks in the script directory. .PARAMETER Credential PSCredential for remote authentication. Prompts if not provided. .PARAMETER Uninstall Uninstall the application instead of installing. .EXAMPLE # Install Dashboard on a single PC .\Install-KioskApp.ps1 -ComputerName "PC001" -App Dashboard .EXAMPLE # Install Lobby Display on multiple PCs from file .\Install-KioskApp.ps1 -ComputerListFile ".\lobby-pcs.txt" -App LobbyDisplay .EXAMPLE # Uninstall Dashboard from a PC .\Install-KioskApp.ps1 -ComputerName "PC001" -App Dashboard -Uninstall .NOTES Author: Shop Floor Tools Requires: PowerShell 5.1+, WinRM enabled on targets, Admin credentials #> [CmdletBinding()] param( [Parameter(Position=0)] [string[]]$ComputerName, [Parameter()] [string]$ComputerListFile, [Parameter(Mandatory=$true)] [ValidateSet('Dashboard', 'LobbyDisplay')] [string]$App, [Parameter()] [string]$InstallerPath, [Parameter()] [PSCredential]$Credential, [Parameter()] [string]$DnsSuffix = "logon.ds.ge.com", [Parameter()] [switch]$Uninstall ) # ============================================================================= # Configuration # ============================================================================= $AppConfig = @{ 'Dashboard' = @{ InstallerName = 'GEAerospaceDashboardSetup.exe' AppName = 'GE Aerospace Dashboard' UninstallGuid = '{9D9EEE25-4D24-422D-98AF-2ADEDA4745ED}' } 'LobbyDisplay' = @{ InstallerName = 'GEAerospaceLobbyDisplaySetup.exe' AppName = 'GE Aerospace Lobby Display' UninstallGuid = '{42FFB952-0B72-493F-8869-D957344CA305}' } } $config = $AppConfig[$App] # ============================================================================= # Helper Functions # ============================================================================= function Write-Log { param([string]$Message, [string]$Level = "INFO") $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $color = switch ($Level) { "ERROR" { "Red" } "WARNING" { "Yellow" } "SUCCESS" { "Green" } "TASK" { "Cyan" } default { "White" } } Write-Host "[$timestamp] [$Level] $Message" -ForegroundColor $color } # ============================================================================= # Main Execution # ============================================================================= Write-Host "" Write-Host ("=" * 70) -ForegroundColor Cyan Write-Host " Kiosk App Installer - $($config.AppName)" -ForegroundColor Cyan Write-Host ("=" * 70) -ForegroundColor Cyan Write-Host "" # Get credentials if (-not $Credential) { Write-Log "Enter credentials for remote PCs:" -Level "INFO" $Credential = Get-Credential -Message "Enter admin credentials for remote PCs" if (-not $Credential) { Write-Log "Credentials required. Exiting." -Level "ERROR" exit 1 } } # Build computer list $computers = @() if ($ComputerListFile) { if (Test-Path $ComputerListFile) { $computers = Get-Content $ComputerListFile | Where-Object { $_.Trim() -and -not $_.StartsWith("#") } } else { Write-Log "Computer list file not found: $ComputerListFile" -Level "ERROR" exit 1 } } elseif ($ComputerName) { $computers = $ComputerName } else { Write-Log "No computers specified. Use -ComputerName or -ComputerListFile" -Level "ERROR" exit 1 } if ($computers.Count -eq 0) { Write-Log "No computers to process." -Level "ERROR" exit 1 } Write-Log "Target computers: $($computers.Count)" -Level "INFO" Write-Log "Action: $(if ($Uninstall) { 'Uninstall' } else { 'Install' })" -Level "TASK" Write-Host "" # Find installer if not specified (for install only) if (-not $Uninstall) { if (-not $InstallerPath) { $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $InstallerPath = Join-Path $scriptDir $config.InstallerName } if (-not (Test-Path $InstallerPath)) { Write-Log "Installer not found: $InstallerPath" -Level "ERROR" Write-Log "Please specify -InstallerPath or place $($config.InstallerName) in the script directory" -Level "ERROR" exit 1 } Write-Log "Installer: $InstallerPath" -Level "INFO" } # Build FQDNs $targetFQDNs = $computers | ForEach-Object { if ($_ -like "*.*") { $_ } else { "$_.$DnsSuffix" } } # Create session options $sessionOption = New-PSSessionOption -OpenTimeout 30000 -OperationTimeout 300000 -NoMachineProfile $successCount = 0 $failCount = 0 foreach ($fqdn in $targetFQDNs) { Write-Host "" Write-Log "Processing: $fqdn" -Level "TASK" try { # Create session $session = New-PSSession -ComputerName $fqdn -Credential $Credential -SessionOption $sessionOption -Authentication Negotiate -ErrorAction Stop if ($Uninstall) { # Uninstall Write-Log " Uninstalling $($config.AppName)..." -Level "INFO" $result = Invoke-Command -Session $session -ScriptBlock { param($guid, $appName) $result = @{ Success = $false; Output = ""; Error = $null } # Find uninstaller $uninstallPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$guid`_is1" if (-not (Test-Path $uninstallPath)) { $uninstallPath = "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\$guid`_is1" } if (Test-Path $uninstallPath) { $uninstallString = (Get-ItemProperty $uninstallPath).UninstallString if ($uninstallString) { # Run uninstaller silently $uninstallExe = $uninstallString -replace '"', '' $proc = Start-Process -FilePath $uninstallExe -ArgumentList "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART" -Wait -PassThru -WindowStyle Hidden $result.Success = ($proc.ExitCode -eq 0) $result.Output = "Exit code: $($proc.ExitCode)" } else { $result.Error = "No uninstall string found" } } else { $result.Error = "$appName not found in registry" } return $result } -ArgumentList $config.UninstallGuid, $config.AppName } else { # Install $remoteTempPath = "C:\Windows\Temp\$($config.InstallerName)" Write-Log " Pushing installer to remote PC..." -Level "INFO" Copy-Item -Path $InstallerPath -Destination $remoteTempPath -ToSession $session -Force -ErrorAction Stop Write-Log " Running installer silently..." -Level "INFO" $result = Invoke-Command -Session $session -ScriptBlock { param($installerPath) $result = @{ Success = $false; Output = ""; Error = $null } try { $proc = Start-Process -FilePath $installerPath -ArgumentList "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART" -Wait -PassThru -WindowStyle Hidden $result.Success = ($proc.ExitCode -eq 0) $result.Output = "Exit code: $($proc.ExitCode)" # Clean up installer Remove-Item $installerPath -Force -ErrorAction SilentlyContinue } catch { $result.Error = $_.Exception.Message } return $result } -ArgumentList $remoteTempPath } # Close session Remove-PSSession $session -ErrorAction SilentlyContinue # Process result if ($result.Success) { Write-Log "[OK] $fqdn - $($result.Output)" -Level "SUCCESS" $successCount++ } else { $errorMsg = if ($result.Error) { $result.Error } else { $result.Output } Write-Log "[FAIL] $fqdn - $errorMsg" -Level "ERROR" $failCount++ } } catch { Write-Log "[FAIL] ${fqdn}: $($_.Exception.Message)" -Level "ERROR" $failCount++ if ($session) { Remove-PSSession $session -ErrorAction SilentlyContinue } } } # Summary Write-Host "" Write-Host ("=" * 70) -ForegroundColor Cyan Write-Host " SUMMARY" -ForegroundColor Cyan Write-Host ("=" * 70) -ForegroundColor Cyan Write-Host " App: $($config.AppName)" -ForegroundColor White Write-Host " Action: $(if ($Uninstall) { 'Uninstall' } else { 'Install' })" -ForegroundColor White Write-Host " Successful: $successCount" -ForegroundColor Green Write-Host " Failed: $failCount" -ForegroundColor $(if ($failCount -gt 0) { "Red" } else { "White" }) Write-Host ("=" * 70) -ForegroundColor Cyan