Documentation: - Add ShopDB-API.md with full API reference (all GET/POST endpoints) - Add detailed docs for Update-ShopfloorPCs-Remote, Invoke-RemoteMaintenance, Update-PC-CompleteAsset - Add DATA_COLLECTION_PARITY.md comparing local vs remote data collection - Add HTML versions of all documentation with styled code blocks - Document software deployment mechanism and how to add new apps - Document deprecated scripts (Invoke-RemoteAssetCollection, Install-KioskApp) Script Updates: - Update deployment source paths to network share (tsgwp00525.wjs.geaerospace.net) - InstallDashboard: \\...\scripts\Dashboard\GEAerospaceDashboardSetup.exe - InstallLobbyDisplay: \\...\scripts\LobbyDisplay\GEAerospaceLobbyDisplaySetup.exe - UpdateEMxAuthToken: \\...\scripts\eMx\eMxInfo.txt - DeployUDCWebServerConfig: \\...\scripts\UDC\udc_webserver_settings.json - Update machine network detection to include 100.0.0.* for CMM cases - Rename PC Type #9 from "Part Marker" to "Inspection" Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1588 lines
58 KiB
PowerShell
1588 lines
58 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Remote maintenance toolkit for shopfloor PCs via WinRM.
|
|
|
|
.DESCRIPTION
|
|
Executes maintenance tasks on remote shopfloor PCs using WinRM.
|
|
Supports system repair, disk optimization, cleanup, and reboots.
|
|
Can target PCs by name, file list, PC type, business unit, or all.
|
|
|
|
.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 All
|
|
Target all shopfloor PCs from ShopDB database.
|
|
|
|
.PARAMETER PcType
|
|
Target PCs by type (e.g., Dashboard, Lobby Display, CMM, Shopfloor).
|
|
Valid values: Standard, Engineer, Shopfloor, CMM, Wax / Trace, Keyence,
|
|
Genspect, Heat Treat, Inspection, Dashboard, Lobby Display, Uncategorized
|
|
|
|
.PARAMETER BusinessUnit
|
|
Target PCs by business unit (e.g., Blisk, HPT, Spools).
|
|
Valid values: TBD, Blisk, HPT, Spools, Inspection, Venture, Turn/Burn, DT
|
|
|
|
.PARAMETER Task
|
|
Maintenance task to execute. Available tasks:
|
|
|
|
REPAIR:
|
|
- DISM : Run DISM /Online /Cleanup-Image /RestoreHealth
|
|
- SFC : Run SFC /scannow (System File Checker)
|
|
|
|
OPTIMIZATION:
|
|
- OptimizeDisk : TRIM for SSD, Defrag for HDD
|
|
- DiskCleanup : Windows Disk Cleanup (temp files, updates)
|
|
- ClearUpdateCache : Clear Windows Update cache (fixes stuck updates)
|
|
- ClearBrowserCache: Clear Chrome/Edge cache files
|
|
|
|
SERVICES:
|
|
- RestartSpooler : Restart Print Spooler service
|
|
- FlushDNS : Clear DNS resolver cache
|
|
- RestartWinRM : Restart WinRM service
|
|
|
|
TIME/DATE:
|
|
- SetTimezone : Set timezone to Eastern Standard Time
|
|
- SyncTime : Force time sync with domain controller
|
|
|
|
DNC:
|
|
- UpdateEMxAuthToken : Update eMx auth token (eMxInfo.txt) from network share (backs up old file first)
|
|
- DeployUDCWebServerConfig : Deploy UDC web server settings to PCs with UDC installed
|
|
|
|
SYSTEM:
|
|
- Reboot : Restart the computer (30 second delay)
|
|
|
|
SOFTWARE DEPLOYMENT:
|
|
- InstallDashboard : Install GE Aerospace Dashboard kiosk app
|
|
- InstallLobbyDisplay : Install GE Aerospace Lobby Display kiosk app
|
|
- UninstallDashboard : Uninstall GE Aerospace Dashboard
|
|
- UninstallLobbyDisplay : Uninstall GE Aerospace Lobby Display
|
|
|
|
.PARAMETER Credential
|
|
PSCredential for remote authentication. Prompts if not provided.
|
|
|
|
.PARAMETER ApiUrl
|
|
ShopDB API URL for database updates.
|
|
|
|
.PARAMETER ThrottleLimit
|
|
Maximum concurrent remote sessions (default: 5).
|
|
|
|
.EXAMPLE
|
|
# Run DISM on a single PC
|
|
.\Invoke-RemoteMaintenance.ps1 -ComputerName "G1ZTNCX3ESF" -Task DISM
|
|
|
|
.EXAMPLE
|
|
# Optimize disks on multiple PCs
|
|
.\Invoke-RemoteMaintenance.ps1 -ComputerName "PC01","PC02" -Task OptimizeDisk
|
|
|
|
.EXAMPLE
|
|
# Run disk cleanup on all shopfloor PCs
|
|
.\Invoke-RemoteMaintenance.ps1 -All -Task DiskCleanup
|
|
|
|
.EXAMPLE
|
|
# Reboot all Dashboard PCs
|
|
.\Invoke-RemoteMaintenance.ps1 -PcType Dashboard -Task Reboot
|
|
|
|
.EXAMPLE
|
|
# Reboot all Lobby Display PCs
|
|
.\Invoke-RemoteMaintenance.ps1 -PcType "Lobby Display" -Task Reboot
|
|
|
|
.EXAMPLE
|
|
# Flush DNS on all PCs in Blisk business unit
|
|
.\Invoke-RemoteMaintenance.ps1 -BusinessUnit Blisk -Task FlushDNS
|
|
|
|
.EXAMPLE
|
|
# Clear browser cache on all CMM PCs
|
|
.\Invoke-RemoteMaintenance.ps1 -PcType CMM -Task ClearBrowserCache
|
|
|
|
.EXAMPLE
|
|
# Install Dashboard on specific PCs
|
|
.\Invoke-RemoteMaintenance.ps1 -ComputerName "PC001","PC002" -Task InstallDashboard
|
|
|
|
.EXAMPLE
|
|
# Install Lobby Display from a list file
|
|
.\Invoke-RemoteMaintenance.ps1 -ComputerListFile ".\lobby-pcs.txt" -Task InstallLobbyDisplay
|
|
|
|
.EXAMPLE
|
|
# Uninstall Dashboard from a PC
|
|
.\Invoke-RemoteMaintenance.ps1 -ComputerName "PC001" -Task UninstallDashboard
|
|
|
|
.NOTES
|
|
Author: Shop Floor Tools
|
|
Date: 2025-12-26
|
|
Requires: PowerShell 5.1+, WinRM enabled on targets, Admin credentials
|
|
#>
|
|
|
|
[CmdletBinding(DefaultParameterSetName='ByName')]
|
|
param(
|
|
[Parameter(ParameterSetName='ByName', Position=0)]
|
|
[string[]]$ComputerName,
|
|
|
|
[Parameter(ParameterSetName='ByFile')]
|
|
[string]$ComputerListFile,
|
|
|
|
[Parameter(ParameterSetName='All')]
|
|
[switch]$All,
|
|
|
|
[Parameter(ParameterSetName='ByPcType')]
|
|
[ValidateSet('Standard', 'Engineer', 'Shopfloor', 'CMM', 'Wax / Trace', 'Keyence',
|
|
'Genspect', 'Heat Treat', 'Inspection', 'Dashboard', 'Lobby Display', 'Uncategorized')]
|
|
[string]$PcType,
|
|
|
|
[Parameter(ParameterSetName='ByBusinessUnit')]
|
|
[ValidateSet('TBD', 'Blisk', 'HPT', 'Spools', 'Inspection', 'Venture', 'Turn/Burn', 'DT')]
|
|
[string]$BusinessUnit,
|
|
|
|
[Parameter(Mandatory=$true)]
|
|
[ValidateSet(
|
|
'DISM', 'SFC', 'OptimizeDisk', 'DiskCleanup', 'ClearUpdateCache',
|
|
'RestartSpooler', 'FlushDNS', 'ClearBrowserCache', 'RestartWinRM',
|
|
'SetTimezone', 'SyncTime', 'UpdateEMxAuthToken', 'DeployUDCWebServerConfig', 'Reboot',
|
|
'InstallDashboard', 'InstallLobbyDisplay', 'UninstallDashboard', 'UninstallLobbyDisplay'
|
|
)]
|
|
[string]$Task,
|
|
|
|
[Parameter()]
|
|
[PSCredential]$Credential,
|
|
|
|
[Parameter()]
|
|
[string]$ApiUrl = "https://tsgwp00525.rd.ds.ge.com/shopdb/api.asp",
|
|
|
|
[Parameter()]
|
|
[string]$DnsSuffix = "logon.ds.ge.com",
|
|
|
|
[Parameter()]
|
|
[int]$ThrottleLimit = 5
|
|
)
|
|
|
|
# =============================================================================
|
|
# SSL/TLS Configuration
|
|
# =============================================================================
|
|
# Enable all modern TLS versions
|
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls
|
|
|
|
# Bypass SSL certificate validation (for self-signed certs)
|
|
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { param($sender, $cert, $chain, $errors) return $true }
|
|
|
|
# =============================================================================
|
|
# 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
|
|
}
|
|
|
|
function Get-ShopfloorPCsFromApi {
|
|
param(
|
|
[string]$ApiUrl,
|
|
[int]$PcTypeId = 0,
|
|
[int]$BusinessUnitId = 0
|
|
)
|
|
try {
|
|
# Force TLS 1.2 immediately before request
|
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
|
|
$queryParams = "action=getShopfloorPCs"
|
|
if ($PcTypeId -gt 0) {
|
|
$queryParams += "&pctypeid=$PcTypeId"
|
|
}
|
|
if ($BusinessUnitId -gt 0) {
|
|
$queryParams += "&businessunitid=$BusinessUnitId"
|
|
}
|
|
|
|
$fullUrl = "$ApiUrl`?$queryParams"
|
|
|
|
# Use WebClient - more reliable with TLS in script context
|
|
$webClient = New-Object System.Net.WebClient
|
|
$json = $webClient.DownloadString($fullUrl)
|
|
$response = $json | ConvertFrom-Json
|
|
|
|
if ($response.success -and $response.data) {
|
|
return $response.data
|
|
}
|
|
return @()
|
|
} catch {
|
|
Write-Log "Failed to query API: $_" -Level "ERROR"
|
|
return @()
|
|
}
|
|
}
|
|
|
|
# Lookup tables for filtering
|
|
$PcTypeLookup = @{
|
|
'Standard' = 1; 'Engineer' = 2; 'Shopfloor' = 3; 'Uncategorized' = 4;
|
|
'CMM' = 5; 'Wax / Trace' = 6; 'Keyence' = 7; 'Genspect' = 8;
|
|
'Heat Treat' = 9; 'Inspection' = 10; 'Dashboard' = 11; 'Lobby Display' = 12
|
|
}
|
|
|
|
$BusinessUnitLookup = @{
|
|
'TBD' = 1; 'Blisk' = 2; 'HPT' = 3; 'Spools' = 4;
|
|
'Inspection' = 5; 'Venture' = 6; 'Turn/Burn' = 7; 'DT' = 8
|
|
}
|
|
|
|
|
|
# =============================================================================
|
|
# Maintenance Task Scriptblocks
|
|
# =============================================================================
|
|
|
|
$TaskScripts = @{
|
|
|
|
# -------------------------------------------------------------------------
|
|
# DISM - Deployment Image Servicing and Management
|
|
# -------------------------------------------------------------------------
|
|
'DISM' = {
|
|
$result = @{
|
|
Success = $false
|
|
Task = 'DISM'
|
|
Hostname = $env:COMPUTERNAME
|
|
Output = ""
|
|
Error = $null
|
|
StartTime = Get-Date
|
|
Duration = 0
|
|
}
|
|
|
|
try {
|
|
Write-Output "Starting DISM /Online /Cleanup-Image /RestoreHealth..."
|
|
Write-Output "This may take 10-30 minutes..."
|
|
|
|
$dismResult = & dism.exe /Online /Cleanup-Image /RestoreHealth 2>&1
|
|
$result.Output = $dismResult -join "`n"
|
|
$result.ExitCode = $LASTEXITCODE
|
|
$result.Success = ($LASTEXITCODE -eq 0)
|
|
|
|
if ($result.Success) {
|
|
Write-Output "DISM completed successfully."
|
|
} else {
|
|
Write-Output "DISM completed with exit code: $LASTEXITCODE"
|
|
}
|
|
} catch {
|
|
$result.Error = $_.Exception.Message
|
|
}
|
|
|
|
$result.EndTime = Get-Date
|
|
$result.Duration = [math]::Round(((Get-Date) - $result.StartTime).TotalMinutes, 2)
|
|
return $result
|
|
}
|
|
|
|
# -------------------------------------------------------------------------
|
|
# SFC - System File Checker
|
|
# -------------------------------------------------------------------------
|
|
'SFC' = {
|
|
$result = @{
|
|
Success = $false
|
|
Task = 'SFC'
|
|
Hostname = $env:COMPUTERNAME
|
|
Output = ""
|
|
Error = $null
|
|
StartTime = Get-Date
|
|
Duration = 0
|
|
}
|
|
|
|
try {
|
|
Write-Output "Starting SFC /scannow..."
|
|
Write-Output "This may take 10-20 minutes..."
|
|
|
|
$sfcResult = & sfc.exe /scannow 2>&1
|
|
$result.Output = $sfcResult -join "`n"
|
|
$result.ExitCode = $LASTEXITCODE
|
|
|
|
# SFC exit codes: 0 = no issues, 1 = issues found and fixed
|
|
$result.Success = ($LASTEXITCODE -eq 0 -or $LASTEXITCODE -eq 1)
|
|
|
|
# Parse output for summary
|
|
if ($result.Output -match "found corrupt files and successfully repaired") {
|
|
$result.Summary = "Corrupt files found and repaired"
|
|
} elseif ($result.Output -match "did not find any integrity violations") {
|
|
$result.Summary = "No integrity violations found"
|
|
} elseif ($result.Output -match "found corrupt files but was unable to fix") {
|
|
$result.Summary = "Corrupt files found but could not be repaired"
|
|
$result.Success = $false
|
|
} else {
|
|
$result.Summary = "Scan completed"
|
|
}
|
|
|
|
} catch {
|
|
$result.Error = $_.Exception.Message
|
|
}
|
|
|
|
$result.EndTime = Get-Date
|
|
$result.Duration = [math]::Round(((Get-Date) - $result.StartTime).TotalMinutes, 2)
|
|
return $result
|
|
}
|
|
|
|
# -------------------------------------------------------------------------
|
|
# OptimizeDisk - TRIM for SSD, Defrag for HDD
|
|
# -------------------------------------------------------------------------
|
|
'OptimizeDisk' = {
|
|
$result = @{
|
|
Success = $false
|
|
Task = 'OptimizeDisk'
|
|
Hostname = $env:COMPUTERNAME
|
|
Output = ""
|
|
Error = $null
|
|
Drives = @()
|
|
}
|
|
|
|
try {
|
|
# Get all fixed drives
|
|
$volumes = Get-Volume | Where-Object { $_.DriveType -eq 'Fixed' -and $_.DriveLetter }
|
|
|
|
foreach ($vol in $volumes) {
|
|
$driveLetter = $vol.DriveLetter
|
|
$driveResult = @{
|
|
DriveLetter = $driveLetter
|
|
Success = $false
|
|
MediaType = "Unknown"
|
|
Action = ""
|
|
}
|
|
|
|
# Detect if SSD or HDD
|
|
$physicalDisk = Get-PhysicalDisk | Where-Object {
|
|
$diskNum = (Get-Partition -DriveLetter $driveLetter -ErrorAction SilentlyContinue).DiskNumber
|
|
$_.DeviceId -eq $diskNum
|
|
}
|
|
|
|
if ($physicalDisk) {
|
|
$driveResult.MediaType = $physicalDisk.MediaType
|
|
}
|
|
|
|
Write-Output "Optimizing drive ${driveLetter}: ($($driveResult.MediaType))..."
|
|
|
|
try {
|
|
if ($driveResult.MediaType -eq 'SSD') {
|
|
# TRIM for SSD
|
|
Optimize-Volume -DriveLetter $driveLetter -ReTrim -ErrorAction Stop
|
|
$driveResult.Action = "TRIM"
|
|
} else {
|
|
# Defrag for HDD
|
|
Optimize-Volume -DriveLetter $driveLetter -Defrag -ErrorAction Stop
|
|
$driveResult.Action = "Defrag"
|
|
}
|
|
$driveResult.Success = $true
|
|
Write-Output " ${driveLetter}: $($driveResult.Action) completed"
|
|
} catch {
|
|
$driveResult.Error = $_.Exception.Message
|
|
Write-Output " ${driveLetter}: Failed - $($_.Exception.Message)"
|
|
}
|
|
|
|
$result.Drives += $driveResult
|
|
}
|
|
|
|
$result.Success = ($result.Drives | Where-Object { $_.Success }).Count -gt 0
|
|
$result.Output = "Optimized $($result.Drives.Count) drive(s)"
|
|
|
|
} catch {
|
|
$result.Error = $_.Exception.Message
|
|
}
|
|
|
|
return $result
|
|
}
|
|
|
|
# -------------------------------------------------------------------------
|
|
# DiskCleanup - Windows Disk Cleanup
|
|
# -------------------------------------------------------------------------
|
|
'DiskCleanup' = {
|
|
$result = @{
|
|
Success = $false
|
|
Task = 'DiskCleanup'
|
|
Hostname = $env:COMPUTERNAME
|
|
Output = ""
|
|
Error = $null
|
|
SpaceFreed = 0
|
|
}
|
|
|
|
try {
|
|
# Get initial free space
|
|
$initialFree = (Get-PSDrive C).Free
|
|
|
|
Write-Output "Running Disk Cleanup..."
|
|
|
|
# Set cleanup flags in registry for automated cleanup
|
|
$cleanupPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches"
|
|
$categories = @(
|
|
"Temporary Files",
|
|
"Temporary Setup Files",
|
|
"Old ChkDsk Files",
|
|
"Setup Log Files",
|
|
"Windows Update Cleanup",
|
|
"Windows Defender",
|
|
"Thumbnail Cache",
|
|
"Recycle Bin"
|
|
)
|
|
|
|
foreach ($cat in $categories) {
|
|
$catPath = Join-Path $cleanupPath $cat
|
|
if (Test-Path $catPath) {
|
|
Set-ItemProperty -Path $catPath -Name "StateFlags0100" -Value 2 -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
# Run cleanmgr with sagerun
|
|
$cleanupProcess = Start-Process -FilePath "cleanmgr.exe" -ArgumentList "/sagerun:100" -Wait -PassThru -WindowStyle Hidden
|
|
|
|
# Also clear temp folders directly
|
|
$tempPaths = @(
|
|
"$env:TEMP",
|
|
"$env:SystemRoot\Temp",
|
|
"$env:SystemRoot\Prefetch"
|
|
)
|
|
|
|
$filesDeleted = 0
|
|
foreach ($path in $tempPaths) {
|
|
if (Test-Path $path) {
|
|
$files = Get-ChildItem -Path $path -Recurse -Force -ErrorAction SilentlyContinue
|
|
foreach ($file in $files) {
|
|
try {
|
|
Remove-Item $file.FullName -Force -Recurse -ErrorAction SilentlyContinue
|
|
$filesDeleted++
|
|
} catch { }
|
|
}
|
|
}
|
|
}
|
|
|
|
# Calculate space freed
|
|
Start-Sleep -Seconds 2
|
|
$finalFree = (Get-PSDrive C).Free
|
|
$result.SpaceFreed = [math]::Round(($finalFree - $initialFree) / 1GB, 2)
|
|
|
|
$result.Success = $true
|
|
$result.Output = "Cleanup completed. Space freed: $($result.SpaceFreed) GB. Temp files deleted: $filesDeleted"
|
|
Write-Output $result.Output
|
|
|
|
} catch {
|
|
$result.Error = $_.Exception.Message
|
|
}
|
|
|
|
return $result
|
|
}
|
|
|
|
# -------------------------------------------------------------------------
|
|
# ClearUpdateCache - Clear Windows Update cache
|
|
# -------------------------------------------------------------------------
|
|
'ClearUpdateCache' = {
|
|
$result = @{
|
|
Success = $false
|
|
Task = 'ClearUpdateCache'
|
|
Hostname = $env:COMPUTERNAME
|
|
Output = ""
|
|
Error = $null
|
|
}
|
|
|
|
try {
|
|
Write-Output "Stopping Windows Update service..."
|
|
Stop-Service -Name wuauserv -Force -ErrorAction SilentlyContinue
|
|
Stop-Service -Name bits -Force -ErrorAction SilentlyContinue
|
|
|
|
Start-Sleep -Seconds 2
|
|
|
|
Write-Output "Clearing SoftwareDistribution folder..."
|
|
$swDistPath = "$env:SystemRoot\SoftwareDistribution"
|
|
if (Test-Path $swDistPath) {
|
|
Remove-Item "$swDistPath\*" -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
Write-Output "Clearing catroot2 folder..."
|
|
$catroot2Path = "$env:SystemRoot\System32\catroot2"
|
|
if (Test-Path $catroot2Path) {
|
|
Remove-Item "$catroot2Path\*" -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
Write-Output "Starting Windows Update service..."
|
|
Start-Service -Name wuauserv -ErrorAction SilentlyContinue
|
|
Start-Service -Name bits -ErrorAction SilentlyContinue
|
|
|
|
$result.Success = $true
|
|
$result.Output = "Windows Update cache cleared successfully"
|
|
Write-Output $result.Output
|
|
|
|
} catch {
|
|
$result.Error = $_.Exception.Message
|
|
}
|
|
|
|
return $result
|
|
}
|
|
|
|
# -------------------------------------------------------------------------
|
|
# RestartSpooler - Restart Print Spooler service
|
|
# -------------------------------------------------------------------------
|
|
'RestartSpooler' = {
|
|
$result = @{
|
|
Success = $false
|
|
Task = 'RestartSpooler'
|
|
Hostname = $env:COMPUTERNAME
|
|
Output = ""
|
|
Error = $null
|
|
}
|
|
|
|
try {
|
|
Write-Output "Stopping Print Spooler..."
|
|
Stop-Service -Name Spooler -Force -ErrorAction Stop
|
|
|
|
# Clear print queue
|
|
$printQueuePath = "$env:SystemRoot\System32\spool\PRINTERS"
|
|
if (Test-Path $printQueuePath) {
|
|
Remove-Item "$printQueuePath\*" -Force -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
Write-Output "Starting Print Spooler..."
|
|
Start-Service -Name Spooler -ErrorAction Stop
|
|
|
|
$spoolerStatus = (Get-Service -Name Spooler).Status
|
|
$result.Success = ($spoolerStatus -eq 'Running')
|
|
$result.Output = "Print Spooler restarted. Status: $spoolerStatus"
|
|
Write-Output $result.Output
|
|
|
|
} catch {
|
|
$result.Error = $_.Exception.Message
|
|
}
|
|
|
|
return $result
|
|
}
|
|
|
|
# -------------------------------------------------------------------------
|
|
# FlushDNS - Clear DNS resolver cache
|
|
# -------------------------------------------------------------------------
|
|
'FlushDNS' = {
|
|
$result = @{
|
|
Success = $false
|
|
Task = 'FlushDNS'
|
|
Hostname = $env:COMPUTERNAME
|
|
Output = ""
|
|
Error = $null
|
|
}
|
|
|
|
try {
|
|
Write-Output "Flushing DNS cache..."
|
|
$flushResult = & ipconfig /flushdns 2>&1
|
|
$result.Output = $flushResult -join "`n"
|
|
$result.Success = ($LASTEXITCODE -eq 0)
|
|
Write-Output "DNS cache flushed successfully"
|
|
} catch {
|
|
$result.Error = $_.Exception.Message
|
|
}
|
|
|
|
return $result
|
|
}
|
|
|
|
# -------------------------------------------------------------------------
|
|
# ClearBrowserCache - Clear Chrome/Edge cache
|
|
# -------------------------------------------------------------------------
|
|
'ClearBrowserCache' = {
|
|
$result = @{
|
|
Success = $false
|
|
Task = 'ClearBrowserCache'
|
|
Hostname = $env:COMPUTERNAME
|
|
Output = ""
|
|
Error = $null
|
|
BrowsersCleared = @()
|
|
}
|
|
|
|
try {
|
|
# Get all user profiles
|
|
$userProfiles = Get-ChildItem "C:\Users" -Directory | Where-Object { $_.Name -notin @('Public', 'Default', 'Default User') }
|
|
|
|
foreach ($profile in $userProfiles) {
|
|
$userName = $profile.Name
|
|
|
|
# Chrome cache paths
|
|
$chromeCachePaths = @(
|
|
"$($profile.FullName)\AppData\Local\Google\Chrome\User Data\Default\Cache",
|
|
"$($profile.FullName)\AppData\Local\Google\Chrome\User Data\Default\Code Cache"
|
|
)
|
|
|
|
# Edge cache paths
|
|
$edgeCachePaths = @(
|
|
"$($profile.FullName)\AppData\Local\Microsoft\Edge\User Data\Default\Cache",
|
|
"$($profile.FullName)\AppData\Local\Microsoft\Edge\User Data\Default\Code Cache"
|
|
)
|
|
|
|
$allPaths = $chromeCachePaths + $edgeCachePaths
|
|
|
|
foreach ($cachePath in $allPaths) {
|
|
if (Test-Path $cachePath) {
|
|
try {
|
|
Remove-Item "$cachePath\*" -Recurse -Force -ErrorAction SilentlyContinue
|
|
$browserType = if ($cachePath -like "*Chrome*") { "Chrome" } else { "Edge" }
|
|
$result.BrowsersCleared += "$userName-$browserType"
|
|
} catch { }
|
|
}
|
|
}
|
|
}
|
|
|
|
$result.Success = $true
|
|
$result.Output = "Cleared cache for: $($result.BrowsersCleared -join ', ')"
|
|
Write-Output $result.Output
|
|
|
|
} catch {
|
|
$result.Error = $_.Exception.Message
|
|
}
|
|
|
|
return $result
|
|
}
|
|
|
|
# -------------------------------------------------------------------------
|
|
# RestartWinRM - Restart WinRM service
|
|
# -------------------------------------------------------------------------
|
|
'RestartWinRM' = {
|
|
$result = @{
|
|
Success = $false
|
|
Task = 'RestartWinRM'
|
|
Hostname = $env:COMPUTERNAME
|
|
Output = ""
|
|
Error = $null
|
|
}
|
|
|
|
try {
|
|
Write-Output "Restarting WinRM service..."
|
|
|
|
Restart-Service -Name WinRM -Force -ErrorAction Stop
|
|
|
|
Start-Sleep -Seconds 2
|
|
|
|
$winrmStatus = (Get-Service -Name WinRM).Status
|
|
$result.Success = ($winrmStatus -eq 'Running')
|
|
$result.Output = "WinRM service restarted. Status: $winrmStatus"
|
|
Write-Output $result.Output
|
|
|
|
} catch {
|
|
$result.Error = $_.Exception.Message
|
|
}
|
|
|
|
return $result
|
|
}
|
|
|
|
# -------------------------------------------------------------------------
|
|
# SetTimezone - Set timezone to Eastern Standard Time
|
|
# -------------------------------------------------------------------------
|
|
'SetTimezone' = {
|
|
$result = @{
|
|
Success = $false
|
|
Task = 'SetTimezone'
|
|
Hostname = $env:COMPUTERNAME
|
|
Output = ""
|
|
Error = $null
|
|
PreviousTimezone = ""
|
|
NewTimezone = ""
|
|
}
|
|
|
|
try {
|
|
# Get current timezone
|
|
$currentTz = Get-TimeZone
|
|
$result.PreviousTimezone = $currentTz.Id
|
|
|
|
$targetTz = "Eastern Standard Time"
|
|
|
|
if ($currentTz.Id -eq $targetTz) {
|
|
$result.Success = $true
|
|
$result.NewTimezone = $currentTz.Id
|
|
$result.Output = "Timezone already set to $targetTz"
|
|
Write-Output $result.Output
|
|
} else {
|
|
Write-Output "Changing timezone from $($currentTz.Id) to $targetTz..."
|
|
|
|
Set-TimeZone -Id $targetTz -ErrorAction Stop
|
|
|
|
# Verify change
|
|
$newTz = Get-TimeZone
|
|
$result.NewTimezone = $newTz.Id
|
|
$result.Success = ($newTz.Id -eq $targetTz)
|
|
$result.Output = "Timezone changed: $($currentTz.Id) -> $($newTz.Id)"
|
|
Write-Output $result.Output
|
|
}
|
|
|
|
} catch {
|
|
$result.Error = $_.Exception.Message
|
|
}
|
|
|
|
return $result
|
|
}
|
|
|
|
# -------------------------------------------------------------------------
|
|
# SyncTime - Sync time with domain controller
|
|
# -------------------------------------------------------------------------
|
|
'SyncTime' = {
|
|
$result = @{
|
|
Success = $false
|
|
Task = 'SyncTime'
|
|
Hostname = $env:COMPUTERNAME
|
|
Output = ""
|
|
Error = $null
|
|
TimeBefore = ""
|
|
TimeAfter = ""
|
|
TimeSource = ""
|
|
}
|
|
|
|
try {
|
|
$result.TimeBefore = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
|
|
|
|
Write-Output "Syncing time with domain controller..."
|
|
|
|
# Get current time source
|
|
$w32tmSource = & w32tm /query /source 2>&1
|
|
$result.TimeSource = ($w32tmSource -join " ").Trim()
|
|
|
|
# Force time resync
|
|
$resyncResult = & w32tm /resync /force 2>&1
|
|
$resyncOutput = $resyncResult -join "`n"
|
|
|
|
# Check if successful
|
|
if ($resyncOutput -match "The command completed successfully" -or $LASTEXITCODE -eq 0) {
|
|
Start-Sleep -Seconds 1
|
|
$result.TimeAfter = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
|
|
$result.Success = $true
|
|
$result.Output = "Time synced with $($result.TimeSource). Time: $($result.TimeAfter)"
|
|
} else {
|
|
$result.Output = "Sync attempted. Result: $resyncOutput"
|
|
$result.Success = $false
|
|
}
|
|
|
|
Write-Output $result.Output
|
|
|
|
} catch {
|
|
$result.Error = $_.Exception.Message
|
|
}
|
|
|
|
return $result
|
|
}
|
|
|
|
# -------------------------------------------------------------------------
|
|
# UpdateEMxAuthToken - Backup and prepare for file copy (runs on remote PC)
|
|
# The actual file is pushed via Copy-Item -ToSession from the caller
|
|
# -------------------------------------------------------------------------
|
|
'UpdateEMxAuthToken' = {
|
|
param($SourceFileContent)
|
|
|
|
$result = @{
|
|
Success = $false
|
|
Task = 'UpdateEMxAuthToken'
|
|
Hostname = $env:COMPUTERNAME
|
|
Output = ""
|
|
Error = $null
|
|
FailReason = ""
|
|
PathsUpdated = @()
|
|
PathsFailed = @()
|
|
BackupsCreated = @()
|
|
TempFile = ""
|
|
DNCKilled = $false
|
|
DNCRestarted = $false
|
|
}
|
|
|
|
$destFile = "eMxInfo.txt"
|
|
$tempPath = "C:\Windows\Temp\eMxInfo.txt"
|
|
|
|
# Check both possible DNC installation paths
|
|
$destDirs = @(
|
|
"C:\Program Files (x86)\DNC\Server Files",
|
|
"C:\Program Files\DNC\Server Files"
|
|
)
|
|
|
|
try {
|
|
# Check if temp file was pushed
|
|
if (-not (Test-Path $tempPath)) {
|
|
$result.FailReason = "Source file not found at $tempPath - file push may have failed"
|
|
$result.Error = $result.FailReason
|
|
Write-Output $result.FailReason
|
|
return $result
|
|
}
|
|
|
|
# Track which paths exist
|
|
$validPaths = @()
|
|
foreach ($destDir in $destDirs) {
|
|
if (Test-Path $destDir) {
|
|
$validPaths += $destDir
|
|
}
|
|
}
|
|
|
|
if ($validPaths.Count -eq 0) {
|
|
$result.FailReason = "No DNC Server Files directory found in Program Files or Program Files (x86)"
|
|
$result.Error = $result.FailReason
|
|
Write-Output $result.FailReason
|
|
# Clean up temp file
|
|
Remove-Item $tempPath -Force -ErrorAction SilentlyContinue
|
|
return $result
|
|
}
|
|
|
|
Write-Output "Found $($validPaths.Count) DNC installation(s)"
|
|
|
|
# Kill DNCMain.exe before copying
|
|
Write-Output "Stopping DNCMain.exe..."
|
|
$dncProcess = Get-Process -Name "DNCMain" -ErrorAction SilentlyContinue
|
|
if ($dncProcess) {
|
|
try {
|
|
taskkill /IM DNCMain.exe /F 2>&1 | Out-Null
|
|
Start-Sleep -Seconds 2
|
|
$result.DNCKilled = $true
|
|
Write-Output " DNCMain.exe stopped"
|
|
} catch {
|
|
Write-Output " Warning: Could not stop DNCMain.exe - $($_.Exception.Message)"
|
|
}
|
|
} else {
|
|
Write-Output " DNCMain.exe not running"
|
|
}
|
|
|
|
# Process each valid path
|
|
foreach ($destDir in $validPaths) {
|
|
$destPath = Join-Path $destDir $destFile
|
|
$pathLabel = if ($destDir -like "*x86*") { "x86" } else { "x64" }
|
|
|
|
Write-Output "Processing $pathLabel path: $destDir"
|
|
|
|
# Check if destination file exists and back it up
|
|
if (Test-Path $destPath) {
|
|
$dateStamp = Get-Date -Format "yyyyMMdd"
|
|
$backupName = "eMxInfo-old-$dateStamp.txt"
|
|
$backupPath = Join-Path $destDir $backupName
|
|
|
|
Write-Output " File exists, renaming to $backupName..."
|
|
|
|
try {
|
|
# Remove existing backup if same date
|
|
if (Test-Path $backupPath) {
|
|
Remove-Item $backupPath -Force -ErrorAction Stop
|
|
}
|
|
|
|
Rename-Item -Path $destPath -NewName $backupName -Force -ErrorAction Stop
|
|
$result.BackupsCreated += "$pathLabel`:$backupName"
|
|
} catch {
|
|
$result.PathsFailed += "$pathLabel`: Failed to rename - $($_.Exception.Message)"
|
|
Write-Output " FAILED to rename: $($_.Exception.Message)"
|
|
continue
|
|
}
|
|
} else {
|
|
Write-Output " File does not exist, creating new..."
|
|
}
|
|
|
|
# Copy from temp location to destination
|
|
try {
|
|
Copy-Item -Path $tempPath -Destination $destPath -Force -ErrorAction Stop
|
|
|
|
# Verify the copy
|
|
if (Test-Path $destPath) {
|
|
$result.PathsUpdated += $pathLabel
|
|
Write-Output " SUCCESS"
|
|
} else {
|
|
$result.PathsFailed += "$pathLabel`: Copy succeeded but file not found"
|
|
Write-Output " FAILED: File not found after copy"
|
|
}
|
|
} catch {
|
|
$result.PathsFailed += "$pathLabel`: Failed to copy - $($_.Exception.Message)"
|
|
Write-Output " FAILED to copy: $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
# Clean up temp file
|
|
Remove-Item $tempPath -Force -ErrorAction SilentlyContinue
|
|
|
|
# Determine overall success
|
|
if ($result.PathsUpdated.Count -gt 0) {
|
|
$result.Success = $true
|
|
$result.Output = "Updated: $($result.PathsUpdated -join ', ')"
|
|
if ($result.BackupsCreated.Count -gt 0) {
|
|
$result.Output += " | Backups: $($result.BackupsCreated -join ', ')"
|
|
}
|
|
if ($result.PathsFailed.Count -gt 0) {
|
|
$result.Output += " | Failed: $($result.PathsFailed.Count)"
|
|
}
|
|
|
|
# Restart DNC - find LDnc.exe in one of the valid paths
|
|
Write-Output "Starting LDnc.exe..."
|
|
$ldncStarted = $false
|
|
foreach ($destDir in $validPaths) {
|
|
$ldncPath = Join-Path $destDir "LDnc.exe"
|
|
if (Test-Path $ldncPath) {
|
|
try {
|
|
Start-Process -FilePath $ldncPath -ErrorAction Stop
|
|
$result.DNCRestarted = $true
|
|
$ldncStarted = $true
|
|
Write-Output " LDnc.exe started from $destDir"
|
|
break
|
|
} catch {
|
|
Write-Output " Warning: Could not start LDnc.exe from $destDir - $($_.Exception.Message)"
|
|
}
|
|
}
|
|
}
|
|
if (-not $ldncStarted) {
|
|
Write-Output " Warning: LDnc.exe not found or could not be started"
|
|
}
|
|
|
|
$result.Output += " | DNC restarted: $($result.DNCRestarted)"
|
|
} else {
|
|
$result.FailReason = "All paths failed: $($result.PathsFailed -join '; ')"
|
|
$result.Error = $result.FailReason
|
|
}
|
|
|
|
} catch {
|
|
$result.FailReason = "Unexpected error: $($_.Exception.Message)"
|
|
$result.Error = $result.FailReason
|
|
Write-Output $result.FailReason
|
|
}
|
|
|
|
return $result
|
|
}
|
|
|
|
# -------------------------------------------------------------------------
|
|
# DeployUDCWebServerConfig - Deploy udc_webserver_settings.json to UDC PCs
|
|
# The actual file is pushed via Copy-Item -ToSession from the caller
|
|
# -------------------------------------------------------------------------
|
|
'DeployUDCWebServerConfig' = {
|
|
$result = @{
|
|
Success = $false
|
|
Task = 'DeployUDCWebServerConfig'
|
|
Hostname = $env:COMPUTERNAME
|
|
Output = ""
|
|
Error = $null
|
|
FailReason = ""
|
|
UDCInstalled = $false
|
|
BackupCreated = $false
|
|
}
|
|
|
|
$udcInstallDir = "C:\Program Files\UDC"
|
|
$destDir = "C:\ProgramData\UDC"
|
|
$destFile = "udc_webserver_settings.json"
|
|
$destPath = Join-Path $destDir $destFile
|
|
$tempPath = "C:\Windows\Temp\udc_webserver_settings.json"
|
|
|
|
try {
|
|
# Check if UDC is installed
|
|
if (-not (Test-Path $udcInstallDir)) {
|
|
$result.FailReason = "UDC not installed ($udcInstallDir not found) - skipping"
|
|
$result.Output = $result.FailReason
|
|
Write-Output $result.FailReason
|
|
return $result
|
|
}
|
|
|
|
$result.UDCInstalled = $true
|
|
Write-Output "UDC installation found at $udcInstallDir"
|
|
|
|
# Check if temp file was pushed
|
|
if (-not (Test-Path $tempPath)) {
|
|
$result.FailReason = "Source file not found at $tempPath - file push may have failed"
|
|
$result.Error = $result.FailReason
|
|
Write-Output $result.FailReason
|
|
return $result
|
|
}
|
|
|
|
# Create destination directory if it doesn't exist
|
|
if (-not (Test-Path $destDir)) {
|
|
New-Item -Path $destDir -ItemType Directory -Force | Out-Null
|
|
Write-Output "Created directory: $destDir"
|
|
}
|
|
|
|
# Backup existing config if present
|
|
if (Test-Path $destPath) {
|
|
$dateStamp = Get-Date -Format "yyyyMMdd"
|
|
$backupName = "udc_webserver_settings-old-$dateStamp.json"
|
|
$backupPath = Join-Path $destDir $backupName
|
|
|
|
Write-Output "Existing config found, backing up to $backupName..."
|
|
|
|
try {
|
|
if (Test-Path $backupPath) {
|
|
Remove-Item $backupPath -Force -ErrorAction Stop
|
|
}
|
|
Rename-Item -Path $destPath -NewName $backupName -Force -ErrorAction Stop
|
|
$result.BackupCreated = $true
|
|
} catch {
|
|
Write-Output "Warning: Could not backup existing config - $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
# Copy from temp location to destination
|
|
Copy-Item -Path $tempPath -Destination $destPath -Force -ErrorAction Stop
|
|
|
|
# Verify the copy
|
|
if (Test-Path $destPath) {
|
|
$result.Success = $true
|
|
$result.Output = "Config deployed to $destPath"
|
|
if ($result.BackupCreated) {
|
|
$result.Output += " (backup created)"
|
|
}
|
|
Write-Output "SUCCESS: $($result.Output)"
|
|
} else {
|
|
$result.FailReason = "Copy succeeded but file not found at destination"
|
|
$result.Error = $result.FailReason
|
|
Write-Output "FAILED: $($result.FailReason)"
|
|
}
|
|
|
|
# Clean up temp file
|
|
Remove-Item $tempPath -Force -ErrorAction SilentlyContinue
|
|
|
|
} catch {
|
|
$result.FailReason = "Unexpected error: $($_.Exception.Message)"
|
|
$result.Error = $result.FailReason
|
|
Write-Output $result.FailReason
|
|
}
|
|
|
|
return $result
|
|
}
|
|
|
|
# -------------------------------------------------------------------------
|
|
# InstallDashboard / InstallLobbyDisplay - Install kiosk app
|
|
# -------------------------------------------------------------------------
|
|
'InstallKioskApp' = {
|
|
param($InstallerPath, $AppName)
|
|
|
|
$result = @{
|
|
Success = $false
|
|
Task = 'InstallKioskApp'
|
|
Hostname = $env:COMPUTERNAME
|
|
Output = ""
|
|
Error = $null
|
|
AppName = $AppName
|
|
}
|
|
|
|
try {
|
|
if (-not (Test-Path $InstallerPath)) {
|
|
$result.Error = "Installer not found at $InstallerPath"
|
|
Write-Output $result.Error
|
|
return $result
|
|
}
|
|
|
|
Write-Output "Installing $AppName..."
|
|
$proc = Start-Process -FilePath $InstallerPath -ArgumentList "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART" -Wait -PassThru -WindowStyle Hidden
|
|
|
|
# Clean up installer
|
|
Remove-Item $InstallerPath -Force -ErrorAction SilentlyContinue
|
|
|
|
if ($proc.ExitCode -eq 0) {
|
|
$result.Success = $true
|
|
$result.Output = "$AppName installed successfully"
|
|
} else {
|
|
$result.Error = "Installer exited with code $($proc.ExitCode)"
|
|
}
|
|
|
|
Write-Output $result.Output
|
|
} catch {
|
|
$result.Error = $_.Exception.Message
|
|
Write-Output "Error: $($result.Error)"
|
|
}
|
|
|
|
return $result
|
|
}
|
|
|
|
# -------------------------------------------------------------------------
|
|
# UninstallDashboard / UninstallLobbyDisplay - Uninstall kiosk app
|
|
# -------------------------------------------------------------------------
|
|
'UninstallKioskApp' = {
|
|
param($UninstallGuid, $AppName)
|
|
|
|
$result = @{
|
|
Success = $false
|
|
Task = 'UninstallKioskApp'
|
|
Hostname = $env:COMPUTERNAME
|
|
Output = ""
|
|
Error = $null
|
|
AppName = $AppName
|
|
}
|
|
|
|
try {
|
|
Write-Output "Uninstalling $AppName..."
|
|
|
|
# Find uninstaller in registry
|
|
$uninstallPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$UninstallGuid`_is1"
|
|
if (-not (Test-Path $uninstallPath)) {
|
|
$uninstallPath = "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\$UninstallGuid`_is1"
|
|
}
|
|
|
|
if (Test-Path $uninstallPath) {
|
|
$uninstallString = (Get-ItemProperty $uninstallPath).UninstallString
|
|
if ($uninstallString) {
|
|
$uninstallExe = $uninstallString -replace '"', ''
|
|
$proc = Start-Process -FilePath $uninstallExe -ArgumentList "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART" -Wait -PassThru -WindowStyle Hidden
|
|
|
|
if ($proc.ExitCode -eq 0) {
|
|
$result.Success = $true
|
|
$result.Output = "$AppName uninstalled successfully"
|
|
} else {
|
|
$result.Error = "Uninstaller exited with code $($proc.ExitCode)"
|
|
}
|
|
} else {
|
|
$result.Error = "No uninstall string found in registry"
|
|
}
|
|
} else {
|
|
$result.Error = "$AppName not found in registry (may not be installed)"
|
|
}
|
|
|
|
Write-Output $(if ($result.Success) { $result.Output } else { $result.Error })
|
|
} catch {
|
|
$result.Error = $_.Exception.Message
|
|
Write-Output "Error: $($result.Error)"
|
|
}
|
|
|
|
return $result
|
|
}
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Reboot - Restart the computer
|
|
# -------------------------------------------------------------------------
|
|
'Reboot' = {
|
|
$result = @{
|
|
Success = $false
|
|
Task = 'Reboot'
|
|
Hostname = $env:COMPUTERNAME
|
|
Output = ""
|
|
Error = $null
|
|
}
|
|
|
|
try {
|
|
Write-Output "Initiating system restart..."
|
|
|
|
# Use shutdown command with 30 second delay to allow WinRM to return
|
|
$shutdownResult = & shutdown.exe /r /t 30 /c "Remote maintenance reboot initiated" 2>&1
|
|
$exitCode = $LASTEXITCODE
|
|
|
|
if ($exitCode -eq 0) {
|
|
$result.Success = $true
|
|
$result.Output = "Reboot scheduled in 30 seconds"
|
|
Write-Output $result.Output
|
|
} else {
|
|
$result.Error = "Shutdown command failed with exit code $exitCode : $shutdownResult"
|
|
Write-Output $result.Error
|
|
}
|
|
} catch {
|
|
$result.Error = $_.Exception.Message
|
|
Write-Output "Error: $($result.Error)"
|
|
}
|
|
|
|
return $result
|
|
}
|
|
}
|
|
|
|
# =============================================================================
|
|
# Main Execution
|
|
# =============================================================================
|
|
|
|
Write-Host ""
|
|
Write-Host "=" * 70 -ForegroundColor Cyan
|
|
Write-Host " Remote Maintenance Tool - Task: $Task" -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 ($All) {
|
|
Write-Log "Querying ShopDB for all shopfloor PCs..." -Level "INFO"
|
|
$shopfloorPCs = Get-ShopfloorPCsFromApi -ApiUrl $ApiUrl
|
|
$computers = $shopfloorPCs | ForEach-Object { $_.hostname } | Where-Object { $_ }
|
|
Write-Log "Found $($computers.Count) shopfloor PCs" -Level "INFO"
|
|
} elseif ($PcType) {
|
|
$pcTypeId = $PcTypeLookup[$PcType]
|
|
Write-Log "Querying ShopDB for PCs of type '$PcType' (ID: $pcTypeId)..." -Level "INFO"
|
|
$shopfloorPCs = Get-ShopfloorPCsFromApi -ApiUrl $ApiUrl -PcTypeId $pcTypeId
|
|
$computers = $shopfloorPCs | ForEach-Object { $_.hostname } | Where-Object { $_ }
|
|
Write-Log "Found $($computers.Count) PCs of type '$PcType'" -Level "INFO"
|
|
} elseif ($BusinessUnit) {
|
|
$businessUnitId = $BusinessUnitLookup[$BusinessUnit]
|
|
Write-Log "Querying ShopDB for PCs in business unit '$BusinessUnit' (ID: $businessUnitId)..." -Level "INFO"
|
|
$shopfloorPCs = Get-ShopfloorPCsFromApi -ApiUrl $ApiUrl -BusinessUnitId $businessUnitId
|
|
$computers = $shopfloorPCs | ForEach-Object { $_.hostname } | Where-Object { $_ }
|
|
Write-Log "Found $($computers.Count) PCs in business unit '$BusinessUnit'" -Level "INFO"
|
|
} elseif ($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, -ComputerListFile, -All, -PcType, or -BusinessUnit" -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 "Task: $Task" -Level "TASK"
|
|
Write-Host ""
|
|
|
|
# Build FQDNs
|
|
$targetFQDNs = $computers | ForEach-Object {
|
|
if ($_ -like "*.*") { $_ } else { "$_.$DnsSuffix" }
|
|
}
|
|
|
|
# Determine which tasks to run
|
|
$tasksToRun = @($Task)
|
|
|
|
# Create session options
|
|
$sessionOption = New-PSSessionOption -OpenTimeout 30000 -OperationTimeout 600000 -NoMachineProfile
|
|
|
|
# Special handling for UpdateEMxAuthToken - requires pushing file first
|
|
if ($Task -eq 'UpdateEMxAuthToken') {
|
|
$sourcePath = "\\tsgwp00525.wjs.geaerospace.net\dt\shopfloor\scripts\eMx\eMxInfo.txt"
|
|
$remoteTempPath = "C:\Windows\Temp\eMxInfo.txt"
|
|
|
|
Write-Log "UpdateEMxAuthToken: Checking source file..." -Level "INFO"
|
|
|
|
if (-not (Test-Path $sourcePath)) {
|
|
Write-Log "Source file not found: $sourcePath" -Level "ERROR"
|
|
exit 1
|
|
}
|
|
|
|
Write-Log "Source file found. Will push to each PC before executing." -Level "INFO"
|
|
|
|
$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
|
|
|
|
# Push the file to remote temp location
|
|
Write-Log " Pushing file to remote PC..." -Level "INFO"
|
|
Copy-Item -Path $sourcePath -Destination $remoteTempPath -ToSession $session -Force -ErrorAction Stop
|
|
|
|
# Execute the scriptblock
|
|
Write-Log " Executing update task..." -Level "INFO"
|
|
$result = Invoke-Command -Session $session -ScriptBlock $TaskScripts['UpdateEMxAuthToken'] -ErrorAction Stop
|
|
|
|
# Close session
|
|
Remove-PSSession $session -ErrorAction SilentlyContinue
|
|
|
|
# Process result
|
|
if ($result.Success) {
|
|
Write-Log "[OK] $($result.Hostname)" -Level "SUCCESS"
|
|
Write-Host " $($result.Output)" -ForegroundColor Gray
|
|
if ($result.PathsFailed.Count -gt 0) {
|
|
foreach ($fail in $result.PathsFailed) {
|
|
Write-Host " [!] $fail" -ForegroundColor Yellow
|
|
}
|
|
}
|
|
$successCount++
|
|
} else {
|
|
$errorMsg = if ($result.FailReason) { $result.FailReason } else { $result.Error }
|
|
Write-Log "[FAIL] $($result.Hostname): $errorMsg" -Level "ERROR"
|
|
$failCount++
|
|
}
|
|
|
|
} catch {
|
|
Write-Log "[FAIL] ${fqdn}: $($_.Exception.Message)" -Level "ERROR"
|
|
$failCount++
|
|
# Clean up session if it exists
|
|
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 " Task: $Task" -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
|
|
|
|
exit 0
|
|
}
|
|
|
|
# Special handling for DeployUDCWebServerConfig - check for UDC installation, then push config file
|
|
if ($Task -eq 'DeployUDCWebServerConfig') {
|
|
$sourcePath = "\\tsgwp00525.wjs.geaerospace.net\dt\shopfloor\scripts\UDC\udc_webserver_settings.json"
|
|
$remoteTempPath = "C:\Windows\Temp\udc_webserver_settings.json"
|
|
|
|
Write-Log "DeployUDCWebServerConfig: Checking source file..." -Level "INFO"
|
|
|
|
if (-not (Test-Path $sourcePath)) {
|
|
Write-Log "Source file not found: $sourcePath" -Level "ERROR"
|
|
exit 1
|
|
}
|
|
|
|
Write-Log "Source file found: $sourcePath" -Level "INFO"
|
|
Write-Log "Will check each PC for UDC installation before deploying." -Level "INFO"
|
|
|
|
$successCount = 0
|
|
$failCount = 0
|
|
$skippedCount = 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
|
|
|
|
# Check if UDC is installed before pushing the file
|
|
$udcInstalled = Invoke-Command -Session $session -ScriptBlock { Test-Path "C:\Program Files\UDC" } -ErrorAction Stop
|
|
|
|
if (-not $udcInstalled) {
|
|
Write-Log "[SKIP] $fqdn - UDC not installed" -Level "INFO"
|
|
Remove-PSSession $session -ErrorAction SilentlyContinue
|
|
$skippedCount++
|
|
continue
|
|
}
|
|
|
|
# Push the config file to remote temp location
|
|
Write-Log " UDC installed - pushing config file..." -Level "INFO"
|
|
Copy-Item -Path $sourcePath -Destination $remoteTempPath -ToSession $session -Force -ErrorAction Stop
|
|
|
|
# Execute the scriptblock
|
|
Write-Log " Executing deploy task..." -Level "INFO"
|
|
$result = Invoke-Command -Session $session -ScriptBlock $TaskScripts['DeployUDCWebServerConfig'] -ErrorAction Stop
|
|
|
|
# Close session
|
|
Remove-PSSession $session -ErrorAction SilentlyContinue
|
|
|
|
# Process result
|
|
if ($result.Success) {
|
|
Write-Log "[OK] $($result.Hostname): $($result.Output)" -Level "SUCCESS"
|
|
$successCount++
|
|
} else {
|
|
$errorMsg = if ($result.FailReason) { $result.FailReason } else { $result.Error }
|
|
Write-Log "[FAIL] $($result.Hostname): $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 " Task: $Task" -ForegroundColor White
|
|
Write-Host " Successful: $successCount" -ForegroundColor Green
|
|
Write-Host " Skipped: $skippedCount (UDC not installed)" -ForegroundColor Yellow
|
|
Write-Host " Failed: $failCount" -ForegroundColor $(if ($failCount -gt 0) { "Red" } else { "White" })
|
|
Write-Host ("=" * 70) -ForegroundColor Cyan
|
|
|
|
exit 0
|
|
}
|
|
|
|
# Special handling for Install/Uninstall kiosk apps
|
|
$KioskAppConfig = @{
|
|
'InstallDashboard' = @{
|
|
Action = 'Install'
|
|
InstallerPath = '\\tsgwp00525.wjs.geaerospace.net\dt\shopfloor\scripts\Dashboard\GEAerospaceDashboardSetup.exe'
|
|
InstallerName = 'GEAerospaceDashboardSetup.exe'
|
|
AppName = 'GE Aerospace Dashboard'
|
|
UninstallGuid = '{9D9EEE25-4D24-422D-98AF-2ADEDA4745ED}'
|
|
}
|
|
'InstallLobbyDisplay' = @{
|
|
Action = 'Install'
|
|
InstallerPath = '\\tsgwp00525.wjs.geaerospace.net\dt\shopfloor\scripts\LobbyDisplay\GEAerospaceLobbyDisplaySetup.exe'
|
|
InstallerName = 'GEAerospaceLobbyDisplaySetup.exe'
|
|
AppName = 'GE Aerospace Lobby Display'
|
|
UninstallGuid = '{42FFB952-0B72-493F-8869-D957344CA305}'
|
|
}
|
|
'UninstallDashboard' = @{
|
|
Action = 'Uninstall'
|
|
InstallerName = 'GEAerospaceDashboardSetup.exe'
|
|
AppName = 'GE Aerospace Dashboard'
|
|
UninstallGuid = '{9D9EEE25-4D24-422D-98AF-2ADEDA4745ED}'
|
|
}
|
|
'UninstallLobbyDisplay' = @{
|
|
Action = 'Uninstall'
|
|
InstallerName = 'GEAerospaceLobbyDisplaySetup.exe'
|
|
AppName = 'GE Aerospace Lobby Display'
|
|
UninstallGuid = '{42FFB952-0B72-493F-8869-D957344CA305}'
|
|
}
|
|
}
|
|
|
|
if ($KioskAppConfig.ContainsKey($Task)) {
|
|
$appConfig = $KioskAppConfig[$Task]
|
|
|
|
if ($appConfig.Action -eq 'Install') {
|
|
# Find installer from network share
|
|
$installerPath = $appConfig.InstallerPath
|
|
if (-not (Test-Path $installerPath)) {
|
|
Write-Log "Installer not found: $installerPath" -Level "ERROR"
|
|
Write-Log "Ensure the installer exists on the network share" -Level "ERROR"
|
|
exit 1
|
|
}
|
|
Write-Log "$($appConfig.Action): $($appConfig.AppName)" -Level "INFO"
|
|
Write-Log "Installer: $installerPath" -Level "INFO"
|
|
} else {
|
|
Write-Log "$($appConfig.Action): $($appConfig.AppName)" -Level "INFO"
|
|
}
|
|
|
|
$successCount = 0
|
|
$failCount = 0
|
|
|
|
foreach ($fqdn in $targetFQDNs) {
|
|
Write-Host ""
|
|
Write-Log "Processing: $fqdn" -Level "TASK"
|
|
|
|
try {
|
|
$session = New-PSSession -ComputerName $fqdn -Credential $Credential -SessionOption $sessionOption -Authentication Negotiate -ErrorAction Stop
|
|
|
|
if ($appConfig.Action -eq 'Install') {
|
|
$remoteTempPath = "C:\Windows\Temp\$($appConfig.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 $TaskScripts['InstallKioskApp'] -ArgumentList $remoteTempPath, $appConfig.AppName -ErrorAction Stop
|
|
} else {
|
|
Write-Log " Running uninstaller..." -Level "INFO"
|
|
$result = Invoke-Command -Session $session -ScriptBlock $TaskScripts['UninstallKioskApp'] -ArgumentList $appConfig.UninstallGuid, $appConfig.AppName -ErrorAction Stop
|
|
}
|
|
|
|
Remove-PSSession $session -ErrorAction SilentlyContinue
|
|
|
|
if ($result.Success) {
|
|
Write-Log "[OK] $($result.Hostname) - $($result.Output)" -Level "SUCCESS"
|
|
$successCount++
|
|
} else {
|
|
$errorMsg = if ($result.Error) { $result.Error } else { "Unknown error" }
|
|
Write-Log "[FAIL] $($result.Hostname): $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 " Task: $Task" -ForegroundColor White
|
|
Write-Host " App: $($appConfig.AppName)" -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
|
|
|
|
exit 0
|
|
}
|
|
|
|
# Process each task
|
|
foreach ($currentTask in $tasksToRun) {
|
|
|
|
if ($tasksToRun.Count -gt 1) {
|
|
Write-Host ""
|
|
Write-Log "Running task: $currentTask" -Level "TASK"
|
|
}
|
|
|
|
$scriptBlock = $TaskScripts[$currentTask]
|
|
|
|
if (-not $scriptBlock) {
|
|
Write-Log "Unknown task: $currentTask" -Level "ERROR"
|
|
continue
|
|
}
|
|
|
|
# Execute on remote computers
|
|
$sessionParams = @{
|
|
ComputerName = $targetFQDNs
|
|
ScriptBlock = $scriptBlock
|
|
Credential = $Credential
|
|
SessionOption = $sessionOption
|
|
ErrorAction = 'SilentlyContinue'
|
|
ErrorVariable = 'remoteErrors'
|
|
}
|
|
|
|
if ($ThrottleLimit -and $PSVersionTable.PSVersion.Major -ge 7) {
|
|
$sessionParams.ThrottleLimit = $ThrottleLimit
|
|
}
|
|
|
|
Write-Log "Executing on $($targetFQDNs.Count) computer(s)..." -Level "INFO"
|
|
|
|
$results = Invoke-Command @sessionParams
|
|
|
|
# Process results
|
|
$successCount = 0
|
|
$failCount = 0
|
|
|
|
foreach ($result in $results) {
|
|
if ($result.Success) {
|
|
Write-Log "[OK] $($result.Hostname)" -Level "SUCCESS"
|
|
|
|
# Display task-specific output
|
|
switch ($currentTask) {
|
|
'OptimizeDisk' {
|
|
foreach ($drive in $result.Drives) {
|
|
$status = if ($drive.Success) { "OK" } else { "FAIL" }
|
|
Write-Host " Drive $($drive.DriveLetter): $($drive.MediaType) - $($drive.Action) [$status]" -ForegroundColor Gray
|
|
}
|
|
}
|
|
'DISM' {
|
|
Write-Host " Duration: $($result.Duration) minutes, Exit code: $($result.ExitCode)" -ForegroundColor Gray
|
|
}
|
|
'SFC' {
|
|
Write-Host " $($result.Summary), Duration: $($result.Duration) minutes" -ForegroundColor Gray
|
|
}
|
|
'DiskCleanup' {
|
|
Write-Host " Space freed: $($result.SpaceFreed) GB" -ForegroundColor Gray
|
|
}
|
|
'UpdateEMxAuthToken' {
|
|
Write-Host " $($result.Output)" -ForegroundColor Gray
|
|
if ($result.PathsFailed.Count -gt 0) {
|
|
foreach ($fail in $result.PathsFailed) {
|
|
Write-Host " [!] $fail" -ForegroundColor Yellow
|
|
}
|
|
}
|
|
}
|
|
default {
|
|
if ($result.Output) {
|
|
Write-Host " $($result.Output)" -ForegroundColor Gray
|
|
}
|
|
}
|
|
}
|
|
|
|
$successCount++
|
|
|
|
} else {
|
|
$errorMsg = if ($result.FailReason) { $result.FailReason } else { $result.Error }
|
|
Write-Log "[FAIL] $($result.Hostname): $errorMsg" -Level "ERROR"
|
|
$failCount++
|
|
}
|
|
}
|
|
|
|
# Handle connection errors
|
|
foreach ($err in $remoteErrors) {
|
|
$target = if ($err.TargetObject) { $err.TargetObject } else { "Unknown" }
|
|
Write-Log "[FAIL] ${target}: $($err.Exception.Message)" -Level "ERROR"
|
|
$failCount++
|
|
}
|
|
}
|
|
|
|
# Summary
|
|
Write-Host ""
|
|
Write-Host "=" * 70 -ForegroundColor Cyan
|
|
Write-Host " SUMMARY" -ForegroundColor Cyan
|
|
Write-Host "=" * 70 -ForegroundColor Cyan
|
|
Write-Host " Task: $Task" -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
|