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:
535
winrm-setup-package/Invoke-RemoteTask.ps1
Normal file
535
winrm-setup-package/Invoke-RemoteTask.ps1
Normal file
@@ -0,0 +1,535 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Simple remote maintenance toolkit for Windows PCs via WinRM.
|
||||
|
||||
.DESCRIPTION
|
||||
Executes maintenance tasks on remote Windows PCs using WinRM.
|
||||
Reads target computers from a text file (one hostname/IP per line).
|
||||
|
||||
.PARAMETER HostsFile
|
||||
Path to text file containing computer names/IPs (one per line).
|
||||
Lines starting with # are treated as comments.
|
||||
Default: .\hosts.txt
|
||||
|
||||
.PARAMETER ComputerName
|
||||
Single computer name or IP address (alternative to HostsFile).
|
||||
|
||||
.PARAMETER Task
|
||||
Maintenance task to execute. Available tasks:
|
||||
- RestartSpooler : Restart Print Spooler service
|
||||
- FlushDNS : Clear DNS resolver cache
|
||||
- ClearTempFiles : Clear Windows temp files
|
||||
- DiskCleanup : Run Windows Disk Cleanup
|
||||
- OptimizeDisk : TRIM (SSD) or Defrag (HDD)
|
||||
- SyncTime : Force time sync with domain controller
|
||||
- RestartService : Restart a specific Windows service
|
||||
- RunCommand : Run a custom command
|
||||
- RestartComputer : Restart the remote PC (requires confirmation)
|
||||
|
||||
.PARAMETER ServiceName
|
||||
Service name for RestartService task.
|
||||
|
||||
.PARAMETER Command
|
||||
Custom command for RunCommand task.
|
||||
|
||||
.PARAMETER Credential
|
||||
PSCredential for remote authentication. Prompts if not provided.
|
||||
|
||||
.PARAMETER DnsSuffix
|
||||
DNS suffix to append to hostnames (if not already FQDN).
|
||||
Default: logon.ds.ge.com
|
||||
|
||||
.PARAMETER ThrottleLimit
|
||||
Maximum number of concurrent remote connections.
|
||||
Default: 10
|
||||
|
||||
.PARAMETER LogResults
|
||||
Save results to a timestamped log file in the script directory.
|
||||
Log files are saved as: RemoteTask_YYYYMMDD_HHMMSS.log
|
||||
|
||||
.EXAMPLE
|
||||
# Restart print spooler on all hosts in hosts.txt
|
||||
.\Invoke-RemoteTask.ps1 -Task RestartSpooler
|
||||
|
||||
.EXAMPLE
|
||||
# Flush DNS on a single computer
|
||||
.\Invoke-RemoteTask.ps1 -ComputerName "PC001" -Task FlushDNS
|
||||
|
||||
.EXAMPLE
|
||||
# Run disk cleanup on hosts from custom file
|
||||
.\Invoke-RemoteTask.ps1 -HostsFile ".\shopfloor-pcs.txt" -Task DiskCleanup
|
||||
|
||||
.EXAMPLE
|
||||
# Restart a specific service
|
||||
.\Invoke-RemoteTask.ps1 -Task RestartService -ServiceName "Spooler"
|
||||
|
||||
.EXAMPLE
|
||||
# Run custom command
|
||||
.\Invoke-RemoteTask.ps1 -Task RunCommand -Command "Get-Process | Select -First 5"
|
||||
|
||||
.NOTES
|
||||
Author: Shop Floor Tools
|
||||
Requirements: PowerShell 5.1+, WinRM enabled on targets
|
||||
#>
|
||||
|
||||
[CmdletBinding(DefaultParameterSetName='ByFile')]
|
||||
param(
|
||||
[Parameter(ParameterSetName='ByFile')]
|
||||
[string]$HostsFile = ".\hosts.txt",
|
||||
|
||||
[Parameter(ParameterSetName='ByName')]
|
||||
[string[]]$ComputerName,
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
[ValidateSet(
|
||||
'RestartSpooler', 'FlushDNS', 'ClearTempFiles', 'DiskCleanup',
|
||||
'OptimizeDisk', 'SyncTime', 'RestartService', 'RunCommand',
|
||||
'GetDiskSpace', 'GetUptime', 'TestConnection', 'RestartComputer'
|
||||
)]
|
||||
[string]$Task,
|
||||
|
||||
[Parameter()]
|
||||
[string]$ServiceName,
|
||||
|
||||
[Parameter()]
|
||||
[string]$Command,
|
||||
|
||||
[Parameter()]
|
||||
[PSCredential]$Credential,
|
||||
|
||||
[Parameter()]
|
||||
[string]$DnsSuffix = "logon.ds.ge.com",
|
||||
|
||||
[Parameter()]
|
||||
[int]$ThrottleLimit = 10,
|
||||
|
||||
[Parameter()]
|
||||
[switch]$LogResults
|
||||
)
|
||||
|
||||
# =============================================================================
|
||||
# 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
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Task Scriptblocks
|
||||
# =============================================================================
|
||||
|
||||
$TaskScripts = @{
|
||||
|
||||
'RestartSpooler' = {
|
||||
$result = @{ Success = $false; Hostname = $env:COMPUTERNAME; Output = ""; Error = $null }
|
||||
try {
|
||||
Stop-Service -Name Spooler -Force -ErrorAction Stop
|
||||
$queuePath = "$env:SystemRoot\System32\spool\PRINTERS"
|
||||
if (Test-Path $queuePath) { Remove-Item "$queuePath\*" -Force -ErrorAction SilentlyContinue }
|
||||
Start-Service -Name Spooler -ErrorAction Stop
|
||||
$status = (Get-Service -Name Spooler).Status
|
||||
$result.Success = ($status -eq 'Running')
|
||||
$result.Output = "Print Spooler restarted. Status: $status"
|
||||
} catch { $result.Error = $_.Exception.Message }
|
||||
return $result
|
||||
}
|
||||
|
||||
'FlushDNS' = {
|
||||
$result = @{ Success = $false; Hostname = $env:COMPUTERNAME; Output = ""; Error = $null }
|
||||
try {
|
||||
$flushResult = & ipconfig /flushdns 2>&1
|
||||
$result.Output = ($flushResult -join " ").Trim()
|
||||
$result.Success = $true
|
||||
} catch { $result.Error = $_.Exception.Message }
|
||||
return $result
|
||||
}
|
||||
|
||||
'ClearTempFiles' = {
|
||||
$result = @{ Success = $false; Hostname = $env:COMPUTERNAME; Output = ""; Error = $null; FilesDeleted = 0 }
|
||||
try {
|
||||
$tempPaths = @("$env:TEMP", "$env:SystemRoot\Temp")
|
||||
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; $result.FilesDeleted++ } catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
$result.Success = $true
|
||||
$result.Output = "Deleted $($result.FilesDeleted) temp files"
|
||||
} catch { $result.Error = $_.Exception.Message }
|
||||
return $result
|
||||
}
|
||||
|
||||
'DiskCleanup' = {
|
||||
$result = @{ Success = $false; Hostname = $env:COMPUTERNAME; Output = ""; Error = $null; SpaceFreedMB = 0 }
|
||||
try {
|
||||
$initialFree = (Get-PSDrive C).Free
|
||||
$cleanupPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches"
|
||||
$categories = @("Temporary Files", "Temporary Setup Files", "Old ChkDsk Files", "Windows Update Cleanup", "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 }
|
||||
}
|
||||
Start-Process -FilePath "cleanmgr.exe" -ArgumentList "/sagerun:100" -Wait -WindowStyle Hidden
|
||||
Start-Sleep -Seconds 2
|
||||
$finalFree = (Get-PSDrive C).Free
|
||||
$result.SpaceFreedMB = [math]::Round(($finalFree - $initialFree) / 1MB, 0)
|
||||
$result.Success = $true
|
||||
$result.Output = "Disk cleanup completed. Space freed: $($result.SpaceFreedMB) MB"
|
||||
} catch { $result.Error = $_.Exception.Message }
|
||||
return $result
|
||||
}
|
||||
|
||||
'OptimizeDisk' = {
|
||||
$result = @{ Success = $false; Hostname = $env:COMPUTERNAME; Output = ""; Error = $null }
|
||||
try {
|
||||
$volumes = Get-Volume | Where-Object { $_.DriveType -eq 'Fixed' -and $_.DriveLetter }
|
||||
$optimized = @()
|
||||
foreach ($vol in $volumes) {
|
||||
$driveLetter = $vol.DriveLetter
|
||||
$physicalDisk = Get-PhysicalDisk | Where-Object { $_.DeviceId -eq (Get-Partition -DriveLetter $driveLetter -ErrorAction SilentlyContinue).DiskNumber }
|
||||
$mediaType = if ($physicalDisk) { $physicalDisk.MediaType } else { "Unknown" }
|
||||
try {
|
||||
if ($mediaType -eq 'SSD') { Optimize-Volume -DriveLetter $driveLetter -ReTrim -ErrorAction Stop; $action = "TRIM" }
|
||||
else { Optimize-Volume -DriveLetter $driveLetter -Defrag -ErrorAction Stop; $action = "Defrag" }
|
||||
$optimized += "${driveLetter}:($action)"
|
||||
} catch { $optimized += "${driveLetter}:(Failed)" }
|
||||
}
|
||||
$result.Success = $true
|
||||
$result.Output = "Optimized: $($optimized -join ', ')"
|
||||
} catch { $result.Error = $_.Exception.Message }
|
||||
return $result
|
||||
}
|
||||
|
||||
'SyncTime' = {
|
||||
$result = @{ Success = $false; Hostname = $env:COMPUTERNAME; Output = ""; Error = $null }
|
||||
try {
|
||||
$source = (& w32tm /query /source 2>&1) -join " "
|
||||
$syncResult = & w32tm /resync /force 2>&1
|
||||
$result.Success = ($syncResult -match "success" -or $LASTEXITCODE -eq 0)
|
||||
$result.Output = "Time synced with $source. Current: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
|
||||
} catch { $result.Error = $_.Exception.Message }
|
||||
return $result
|
||||
}
|
||||
|
||||
'RestartService' = {
|
||||
param($ServiceName)
|
||||
$result = @{ Success = $false; Hostname = $env:COMPUTERNAME; Output = ""; Error = $null }
|
||||
if (-not $ServiceName) { $result.Error = "ServiceName parameter required"; return $result }
|
||||
try {
|
||||
Restart-Service -Name $ServiceName -Force -ErrorAction Stop
|
||||
$status = (Get-Service -Name $ServiceName).Status
|
||||
$result.Success = ($status -eq 'Running')
|
||||
$result.Output = "Service '$ServiceName' restarted. Status: $status"
|
||||
} catch { $result.Error = $_.Exception.Message }
|
||||
return $result
|
||||
}
|
||||
|
||||
'RunCommand' = {
|
||||
param($Command)
|
||||
$result = @{ Success = $false; Hostname = $env:COMPUTERNAME; Output = ""; Error = $null }
|
||||
if (-not $Command) { $result.Error = "Command parameter required"; return $result }
|
||||
try {
|
||||
$output = Invoke-Expression $Command 2>&1
|
||||
$result.Output = ($output | Out-String).Trim()
|
||||
$result.Success = $true
|
||||
} catch { $result.Error = $_.Exception.Message }
|
||||
return $result
|
||||
}
|
||||
|
||||
'GetDiskSpace' = {
|
||||
$result = @{ Success = $false; Hostname = $env:COMPUTERNAME; Output = ""; Error = $null }
|
||||
try {
|
||||
$drives = Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Used -ne $null }
|
||||
$info = foreach ($drive in $drives) {
|
||||
$freeGB = [math]::Round($drive.Free / 1GB, 1)
|
||||
$usedGB = [math]::Round($drive.Used / 1GB, 1)
|
||||
$totalGB = $freeGB + $usedGB
|
||||
$pctFree = if ($totalGB -gt 0) { [math]::Round(($freeGB / $totalGB) * 100, 0) } else { 0 }
|
||||
"$($drive.Name): $freeGB GB free ($pctFree%)"
|
||||
}
|
||||
$result.Output = $info -join ", "
|
||||
$result.Success = $true
|
||||
} catch { $result.Error = $_.Exception.Message }
|
||||
return $result
|
||||
}
|
||||
|
||||
'GetUptime' = {
|
||||
$result = @{ Success = $false; Hostname = $env:COMPUTERNAME; Output = ""; Error = $null }
|
||||
try {
|
||||
$os = Get-CimInstance -ClassName Win32_OperatingSystem
|
||||
$uptime = (Get-Date) - $os.LastBootUpTime
|
||||
$result.Output = "Up $([math]::Floor($uptime.TotalDays))d $($uptime.Hours)h $($uptime.Minutes)m (Last boot: $($os.LastBootUpTime.ToString('yyyy-MM-dd HH:mm')))"
|
||||
$result.Success = $true
|
||||
} catch { $result.Error = $_.Exception.Message }
|
||||
return $result
|
||||
}
|
||||
|
||||
'TestConnection' = {
|
||||
$result = @{ Success = $true; Hostname = $env:COMPUTERNAME; Output = "Connection successful"; Error = $null }
|
||||
return $result
|
||||
}
|
||||
|
||||
'RestartComputer' = {
|
||||
$result = @{ Success = $false; Hostname = $env:COMPUTERNAME; Output = ""; Error = $null }
|
||||
try {
|
||||
$result.Output = "Restart initiated"
|
||||
$result.Success = $true
|
||||
# Schedule restart in 5 seconds to allow response to return
|
||||
Start-Process -FilePath "shutdown.exe" -ArgumentList "/r /t 5 /c `"Remote restart initiated via WinRM`"" -NoNewWindow
|
||||
} catch { $result.Error = $_.Exception.Message }
|
||||
return $result
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Main Execution
|
||||
# =============================================================================
|
||||
|
||||
Write-Host ""
|
||||
Write-Host ("=" * 60) -ForegroundColor Cyan
|
||||
Write-Host " Remote Task Executor - Task: $Task" -ForegroundColor Cyan
|
||||
Write-Host ("=" * 60) -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Validate task-specific parameters
|
||||
if ($Task -eq 'RestartService' -and -not $ServiceName) {
|
||||
Write-Log "ServiceName parameter is required for RestartService task" -Level "ERROR"
|
||||
exit 1
|
||||
}
|
||||
if ($Task -eq 'RunCommand' -and -not $Command) {
|
||||
Write-Log "Command parameter is required for RunCommand task" -Level "ERROR"
|
||||
exit 1
|
||||
}
|
||||
if ($Task -eq 'RestartComputer') {
|
||||
Write-Host ""
|
||||
Write-Host "WARNING: This will restart the target computer(s)!" -ForegroundColor Yellow
|
||||
$confirm = Read-Host "Type 'YES' to confirm"
|
||||
if ($confirm -ne 'YES') {
|
||||
Write-Log "Restart cancelled by user" -Level "WARNING"
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
|
||||
# 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 ($ComputerName) {
|
||||
$computers = $ComputerName
|
||||
} else {
|
||||
if (-not (Test-Path $HostsFile)) {
|
||||
Write-Log "Hosts file not found: $HostsFile" -Level "ERROR"
|
||||
Write-Log "Create a text file with one hostname or IP per line." -Level "INFO"
|
||||
exit 1
|
||||
}
|
||||
$computers = Get-Content $HostsFile | Where-Object { $_.Trim() -and -not $_.StartsWith("#") }
|
||||
}
|
||||
|
||||
if ($computers.Count -eq 0) {
|
||||
Write-Log "No computers specified." -Level "ERROR"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Log "Target computers: $($computers.Count)" -Level "INFO"
|
||||
Write-Host ""
|
||||
|
||||
# Build FQDNs if DNS suffix provided
|
||||
$targets = $computers | ForEach-Object {
|
||||
$name = $_.Trim()
|
||||
if ($DnsSuffix -and $name -notlike "*.*") {
|
||||
"$name.$DnsSuffix"
|
||||
} else {
|
||||
$name
|
||||
}
|
||||
}
|
||||
|
||||
# Get the scriptblock
|
||||
$scriptBlock = $TaskScripts[$Task]
|
||||
|
||||
# Create session options
|
||||
$sessionOption = New-PSSessionOption -OpenTimeout 30000 -OperationTimeout 300000 -NoMachineProfile
|
||||
|
||||
Write-Log "Executing on $($targets.Count) computer(s) in parallel (ThrottleLimit: $ThrottleLimit)..." -Level "INFO"
|
||||
Write-Host ""
|
||||
|
||||
# Build arguments for tasks that need them
|
||||
$taskArgs = @()
|
||||
if ($Task -eq 'RestartService') { $taskArgs = @($ServiceName) }
|
||||
if ($Task -eq 'RunCommand') { $taskArgs = @($Command) }
|
||||
|
||||
# Show progress indicator
|
||||
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
|
||||
Write-Host " [" -NoNewline
|
||||
Write-Host "Running..." -ForegroundColor Yellow -NoNewline
|
||||
Write-Host "] Please wait..." -NoNewline
|
||||
|
||||
# Execute on all remote computers in parallel using Invoke-Command
|
||||
$results = @()
|
||||
$connectionErrors = @()
|
||||
|
||||
try {
|
||||
if ($taskArgs.Count -gt 0) {
|
||||
$results = Invoke-Command -ComputerName $targets -ScriptBlock $scriptBlock -ArgumentList $taskArgs `
|
||||
-Credential $Credential -SessionOption $sessionOption -Authentication Negotiate `
|
||||
-ThrottleLimit $ThrottleLimit -ErrorAction SilentlyContinue -ErrorVariable connectionErrors
|
||||
} else {
|
||||
$results = Invoke-Command -ComputerName $targets -ScriptBlock $scriptBlock `
|
||||
-Credential $Credential -SessionOption $sessionOption -Authentication Negotiate `
|
||||
-ThrottleLimit $ThrottleLimit -ErrorAction SilentlyContinue -ErrorVariable connectionErrors
|
||||
}
|
||||
} catch {
|
||||
Write-Host ""
|
||||
Write-Log "Execution error: $($_.Exception.Message)" -Level "ERROR"
|
||||
}
|
||||
|
||||
$stopwatch.Stop()
|
||||
$elapsed = $stopwatch.Elapsed.TotalSeconds
|
||||
|
||||
# Clear the progress line
|
||||
Write-Host "`r" -NoNewline
|
||||
Write-Host (" " * 60) -NoNewline
|
||||
Write-Host "`r" -NoNewline
|
||||
Write-Log "Completed in $([math]::Round($elapsed, 1)) seconds" -Level "INFO"
|
||||
Write-Host ""
|
||||
|
||||
# Collect all results (successes, failures, connection errors)
|
||||
$allResults = @()
|
||||
$successCount = 0
|
||||
$failCount = 0
|
||||
|
||||
foreach ($result in $results) {
|
||||
$status = if ($result.Success) { "OK"; $successCount++ } else { "FAIL"; $failCount++ }
|
||||
$message = if ($result.Success) { $result.Output } else { $result.Error }
|
||||
$allResults += [PSCustomObject]@{
|
||||
Status = $status
|
||||
Computer = $result.Hostname
|
||||
Message = $message
|
||||
}
|
||||
}
|
||||
|
||||
# Process connection errors
|
||||
foreach ($err in $connectionErrors) {
|
||||
$targetName = if ($err.TargetObject) { $err.TargetObject } else { "Unknown" }
|
||||
# Extract just the computer name from FQDN for display
|
||||
$shortName = ($targetName -split '\.')[0]
|
||||
$errorMsg = $err.Exception.Message -replace '\r?\n', ' '
|
||||
# Truncate long error messages
|
||||
if ($errorMsg.Length -gt 60) { $errorMsg = $errorMsg.Substring(0, 57) + "..." }
|
||||
$allResults += [PSCustomObject]@{
|
||||
Status = "FAIL"
|
||||
Computer = $shortName
|
||||
Message = $errorMsg
|
||||
}
|
||||
$failCount++
|
||||
}
|
||||
|
||||
# Sort results: failures first, then successes
|
||||
$allResults = $allResults | Sort-Object @{Expression={$_.Status}; Descending=$true}, Computer
|
||||
|
||||
# Display results in a formatted table
|
||||
Write-Host " STATUS COMPUTER RESULT" -ForegroundColor Cyan
|
||||
Write-Host " ------ -------- ------" -ForegroundColor Cyan
|
||||
|
||||
foreach ($r in $allResults) {
|
||||
$statusColor = if ($r.Status -eq "OK") { "Green" } else { "Red" }
|
||||
$statusIcon = if ($r.Status -eq "OK") { "[OK] " } else { "[FAIL]" }
|
||||
|
||||
# Pad/truncate computer name to 20 chars
|
||||
$compName = $r.Computer
|
||||
if ($compName.Length -gt 18) { $compName = $compName.Substring(0, 15) + "..." }
|
||||
$compName = $compName.PadRight(20)
|
||||
|
||||
# Truncate message if too long
|
||||
$msg = $r.Message
|
||||
if ($msg.Length -gt 50) { $msg = $msg.Substring(0, 47) + "..." }
|
||||
|
||||
Write-Host " " -NoNewline
|
||||
Write-Host $statusIcon -ForegroundColor $statusColor -NoNewline
|
||||
Write-Host " $compName " -NoNewline
|
||||
Write-Host $msg -ForegroundColor $(if ($r.Status -eq "OK") { "White" } else { "Yellow" })
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Summary
|
||||
Write-Host ""
|
||||
Write-Host ("=" * 60) -ForegroundColor Cyan
|
||||
Write-Host " SUMMARY" -ForegroundColor Cyan
|
||||
Write-Host ("=" * 60) -ForegroundColor Cyan
|
||||
Write-Host " Task: $Task" -ForegroundColor White
|
||||
Write-Host " Total: $($computers.Count)" -ForegroundColor White
|
||||
Write-Host " Successful: $successCount" -ForegroundColor Green
|
||||
Write-Host " Failed: $failCount" -ForegroundColor $(if ($failCount -gt 0) { "Red" } else { "White" })
|
||||
Write-Host ("=" * 60) -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Save to log file if requested
|
||||
if ($LogResults) {
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
if (-not $scriptDir) { $scriptDir = Get-Location }
|
||||
$logTimestamp = Get-Date -Format "yyyyMMdd_HHmmss"
|
||||
$logFile = Join-Path $scriptDir "RemoteTask_$logTimestamp.log"
|
||||
|
||||
$logContent = @()
|
||||
$logContent += "=" * 60
|
||||
$logContent += "Remote Task Execution Log"
|
||||
$logContent += "=" * 60
|
||||
$logContent += "Date/Time: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
|
||||
$logContent += "Task: $Task"
|
||||
$logContent += "Targets: $($computers.Count)"
|
||||
$logContent += "ThrottleLimit: $ThrottleLimit"
|
||||
$logContent += "Elapsed: $([math]::Round($elapsed, 1)) seconds"
|
||||
if ($ServiceName) { $logContent += "ServiceName: $ServiceName" }
|
||||
if ($Command) { $logContent += "Command: $Command" }
|
||||
$logContent += ""
|
||||
$logContent += "=" * 60
|
||||
$logContent += "RESULTS"
|
||||
$logContent += "=" * 60
|
||||
$logContent += ""
|
||||
$logContent += "STATUS COMPUTER RESULT"
|
||||
$logContent += "------ -------- ------"
|
||||
|
||||
foreach ($r in $allResults) {
|
||||
$statusText = if ($r.Status -eq "OK") { "[OK] " } else { "[FAIL] " }
|
||||
$compText = $r.Computer.PadRight(28)
|
||||
$logContent += "$statusText $compText $($r.Message)"
|
||||
}
|
||||
|
||||
$logContent += ""
|
||||
$logContent += "=" * 60
|
||||
$logContent += "SUMMARY"
|
||||
$logContent += "=" * 60
|
||||
$logContent += "Total: $($computers.Count)"
|
||||
$logContent += "Successful: $successCount"
|
||||
$logContent += "Failed: $failCount"
|
||||
$logContent += "=" * 60
|
||||
|
||||
$logContent | Out-File -FilePath $logFile -Encoding UTF8
|
||||
Write-Log "Results saved to: $logFile" -Level "SUCCESS"
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# Results are displayed above and optionally saved to log file
|
||||
# To capture results programmatically, use: $results = Invoke-Command ... directly
|
||||
Reference in New Issue
Block a user