Add fixnetworkshare, winrm-setup-package, udc remote-execution suites
- NetworkDriveManager.ps1: S: drive repair utility - winrm-setup-package: Invoke-RemoteTask helper + Setup-WinRM.bat + HTML guide - remote-execution/udc: UDC_Update.ps1 and batch wrappers for updating DNC controllers on shop-floor PCs - Invoke-RemoteMaintenance.ps1: substantial rework (~1650 lines) - Schedule-Maintenance and complete-asset minor updates - Bump edncfix gitlink to v1.6.0 (2748bfa) - .gitignore: block inventory.csv/xlsx (CUI) and logs_*.txt (per-host logs) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
29
remote-execution/DeployOpenTextProfiles-Examples.txt
Normal file
29
remote-execution/DeployOpenTextProfiles-Examples.txt
Normal file
@@ -0,0 +1,29 @@
|
||||
# DeployOpenTextProfiles - Example Usage
|
||||
# Source: \\tsgwp00525.wjs.geaerospace.net\shared\dt\csf\
|
||||
|
||||
# Single PC
|
||||
.\Invoke-RemoteMaintenance.ps1 -ComputerName "G1ZTNCX3ESF" -Task DeployOpenTextProfiles
|
||||
|
||||
# Multiple PCs
|
||||
.\Invoke-RemoteMaintenance.ps1 -ComputerName "G1ZTNCX3ESF","G1ZTNCX4ESF","G1ZTNCX5ESF" -Task DeployOpenTextProfiles
|
||||
|
||||
# All shopfloor PCs
|
||||
.\Invoke-RemoteMaintenance.ps1 -PcType Shopfloor -Task DeployOpenTextProfiles
|
||||
|
||||
# Wax / Trace PCs
|
||||
.\Invoke-RemoteMaintenance.ps1 -PcType "Wax / Trace" -Task DeployOpenTextProfiles
|
||||
|
||||
# Keyence PCs
|
||||
.\Invoke-RemoteMaintenance.ps1 -PcType Keyence -Task DeployOpenTextProfiles
|
||||
|
||||
# Genspect PCs
|
||||
.\Invoke-RemoteMaintenance.ps1 -PcType Genspect -Task DeployOpenTextProfiles
|
||||
|
||||
# Heat Treat PCs
|
||||
.\Invoke-RemoteMaintenance.ps1 -PcType "Heat Treat" -Task DeployOpenTextProfiles
|
||||
|
||||
# CMM PCs
|
||||
.\Invoke-RemoteMaintenance.ps1 -PcType CMM -Task DeployOpenTextProfiles
|
||||
|
||||
# Inspection PCs
|
||||
.\Invoke-RemoteMaintenance.ps1 -PcType Inspection -Task DeployOpenTextProfiles
|
||||
@@ -13,8 +13,11 @@ REQUIREMENTS
|
||||
|
||||
- PowerShell 5.1+
|
||||
- Run as Administrator (required for scheduling only)
|
||||
- Invoke-RemoteMaintenance.ps1 in the same folder
|
||||
- A PC list text file (one hostname per line)
|
||||
- ALL scripts must be in the SAME folder:
|
||||
Invoke-RemoteMaintenance.ps1
|
||||
Schedule-Maintenance.ps1
|
||||
Export-PCList.ps1
|
||||
shopfloor-pcs.txt (or your PC list file)
|
||||
|
||||
|
||||
============================================================
|
||||
@@ -34,53 +37,92 @@ PARAMETERS
|
||||
|
||||
|
||||
============================================================
|
||||
USAGE
|
||||
STEP 1: Save Credentials (one time)
|
||||
============================================================
|
||||
|
||||
1. SAVE CREDENTIALS (one time, does not require admin)
|
||||
.\Schedule-Maintenance.ps1 -SaveCredential -Username "DS\570005354" -Password "MyP@ssw0rd"
|
||||
|
||||
.\Schedule-Maintenance.ps1 -SaveCredential -Username "DS\570005354" -Password "MyP@ssw0rd"
|
||||
|
||||
- Encrypted with Windows DPAPI
|
||||
- Only your user account on this machine can decrypt
|
||||
- Re-run if your password changes
|
||||
|
||||
|
||||
2. RUN IMMEDIATELY (does not require admin)
|
||||
|
||||
.\Schedule-Maintenance.ps1 -ComputerListFile ".\shopfloor-pcs.txt" -Task Reboot
|
||||
|
||||
|
||||
3. SCHEDULE A ONE-TIME TASK (requires admin)
|
||||
|
||||
# Reboot one PC today at 3:00 PM
|
||||
.\Schedule-Maintenance.ps1 -CreateScheduledTask -ComputerListFile ".\test-reboot.txt" -Task Reboot -TaskFrequency Once -TaskTime "15:00" -TaskDate "2026-02-19"
|
||||
|
||||
# Reboot all PCs Sunday Feb 22 at 12:01 AM
|
||||
.\Schedule-Maintenance.ps1 -CreateScheduledTask -ComputerListFile ".\shopfloor-pcs.txt" -Task Reboot -TaskFrequency Once -TaskTime "00:01" -TaskDate "2026-02-22"
|
||||
|
||||
|
||||
4. SCHEDULE A RECURRING TASK (requires admin)
|
||||
|
||||
# Every Sunday at 12:01 AM
|
||||
.\Schedule-Maintenance.ps1 -CreateScheduledTask -ComputerListFile ".\shopfloor-pcs.txt" -Task Reboot -TaskFrequency Weekly -TaskDay Sunday -TaskTime "00:01"
|
||||
|
||||
# Every day at 2:00 AM
|
||||
.\Schedule-Maintenance.ps1 -CreateScheduledTask -ComputerListFile ".\shopfloor-pcs.txt" -Task DiskCleanup -TaskFrequency Daily -TaskTime "02:00"
|
||||
|
||||
|
||||
5. MANAGE SCHEDULED TASKS
|
||||
|
||||
Get-ScheduledTask | Where-Object { $_.TaskName -like "ShopfloorMaintenance*" }
|
||||
Start-ScheduledTask -TaskName "ShopfloorMaintenance-Reboot"
|
||||
Unregister-ScheduledTask -TaskName "ShopfloorMaintenance-Reboot"
|
||||
- Encrypted with AES-256 key
|
||||
- Works from normal or admin PowerShell
|
||||
- Stored in .creds\ folder (not plaintext)
|
||||
- Re-run if your password changes
|
||||
|
||||
|
||||
============================================================
|
||||
LOGS
|
||||
STEP 2: Generate PC List
|
||||
============================================================
|
||||
|
||||
.\logs\maintenance-YYYY-MM-DD_HHMMSS-TaskName.log
|
||||
# All shopfloor PCs from API
|
||||
.\Export-PCList.ps1
|
||||
|
||||
# Filter by type
|
||||
.\Export-PCList.ps1 -PcType Shopfloor
|
||||
|
||||
# Single PC for testing
|
||||
"G63TVG04ESF" | Out-File -FilePath ".\test-reboot.txt" -Encoding UTF8
|
||||
|
||||
|
||||
============================================================
|
||||
STEP 3: Run or Schedule
|
||||
============================================================
|
||||
|
||||
RUN IMMEDIATELY (no admin needed):
|
||||
.\Schedule-Maintenance.ps1 -ComputerListFile ".\shopfloor-pcs.txt" -Task Reboot
|
||||
|
||||
SCHEDULE ONE-TIME (admin required):
|
||||
.\Schedule-Maintenance.ps1 -CreateScheduledTask -ComputerListFile ".\shopfloor-pcs.txt" -Task Reboot -TaskFrequency Once -TaskTime "00:01" -TaskDate "2026-02-22"
|
||||
|
||||
SCHEDULE RECURRING (admin required):
|
||||
.\Schedule-Maintenance.ps1 -CreateScheduledTask -ComputerListFile ".\shopfloor-pcs.txt" -Task Reboot -TaskFrequency Weekly -TaskDay Sunday -TaskTime "00:01"
|
||||
|
||||
|
||||
============================================================
|
||||
CHECKING RESULTS
|
||||
============================================================
|
||||
|
||||
AFTER A SCHEDULED RUN:
|
||||
Get-Content ".\logs\LAST-RUN-SUMMARY.txt"
|
||||
|
||||
FULL LOG (most recent):
|
||||
Get-ChildItem ".\logs\" -Filter "maintenance-*-Reboot.log" | Sort-Object LastWriteTime -Descending | Select-Object -First 1 | Get-Content
|
||||
|
||||
CHECK IF TASK RAN:
|
||||
Get-ScheduledTask -TaskName "ShopfloorMaintenance-Reboot" | Get-ScheduledTaskInfo
|
||||
|
||||
LastTaskResult = 0 means success
|
||||
Anything else means it errored before writing logs
|
||||
|
||||
LOGS LOCATION:
|
||||
.\logs\ (inside your scripts folder)
|
||||
|
||||
|
||||
============================================================
|
||||
MANAGING SCHEDULED TASKS
|
||||
============================================================
|
||||
|
||||
# List maintenance tasks
|
||||
Get-ScheduledTask | Where-Object { $_.TaskName -like "ShopfloorMaintenance*" }
|
||||
|
||||
# Run now (don't wait for schedule)
|
||||
Start-ScheduledTask -TaskName "ShopfloorMaintenance-Reboot"
|
||||
|
||||
# Delete a task
|
||||
Unregister-ScheduledTask -TaskName "ShopfloorMaintenance-Reboot"
|
||||
|
||||
# Or use: Task Scheduler GUI (taskschd.msc)
|
||||
|
||||
|
||||
============================================================
|
||||
AVAILABLE TASKS
|
||||
============================================================
|
||||
|
||||
Reboot, DISM, SFC, OptimizeDisk, DiskCleanup,
|
||||
ClearUpdateCache, ClearBrowserCache, RestartSpooler,
|
||||
FlushDNS, RestartWinRM, SetTimezone, SyncTime,
|
||||
UpdateEMxAuthToken, DeployUDCWebServerConfig,
|
||||
UpdateDNCMXHosts,
|
||||
InstallDashboard, InstallLobbyDisplay,
|
||||
UninstallDashboard, UninstallLobbyDisplay
|
||||
|
||||
|
||||
============================================================
|
||||
@@ -88,13 +130,24 @@ TROUBLESHOOTING
|
||||
============================================================
|
||||
|
||||
"No saved credentials found"
|
||||
-> Run -SaveCredential with -Username and -Password
|
||||
-> .\Schedule-Maintenance.ps1 -SaveCredential -Username "DS\user" -Password "pass"
|
||||
|
||||
"Access is denied" when scheduling
|
||||
-> Right-click PowerShell -> Run as Administrator
|
||||
|
||||
"No credentials provided. Exiting."
|
||||
-> GUI prompt failed. Use -Username and -Password flags
|
||||
-> Use -Username and -Password flags instead of GUI prompt
|
||||
|
||||
No logs folder / empty logs
|
||||
-> Task may not have run yet. Check:
|
||||
Get-ScheduledTask -TaskName "ShopfloorMaintenance-Reboot" | Get-ScheduledTaskInfo
|
||||
|
||||
Password changed
|
||||
-> Re-run -SaveCredential with new password
|
||||
-> .\Schedule-Maintenance.ps1 -SaveCredential -Username "DS\user" -Password "newpass"
|
||||
|
||||
NOTE ABOUT "Running as AEROAD\SSO":
|
||||
This is normal. The scheduled task runs as your Windows login.
|
||||
It still uses your SAVED credentials for WinRM connections
|
||||
to the remote shopfloor PCs. Two separate accounts:
|
||||
1. Your Windows login = runs the script
|
||||
2. Saved credentials = connects to remote PCs
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
18
remote-execution/Resume-Download.bat
Normal file
18
remote-execution/Resume-Download.bat
Normal file
@@ -0,0 +1,18 @@
|
||||
@echo off
|
||||
:: Resume-Download — Resumable download for SharePoint and general URLs
|
||||
:: Uses domain credentials for SharePoint auth
|
||||
|
||||
if "%~1"=="" (
|
||||
set /p "URL=Enter download URL: "
|
||||
) else (
|
||||
set "URL=%~1"
|
||||
)
|
||||
|
||||
if "%~2"=="" (
|
||||
set /p "DEST=Save as (full path, e.g. C:\Temp\file.iso): "
|
||||
) else (
|
||||
set "DEST=%~2"
|
||||
)
|
||||
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0Resume-Download.ps1" -Url "%URL%" -Destination "%DEST%"
|
||||
pause
|
||||
117
remote-execution/Resume-Download.ps1
Normal file
117
remote-execution/Resume-Download.ps1
Normal file
@@ -0,0 +1,117 @@
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$Url,
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$Destination
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
Write-Host "============================================"
|
||||
Write-Host " Resumable File Download"
|
||||
Write-Host "============================================"
|
||||
Write-Host ""
|
||||
Write-Host "Destination: $Destination"
|
||||
Write-Host ""
|
||||
|
||||
try {
|
||||
# Check for partial file to resume
|
||||
$startBytes = 0
|
||||
if (Test-Path $Destination) {
|
||||
$startBytes = (Get-Item $Destination).Length
|
||||
if ($startBytes -gt 0) {
|
||||
Write-Host "Partial file found: $([math]::Round($startBytes / 1MB, 1)) MB already downloaded"
|
||||
Write-Host "Resuming..."
|
||||
Write-Host ""
|
||||
}
|
||||
}
|
||||
|
||||
# Ensure destination directory exists
|
||||
$destDir = Split-Path $Destination -Parent
|
||||
if ($destDir -and !(Test-Path $destDir)) {
|
||||
New-Item -ItemType Directory -Path $destDir -Force | Out-Null
|
||||
}
|
||||
|
||||
# Build headers
|
||||
$headers = @{}
|
||||
if ($startBytes -gt 0) {
|
||||
$headers["Range"] = "bytes=$startBytes-"
|
||||
}
|
||||
|
||||
# Use Invoke-WebRequest with domain credentials for SharePoint auth
|
||||
$params = @{
|
||||
Uri = $Url
|
||||
OutFile = $Destination
|
||||
UseDefaultCredentials = $true
|
||||
UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
||||
Headers = $headers
|
||||
UseBasicParsing = $true
|
||||
}
|
||||
|
||||
# If resuming, we need to handle appending manually
|
||||
if ($startBytes -gt 0) {
|
||||
# Download to a temp file, then append
|
||||
$tempFile = "$Destination.partial"
|
||||
$params.OutFile = $tempFile
|
||||
|
||||
Write-Host "Downloading..."
|
||||
Invoke-WebRequest @params
|
||||
|
||||
# Check if we got actual content
|
||||
$tempSize = (Get-Item $tempFile).Length
|
||||
if ($tempSize -eq 0) {
|
||||
Remove-Item $tempFile -Force
|
||||
Write-Host ""
|
||||
Write-Host "WARNING: Server returned 0 bytes. The URL may have expired."
|
||||
Write-Host "Get a fresh SharePoint link and try again."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Append to existing file
|
||||
$existingBytes = [System.IO.File]::ReadAllBytes($Destination)
|
||||
$newBytes = [System.IO.File]::ReadAllBytes($tempFile)
|
||||
$combined = New-Object byte[] ($existingBytes.Length + $newBytes.Length)
|
||||
[System.Buffer]::BlockCopy($existingBytes, 0, $combined, 0, $existingBytes.Length)
|
||||
[System.Buffer]::BlockCopy($newBytes, 0, $combined, $existingBytes.Length, $newBytes.Length)
|
||||
[System.IO.File]::WriteAllBytes($Destination, $combined)
|
||||
Remove-Item $tempFile -Force
|
||||
|
||||
$totalMB = [math]::Round(($existingBytes.Length + $newBytes.Length) / 1MB, 1)
|
||||
Write-Host ""
|
||||
Write-Host "Download complete: $totalMB MB saved to $Destination"
|
||||
}
|
||||
else {
|
||||
Write-Host "Downloading..."
|
||||
Invoke-WebRequest @params
|
||||
|
||||
# Verify we got actual content
|
||||
$fileSize = (Get-Item $Destination).Length
|
||||
if ($fileSize -eq 0) {
|
||||
Remove-Item $Destination -Force
|
||||
Write-Host ""
|
||||
Write-Host "WARNING: Downloaded 0 bytes. Possible causes:"
|
||||
Write-Host " - SharePoint URL expired or requires browser login"
|
||||
Write-Host " - URL is a redirect/login page, not the actual file"
|
||||
Write-Host ""
|
||||
Write-Host "Try this: In Edge, start the download, then go to"
|
||||
Write-Host "edge://downloads and copy the source URL from there."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$totalMB = [math]::Round($fileSize / 1MB, 1)
|
||||
Write-Host ""
|
||||
Write-Host "Download complete: $totalMB MB saved to $Destination"
|
||||
}
|
||||
|
||||
} catch {
|
||||
Write-Host ""
|
||||
Write-Host "Error: $_"
|
||||
Write-Host ""
|
||||
if (Test-Path $Destination) {
|
||||
$partialMB = [math]::Round((Get-Item $Destination).Length / 1MB, 1)
|
||||
Write-Host "Partial file kept: $partialMB MB"
|
||||
}
|
||||
Write-Host "Re-run with the same arguments to resume."
|
||||
exit 1
|
||||
}
|
||||
32
remote-execution/Run-UpdateDNCMXHosts.bat
Normal file
32
remote-execution/Run-UpdateDNCMXHosts.bat
Normal file
@@ -0,0 +1,32 @@
|
||||
@echo off
|
||||
REM Run-UpdateDNCMXHosts.bat
|
||||
REM Updates FtpHostPrimary/Secondary in DNC\MX registry on remote shopfloor PCs
|
||||
REM Usage:
|
||||
REM Run-UpdateDNCMXHosts.bat -> runs on all PCs in shopfloor-pcs.txt
|
||||
REM Run-UpdateDNCMXHosts.bat G5N9PWM3ESF -> runs on a single PC (for testing)
|
||||
|
||||
echo ============================================================
|
||||
echo UpdateDNCMXHosts - FTP Host Migration
|
||||
echo tsgwp00525.us.ae.ge.com -^> tsgwp00525.wjs.geaerospace.net
|
||||
echo ============================================================
|
||||
echo.
|
||||
|
||||
if not "%~1"=="" (
|
||||
echo Target: %~1
|
||||
echo.
|
||||
powershell.exe -ExecutionPolicy Bypass -File "%~dp0Invoke-RemoteMaintenance.ps1" -ComputerName "%~1" -Task UpdateDNCMXHosts -LogFile
|
||||
) else (
|
||||
if not exist "%~dp0shopfloor-pcs.txt" (
|
||||
echo ERROR: shopfloor-pcs.txt not found in %~dp0
|
||||
echo Either place shopfloor-pcs.txt in the same folder or pass a PC name:
|
||||
echo Run-UpdateDNCMXHosts.bat G5N9PWM3ESF
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo Target: shopfloor-pcs.txt
|
||||
echo.
|
||||
powershell.exe -ExecutionPolicy Bypass -File "%~dp0Invoke-RemoteMaintenance.ps1" -ComputerListFile "%~dp0shopfloor-pcs.txt" -Task UpdateDNCMXHosts -LogFile
|
||||
)
|
||||
|
||||
echo.
|
||||
pause
|
||||
@@ -68,11 +68,41 @@ param(
|
||||
[string]$Password
|
||||
)
|
||||
|
||||
$credFile = Join-Path $PSScriptRoot ".maintenance-cred.xml"
|
||||
$credDir = Join-Path $PSScriptRoot ".creds"
|
||||
$keyFile = Join-Path $credDir "aes.key"
|
||||
$userFile = Join-Path $credDir "username.txt"
|
||||
$passFile = Join-Path $credDir "password.txt"
|
||||
$scriptDir = $PSScriptRoot
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Save credentials (DPAPI - only decryptable by this user on this machine)
|
||||
# Helper: load or create AES key (works across all user contexts on this PC)
|
||||
# ---------------------------------------------------------------------------
|
||||
function Get-AESKey {
|
||||
if (-not (Test-Path $credDir)) {
|
||||
New-Item -Path $credDir -ItemType Directory -Force | Out-Null
|
||||
}
|
||||
if (Test-Path $keyFile) {
|
||||
return [byte[]](Get-Content $keyFile)
|
||||
}
|
||||
$key = New-Object byte[] 32
|
||||
[System.Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($key)
|
||||
$key | Set-Content $keyFile
|
||||
return $key
|
||||
}
|
||||
|
||||
function Load-SavedCredential {
|
||||
if (-not (Test-Path $userFile) -or -not (Test-Path $passFile)) {
|
||||
return $null
|
||||
}
|
||||
$key = Get-AESKey
|
||||
$user = Get-Content $userFile
|
||||
$encPass = Get-Content $passFile
|
||||
$secPass = $encPass | ConvertTo-SecureString -Key $key
|
||||
return New-Object System.Management.Automation.PSCredential($user, $secPass)
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Save credentials (AES key file - works from any user context on this PC)
|
||||
# ---------------------------------------------------------------------------
|
||||
if ($SaveCredential) {
|
||||
if ($Username -and $Password) {
|
||||
@@ -87,9 +117,11 @@ if ($SaveCredential) {
|
||||
Write-Host "No credentials provided. Exiting." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
$cred | Export-Clixml -Path $credFile
|
||||
Write-Host "Credentials saved to: $credFile" -ForegroundColor Green
|
||||
Write-Host "Encrypted with DPAPI - only YOUR user account on THIS machine can decrypt them." -ForegroundColor Yellow
|
||||
$key = Get-AESKey
|
||||
$cred.UserName | Set-Content $userFile
|
||||
$cred.Password | ConvertFrom-SecureString -Key $key | Set-Content $passFile
|
||||
Write-Host "Credentials saved to: $credDir" -ForegroundColor Green
|
||||
Write-Host "Encrypted with AES-256 key - works from any user context (normal or admin)." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host "You can now run tasks unattended:" -ForegroundColor Cyan
|
||||
Write-Host " .\Schedule-Maintenance.ps1 -ComputerListFile '.\shopfloor-pcs.txt' -Task Reboot"
|
||||
@@ -113,7 +145,7 @@ if ($CreateScheduledTask) {
|
||||
$absListFile = (Resolve-Path $ComputerListFile -ErrorAction Stop).Path
|
||||
$absScript = Join-Path $scriptDir "Schedule-Maintenance.ps1"
|
||||
|
||||
if (-not (Test-Path $credFile)) {
|
||||
if (-not (Test-Path $passFile)) {
|
||||
Write-Host "ERROR: No saved credentials found. Run with -SaveCredential first." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
@@ -177,20 +209,13 @@ if (-not $ComputerListFile) {
|
||||
}
|
||||
|
||||
# Load saved credentials
|
||||
if (-not (Test-Path $credFile)) {
|
||||
Write-Host "ERROR: No saved credentials found at $credFile" -ForegroundColor Red
|
||||
$cred = Load-SavedCredential
|
||||
if (-not $cred) {
|
||||
Write-Host "ERROR: No saved credentials found in $credDir" -ForegroundColor Red
|
||||
Write-Host "Run with -SaveCredential first to store credentials." -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
try {
|
||||
$cred = Import-Clixml -Path $credFile
|
||||
Write-Host "Loaded saved credentials for: $($cred.UserName)" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host "ERROR: Failed to load credentials: $_" -ForegroundColor Red
|
||||
Write-Host "Re-run with -SaveCredential to save new credentials." -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
Write-Host "Loaded saved credentials for: $($cred.UserName)" -ForegroundColor Green
|
||||
|
||||
# Log output
|
||||
$logDir = Join-Path $scriptDir "logs"
|
||||
@@ -202,7 +227,41 @@ Write-Host "Log: $logFile" -ForegroundColor Gray
|
||||
|
||||
$mainScript = Join-Path $scriptDir "Invoke-RemoteMaintenance.ps1"
|
||||
|
||||
& $mainScript -ComputerListFile $ComputerListFile -Task $Task -Credential $cred 2>&1 | Tee-Object -FilePath $logFile
|
||||
Start-Transcript -Path $logFile -Force | Out-Null
|
||||
& $mainScript -ComputerListFile $ComputerListFile -Task $Task -Credential $cred
|
||||
Stop-Transcript | Out-Null
|
||||
|
||||
# Parse log for results summary
|
||||
$logContent = Get-Content $logFile -ErrorAction SilentlyContinue
|
||||
$okPCs = @($logContent | Select-String '\[OK\]\s+(\S+)' | ForEach-Object { $_.Matches[0].Groups[1].Value })
|
||||
$failPCs = @($logContent | Select-String '\[FAIL\]\s+(\S+)' | ForEach-Object { $_.Matches[0].Groups[1].Value } | Where-Object { $_ -ne ':' } | Sort-Object -Unique)
|
||||
|
||||
$summaryFile = Join-Path $logDir "LAST-RUN-SUMMARY.txt"
|
||||
$summary = @()
|
||||
$summary += "============================================"
|
||||
$summary += " MAINTENANCE RESULTS - $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
|
||||
$summary += " Task: $Task"
|
||||
$summary += "============================================"
|
||||
$summary += ""
|
||||
$summary += "SUCCEEDED: $($okPCs.Count)"
|
||||
foreach ($pc in $okPCs) { $summary += " [OK] $pc" }
|
||||
$summary += ""
|
||||
$summary += "FAILED: $($failPCs.Count)"
|
||||
foreach ($pc in $failPCs) { $summary += " [FAIL] $pc" }
|
||||
$summary += ""
|
||||
$summary += "Full log: $logFile"
|
||||
|
||||
$summary | Out-File -FilePath $summaryFile -Encoding UTF8
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Complete. Log saved to: $logFile" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "=== RESULTS ===" -ForegroundColor White
|
||||
Write-Host " Succeeded: $($okPCs.Count)" -ForegroundColor Green
|
||||
foreach ($pc in $okPCs) { Write-Host " $pc" -ForegroundColor Green }
|
||||
if ($failPCs.Count -gt 0) {
|
||||
Write-Host " Failed: $($failPCs.Count)" -ForegroundColor Red
|
||||
foreach ($pc in $failPCs) { Write-Host " $pc" -ForegroundColor Red }
|
||||
}
|
||||
Write-Host ""
|
||||
Write-Host "Summary also saved to: $summaryFile" -ForegroundColor Yellow
|
||||
|
||||
279
remote-execution/udc/UDC_Update.ps1
Executable file
279
remote-execution/udc/UDC_Update.ps1
Executable file
@@ -0,0 +1,279 @@
|
||||
# ============================================
|
||||
# UDC Application Update Script (PowerShell)
|
||||
# ============================================
|
||||
|
||||
param(
|
||||
[string]$Version
|
||||
)
|
||||
|
||||
# Set error action preference
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Set variables
|
||||
$UDC_PATH = "C:\Program Files\UDC"
|
||||
$VERSION_FILE = "S:\SPC\UDC\UDC_Update.txt"
|
||||
$LOG_DIR = "S:\DT\Cameron\UDC\logs"
|
||||
|
||||
# Get hostname and timestamp
|
||||
$HOSTNAME = $env:COMPUTERNAME
|
||||
$TIMESTAMP = Get-Date -Format "yyyyMMdd_HHmmss"
|
||||
$LOG_FILE = "$LOG_DIR\logs_$($HOSTNAME)_$TIMESTAMP.txt"
|
||||
|
||||
# Flag to track if we should skip the update
|
||||
$SKIP_UPDATE = $false
|
||||
|
||||
# Function to write logs
|
||||
function Write-Log {
|
||||
param([string]$Message)
|
||||
$LogMessage = "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] $Message"
|
||||
Write-Host $Message
|
||||
Add-Content -Path $LOG_FILE -Value $LogMessage -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# Create log directory if needed
|
||||
try {
|
||||
if (-not (Test-Path $LOG_DIR)) {
|
||||
New-Item -Path $LOG_DIR -ItemType Directory -Force | Out-Null
|
||||
}
|
||||
} catch {
|
||||
Write-Host "WARNING: Cannot access log directory $LOG_DIR"
|
||||
Write-Host "Update will continue but logging is disabled."
|
||||
}
|
||||
|
||||
Write-Log "============================================"
|
||||
Write-Log "UDC Update Script Started on $HOSTNAME"
|
||||
Write-Log "============================================"
|
||||
|
||||
# ============================================
|
||||
# Step 1: Check if PPMon.exe or ppdcs.exe are running
|
||||
# ============================================
|
||||
Write-Host "Checking for conflicting processes..."
|
||||
$conflictingProcesses = Get-Process -Name "PPMon", "ppdcs" -ErrorAction SilentlyContinue
|
||||
|
||||
if ($conflictingProcesses) {
|
||||
foreach ($proc in $conflictingProcesses) {
|
||||
Write-Log "$($proc.Name).exe is running. Skipping update process."
|
||||
Write-Host "$($proc.Name).exe is running. Update will be skipped."
|
||||
}
|
||||
$SKIP_UPDATE = $true
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Step 2: Check if UDC directory exists
|
||||
# ============================================
|
||||
Write-Host "Checking if UDC is installed..."
|
||||
if (-not (Test-Path $UDC_PATH)) {
|
||||
Write-Log "UDC directory not found. Exiting."
|
||||
Write-Host "UDC is not installed on this machine."
|
||||
exit 0
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Step 3: Check if UDC.exe exists
|
||||
# ============================================
|
||||
$UDC_EXE = "$UDC_PATH\UDC.exe"
|
||||
if (-not (Test-Path $UDC_EXE)) {
|
||||
Write-Log "UDC.exe not found. Exiting."
|
||||
Write-Host "UDC.exe not found in installation directory."
|
||||
exit 0
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# ONLY PROCEED WITH UPDATE IF NOT SKIPPED
|
||||
# ============================================
|
||||
if (-not $SKIP_UPDATE) {
|
||||
|
||||
# ============================================
|
||||
# Step 4: Determine target version
|
||||
# ============================================
|
||||
if ($Version) {
|
||||
$NETWORK_VERSION = $Version
|
||||
Write-Host "Using override version: $NETWORK_VERSION"
|
||||
Write-Log "Override version specified: $NETWORK_VERSION"
|
||||
} else {
|
||||
if (-not (Test-Path $VERSION_FILE)) {
|
||||
Write-Log "Version file not found: $VERSION_FILE"
|
||||
Write-Host "ERROR: Version file not found."
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "Reading version information..."
|
||||
try {
|
||||
$versionContent = Get-Content $VERSION_FILE | Where-Object { $_ -match "Version:" }
|
||||
$NETWORK_VERSION = ($versionContent -split ":")[1].Trim()
|
||||
|
||||
if ([string]::IsNullOrEmpty($NETWORK_VERSION)) {
|
||||
throw "Could not parse network version"
|
||||
}
|
||||
|
||||
Write-Host "Network version: $NETWORK_VERSION"
|
||||
Write-Log "Network version: $NETWORK_VERSION"
|
||||
} catch {
|
||||
Write-Log "Could not read network version. Error: $_"
|
||||
Write-Host "ERROR: Could not determine network version."
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Build source path from version
|
||||
$SOURCE_PATH = "S:\SPC\UDC\UDC_$NETWORK_VERSION"
|
||||
|
||||
# ============================================
|
||||
# Step 5: Check if source files exist
|
||||
# ============================================
|
||||
if (-not (Test-Path $SOURCE_PATH)) {
|
||||
Write-Log "Source path not found: $SOURCE_PATH"
|
||||
Write-Host "ERROR: Update source files not found at $SOURCE_PATH"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Step 6: Get local UDC.exe version
|
||||
# ============================================
|
||||
try {
|
||||
$fileVersion = (Get-Item $UDC_EXE).VersionInfo.FileVersion
|
||||
# Trim to 3 parts if it has 4 (e.g., 1.0.30.0 -> 1.0.30)
|
||||
$versionParts = $fileVersion.Split('.')
|
||||
if ($versionParts.Count -eq 4) {
|
||||
$LOCAL_VERSION = "$($versionParts[0]).$($versionParts[1]).$($versionParts[2])"
|
||||
} else {
|
||||
$LOCAL_VERSION = $fileVersion
|
||||
}
|
||||
|
||||
Write-Host "Local version: $LOCAL_VERSION"
|
||||
Write-Log "Local version: $LOCAL_VERSION"
|
||||
} catch {
|
||||
Write-Log "Could not read local version. Error: $_"
|
||||
Write-Host "ERROR: Could not determine local version."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Step 7: Compare versions
|
||||
# ============================================
|
||||
try {
|
||||
$netVer = [version]$NETWORK_VERSION
|
||||
$localVer = [version]$LOCAL_VERSION
|
||||
|
||||
if ($netVer -eq $localVer) {
|
||||
Write-Log "Versions match ($LOCAL_VERSION). No update needed."
|
||||
Write-Host "Version is current. No update required."
|
||||
$SKIP_UPDATE = $true
|
||||
}
|
||||
elseif ($netVer -le $localVer) {
|
||||
Write-Log "Network version ($NETWORK_VERSION) not newer than local ($LOCAL_VERSION)."
|
||||
Write-Host "Local version is current or newer. No update required."
|
||||
$SKIP_UPDATE = $true
|
||||
}
|
||||
else {
|
||||
Write-Host "Update required: $LOCAL_VERSION -> $NETWORK_VERSION"
|
||||
Write-Log "Update required: $LOCAL_VERSION -> $NETWORK_VERSION"
|
||||
}
|
||||
} catch {
|
||||
Write-Log "Error comparing versions. Error: $_"
|
||||
Write-Host "ERROR: Could not compare versions."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Step 8: Perform update if needed
|
||||
# ============================================
|
||||
if (-not $SKIP_UPDATE) {
|
||||
|
||||
# Kill UDC.exe if running
|
||||
Write-Host "Checking if UDC.exe is running..."
|
||||
$udcProcess = Get-Process -Name "UDC" -ErrorAction SilentlyContinue
|
||||
|
||||
if ($udcProcess) {
|
||||
Write-Host "UDC.exe is running. Stopping process..."
|
||||
Write-Log "Killing UDC.exe"
|
||||
try {
|
||||
Stop-Process -Name "UDC" -Force
|
||||
Start-Sleep -Seconds 2
|
||||
Write-Log "UDC.exe stopped successfully"
|
||||
} catch {
|
||||
Write-Log "Warning: Could not stop UDC.exe. Error: $_"
|
||||
Write-Host "WARNING: Could not stop UDC.exe"
|
||||
}
|
||||
}
|
||||
|
||||
# Copy update files
|
||||
Write-Host "Copying update files..."
|
||||
Write-Log "Copying files from $SOURCE_PATH to $UDC_PATH"
|
||||
|
||||
try {
|
||||
# Use robocopy for better performance and logging
|
||||
$robocopyArgs = @(
|
||||
$SOURCE_PATH,
|
||||
$UDC_PATH,
|
||||
"/E", # Copy subdirectories including empty ones
|
||||
"/NFL", # No file list
|
||||
"/NDL", # No directory list
|
||||
"/NJH", # No job header
|
||||
"/NJS", # No job summary
|
||||
"/NC", # No class
|
||||
"/NS", # No size
|
||||
"/NP" # No progress
|
||||
)
|
||||
|
||||
$result = robocopy @robocopyArgs
|
||||
|
||||
# Robocopy exit codes: 0-7 are success, 8+ are errors
|
||||
if ($LASTEXITCODE -ge 8) {
|
||||
throw "Robocopy failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
|
||||
Write-Host "Files copied successfully."
|
||||
Write-Log "Files copied successfully."
|
||||
} catch {
|
||||
Write-Log "ERROR: File copy failed. Error: $_"
|
||||
Write-Host "ERROR: Failed to copy update files."
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Step 9: Ensure UDC.exe is running (ONLY if no conflicting processes)
|
||||
# ============================================
|
||||
# Re-check for conflicting processes before starting UDC
|
||||
$conflictingProcesses = Get-Process -Name "PPMon", "ppdcs" -ErrorAction SilentlyContinue
|
||||
|
||||
if ($conflictingProcesses) {
|
||||
Write-Log "Conflicting processes still running. Will not start UDC.exe"
|
||||
Write-Host "Conflicting processes detected. UDC.exe will not be started."
|
||||
} else {
|
||||
Write-Host "Verifying UDC.exe is running..."
|
||||
$udcProcess = Get-Process -Name "UDC" -ErrorAction SilentlyContinue
|
||||
|
||||
if (-not $udcProcess) {
|
||||
Write-Host "UDC.exe is not running. Starting it now..."
|
||||
Write-Log "UDC.exe not running. Starting UDC.exe"
|
||||
|
||||
try {
|
||||
Start-Process -FilePath $UDC_EXE
|
||||
Start-Sleep -Seconds 2
|
||||
|
||||
# Verify it started
|
||||
$udcProcess = Get-Process -Name "UDC" -ErrorAction SilentlyContinue
|
||||
|
||||
if ($udcProcess) {
|
||||
Write-Log "UDC.exe started successfully."
|
||||
Write-Host "UDC.exe started successfully!"
|
||||
} else {
|
||||
Write-Log "WARNING: UDC.exe may not have started."
|
||||
Write-Host "WARNING: UDC.exe may not have started properly."
|
||||
}
|
||||
} catch {
|
||||
Write-Log "WARNING: Could not start UDC.exe. Error: $_"
|
||||
Write-Host "WARNING: Could not start UDC.exe"
|
||||
}
|
||||
} else {
|
||||
Write-Log "UDC.exe is already running."
|
||||
Write-Host "UDC.exe is already running."
|
||||
}
|
||||
}
|
||||
|
||||
Write-Log "UDC Update Script Completed"
|
||||
Write-Log "============================================"
|
||||
exit 0
|
||||
8
remote-execution/udc/udc_update.bat
Executable file
8
remote-execution/udc/udc_update.bat
Executable file
@@ -0,0 +1,8 @@
|
||||
@echo off
|
||||
:: ============================================
|
||||
:: UDC Update PowerShell Launcher
|
||||
:: ============================================
|
||||
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -Command "& {Start-Process PowerShell -ArgumentList '-NoProfile -ExecutionPolicy Bypass -File ""U:/Scripts/UDC/UDC_Update.ps1""' -Verb RunAs}"
|
||||
|
||||
exit /b %ERRORLEVEL%
|
||||
11
remote-execution/udc/udc_update_override.bat
Normal file
11
remote-execution/udc/udc_update_override.bat
Normal file
@@ -0,0 +1,11 @@
|
||||
@echo off
|
||||
:: ============================================
|
||||
:: UDC Update Override Launcher
|
||||
:: Edit the version below to force a specific update
|
||||
:: ============================================
|
||||
|
||||
set "UDC_VERSION=1.0.32"
|
||||
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "& 'U:/Scripts/UDC/UDC_Update.ps1' -Version '%UDC_VERSION%'"
|
||||
|
||||
exit /b %ERRORLEVEL%
|
||||
Reference in New Issue
Block a user