<#================================================================================= PC Health Check - Remote Edition ================================================================================= .SYNOPSIS Performs comprehensive system health checks on local or remote Windows computers. .DESCRIPTION This script collects critical system information including disk space, CPU usage, memory utilization, network connectivity, services status, event logs, and more. Supports both interactive mode and automated command-line execution. For remote computers, the script uses CIM over DCOM (Windows PowerShell 5.1 compatible) since WinRM is typically disabled on desktops in enterprise environments. .PARAMETER ComputerName Specifies the target computer(s) to check. Accepts multiple comma-separated values. If omitted, checks the local computer. .PARAMETER Credential Specifies a user account with permissions to query the remote computer(s). Use Get-Credential to enter credentials securely. .PARAMETER LogToFile Saves results to a log file in the specified or default LogPath directory. .PARAMETER LogPath Specifies the directory where log files and HTML reports are saved. Default: Desktop\PCHealthChecks .PARAMETER Silent Suppresses console output. Only applicable with -LogToFile or -ExportHTML. Use with -LogToFile to run unattended scheduled tasks. .PARAMETER ExportHTML Creates an HTML report of the results in addition to log file output. .PARAMETER RunAllChecks Runs all checks automatically without user interaction. Use with -ComputerName to check multiple remote computers in batch mode. .PARAMETER IncludeNetwork Includes network connectivity tests to external hosts (google.com, microsoft.com). Disabled by default for remote checks to reduce execution time. .PARAMETER IncludeUpdates Checks for pending Windows updates. May require additional permissions. .PARAMETER ThrottleLimit Maximum number of concurrent remote connections when checking multiple computers. Default: 5 .PARAMETER Timeout Connection timeout in seconds for remote computers. Default: 30 seconds. .PARAMETER Help Displays this help information. .INPUTS None. You cannot pipe objects to this script. .OUTPUTS Console output and optionally log files and HTML reports. .EXAMPLE .\PCHealthCheck.ps1 Runs interactive health check on the local computer. .EXAMPLE .\PCHealthCheck.ps1 -RunAllChecks -LogToFile Runs all checks on local computer and saves results to log file. .EXAMPLE .\PCHealthCheck.ps1 -ComputerName PC01,PC02,PC03 -RunAllChecks -LogToFile Runs all checks on three remote computers and saves results to log file. .EXAMPLE .\PCHealthCheck.ps1 -ComputerName PC01 -Credential (Get-Credential) -ExportHTML Checks remote computer PC01 with custom credentials and generates HTML report. .EXAMPLE .\PCHealthCheck.ps1 -ComputerName PC01 -Silent -LogToFile Runs silent health check on remote computer (no console output). .EXAMPLE Get-Content computers.txt | ForEach-Object { .\PCHealthCheck.ps1 -ComputerName $_ -RunAllChecks -LogToFile } Reads computer names from a text file and checks each one. .NOTES Author: Kevin Simmons Version: 3.0 Date: 2026-10-04 REQUIREMENTS: - PowerShell 5.1 (Windows PowerShell) - Administrator or delegated permissions on target computers - DCOM enabled (standard on Windows desktops/servers) - Firewall ports: RPC (135) and dynamic RPC ports (1024-5000) REMOTE MANAGEMENT NOTES: - WinRM is typically disabled on enterprise desktops - Uses CIM over DCOM for remote connections - For servers with WinRM enabled, consider using Invoke-Command instead - Check connectivity first: Test-Connection -ComputerName -Count 1 .LINK https://learn.microsoft.com/en-us/powershell/ ================================================================================ #> # Command-line parameters [CmdletBinding(SupportsShouldProcess=$true)] param ( [Parameter(Mandatory=$false)] [string[]]$ComputerName, [Parameter(Mandatory=$false)] [pscredential]$Credential, [Parameter(Mandatory=$false)] [switch]$LogToFile, [Parameter(Mandatory=$false)] [string]$LogPath, [Parameter(Mandatory=$false)] [switch]$Silent, [Parameter(Mandatory=$false)] [switch]$ExportHTML, [Parameter(Mandatory=$false)] [switch]$RunAllChecks, [Parameter(Mandatory=$false)] [switch]$IncludeNetwork, [Parameter(Mandatory=$false)] [switch]$IncludeUpdates, [Parameter(Mandatory=$false)] [int]$ThrottleLimit = 5, [Parameter(Mandatory=$false)] [int]$Timeout = 30, [Parameter(Mandatory=$false)] [switch]$Help ) # Show help if requested if ($Help) { Get-Help $MyInvocation.MyCommand.Path -Detailed exit } # Define default log file path if not specified if (-not $LogPath) { $LogPath = "$([Environment]::GetFolderPath('Desktop'))\PCHealthChecks" } # Determine if running locally or remotely $IsLocal = (-not $ComputerName) -or ($ComputerName -contains $env:COMPUTERNAME) # Create HTML report file name $htmlReportFile = "$LogPath\PCHealthCheck_$(Get-Date -Format 'yyyyMMdd_HHmmss').html" # Create log file name (include computer name if remote) if ($IsLocal) { $logFile = "$LogPath\PCHealthCheck_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" } else { $logFile = "$LogPath\PCHealthCheck_$(Get-Date -Format 'yyyyMMdd_HHmmss')_$(($ComputerName -join '_')).log" } # Initialize HTML report content if export is requested $htmlReport = $null $currentSection = $null if ($ExportHTML) { $htmlReport = @" PC Health Check Report

PC Health Check Report

System: $env:COMPUTERNAME

User: $env:USERNAME

Date: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')

"@ } # Define functions first function Write-ColorOutput { param ( [Parameter(Mandatory=$true)] [string]$Message, [Parameter(Mandatory=$false)] [string]$ForegroundColor = "White", [Parameter(Mandatory=$false)] [string]$StatusLevel = "Info", [Parameter(Mandatory=$false)] [string]$ComputerName = $env:COMPUTERNAME ) # Only output to console if not in silent mode if (-not $Silent) { Write-Host $Message -ForegroundColor $ForegroundColor } # Log to file if requested if ($LogToFile) { Add-ToLog $Message $ComputerName } # Add to HTML report if requested if ($ExportHTML) { Add-ToHtmlReport $Message $StatusLevel $ComputerName } } function Add-ToLog { param ( [Parameter(Mandatory=$true)] [string]$Message, [Parameter(Mandatory=$false)] [string]$ComputerName = $env:COMPUTERNAME ) # Ensure log directory exists if (-not (Test-Path -Path $LogPath)) { New-Item -Path $LogPath -ItemType Directory -Force | Out-Null } # Add timestamp and append message to log file $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' "$timestamp [$ComputerName] - $Message" | Out-File -FilePath $logFile -Append } function Add-ToHtmlReport { param ( [Parameter(Mandatory=$true)] [string]$Message, [Parameter(Mandatory=$false)] [string]$StatusLevel = "Info", [Parameter(Mandatory=$false)] [string]$ComputerName = $env:COMPUTERNAME ) # Determine CSS class based on status level $cssClass = "info" if ($StatusLevel -eq "Warning") { $cssClass = "warning" } if ($StatusLevel -eq "Critical") { $cssClass = "critical" } if ($StatusLevel -eq "Good") { $cssClass = "good" } # Format content based on caller function $callerName = (Get-PSCallStack)[1].Command # Start a new section if this is a function header if ($Message -match "^Checking|^Getting|^Testing|^Scanning") { $sectionTitle = $Message -replace "\.\.\..*", "" $script:htmlReport += "

[$ComputerName] $sectionTitle

" $script:currentSection = $callerName $script:htmlReport += "

$Message

" } elseif ($callerName -ne $script:currentSection -and $callerName -ne "Write-ColorOutput" -and $callerName -ne "Show-Banner") { # If we've switched functions, close current section and start new one if ($script:currentSection) { $script:htmlReport += "
" } $script:htmlReport += "

[$ComputerName] $callerName

" $script:currentSection = $callerName $script:htmlReport += "

$Message

" } else { # Regular message within current section if ($cssClass -ne "info") { $script:htmlReport += "

[$ComputerName] $Message

" } else { $script:htmlReport += "

[$ComputerName] $Message

" } } } function Start-HtmlTable { param ( [Parameter(Mandatory=$true)] [string[]]$Headers ) if ($ExportHTML) { $headerRow = $Headers | ForEach-Object { "$_" } $script:htmlReport += "$headerRow" } } function Add-HtmlTableRow { param ( [Parameter(Mandatory=$true)] [string[]]$Cells, [Parameter(Mandatory=$false)] [string]$StatusLevel = "Info" ) if ($ExportHTML) { $cssClass = "info" if ($StatusLevel -eq "Warning") { $cssClass = "warning" } if ($StatusLevel -eq "Critical") { $cssClass = "critical" } if ($StatusLevel -eq "Good") { $cssClass = "good" } $cellRows = $Cells | ForEach-Object { "" } $script:htmlReport += "$cellRows" } } function End-HtmlTable { if ($ExportHTML) { $script:htmlReport += "
$_
" } } function Save-HtmlReport { param ( [Parameter(Mandatory=$false)] [string]$ComputerName = $env:COMPUTERNAME ) if ($ExportHTML) { # Close any open section if ($script:currentSection) { $script:htmlReport += "
" } # Add footer and close HTML $script:htmlReport += @"

Report Summary

PC Health Check completed on $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')

Target Computer: $ComputerName

"@ # Ensure directory exists if (-not (Test-Path -Path $LogPath)) { New-Item -Path $LogPath -ItemType Directory -Force | Out-Null } # Save HTML to file $script:htmlReport | Out-File -FilePath $htmlReportFile -Encoding utf8 # Return the file path for display return $htmlReportFile } return $null } function Show-Banner { param ( [Parameter(Mandatory=$false)] [string]$ComputerName = $env:COMPUTERNAME ) if (-not $Silent) { Clear-Host Write-Host "=======================================" -ForegroundColor Cyan Write-Host " ENHANCED PC HEALTH CHECK " -ForegroundColor Cyan Write-Host "=======================================" -ForegroundColor Cyan Write-Host "System: $ComputerName" -ForegroundColor Cyan Write-Host "User: $env:USERNAME" -ForegroundColor Cyan Write-Host "Date: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -ForegroundColor Cyan if ($LogToFile) { Write-Host "Log File: $logFile" -ForegroundColor Cyan } if ($ExportHTML) { Write-Host "HTML Report: $htmlReportFile" -ForegroundColor Cyan } Write-Host "=======================================" -ForegroundColor Cyan } } function Show-Menu { if (-not $Silent) { Write-Host "`nSelect an option:" -ForegroundColor Yellow Write-Host "1. Check Disk Space" -ForegroundColor White Write-Host "2. Check CPU Usage" -ForegroundColor White Write-Host "3. Check Memory Usage" -ForegroundColor White Write-Host "4. Test Network Connectivity" -ForegroundColor White Write-Host "5. Check for Port Exhaustion" -ForegroundColor White Write-Host "6. Check System Uptime" -ForegroundColor White Write-Host "7. List Top Processes by Memory" -ForegroundColor White Write-Host "8. List Top Processes by CPU" -ForegroundColor White Write-Host "9. Check Windows Services Status" -ForegroundColor White Write-Host "10. Scan Windows Event Logs" -ForegroundColor White Write-Host "11. Check Windows Update Status" -ForegroundColor White Write-Host "13. Check Pending Reboot Status" -ForegroundColor White Write-Host "14. Check Network Adapters" -ForegroundColor White Write-Host "12. Run All Checks" -ForegroundColor Green Write-Host "E. Export HTML Report" -ForegroundColor Cyan Write-Host "H. Help" -ForegroundColor Cyan Write-Host "L. View Log Files" -ForegroundColor Cyan Write-Host "Q. Quit" -ForegroundColor Red } } function Get-CimSessionForComputer { param ( [Parameter(Mandatory=$true)] [string]$ComputerName, [Parameter(Mandatory=$false)] [pscredential]$Credential ) # Try to create CIM session with auto-detection of protocol $sessionOptions = New-CimSessionOption -Protocol Wsman $cimSession = $null try { # Try WinRM first $cimSession = New-CimSession -ComputerName $ComputerName -SessionOption $sessionOptions -Credential $Credential -ErrorAction Stop return $cimSession } catch { # WinRM failed, try DCOM $sessionOptions = New-CimSessionOption -Protocol Dcom try { $cimSession = New-CimSession -ComputerName $ComputerName -SessionOption $sessionOptions -Credential $Credential -ErrorAction Stop return $cimSession } catch { Write-ColorOutput "Failed to connect to ${ComputerName}: $_" "Red" "Critical" $ComputerName return $null } } } function Get-DiskSpace { param ( [Parameter(Mandatory=$false)] [string]$ComputerName = $env:COMPUTERNAME, [Parameter(Mandatory=$false)] [Microsoft.Management.Infrastructure.CimSession]$CimSession = $null ) Write-ColorOutput "`nChecking Disk Space..." "Yellow" $ComputerName try { if (-not $CimSession -and $ComputerName -eq $env:COMPUTERNAME) { $disks = Get-PSDrive -PSProvider FileSystem | Select-Object Name, @{Name="FreeSpace(GB)";Expression={[math]::Round($_.Free/1GB, 2)}}, @{Name="TotalSpace(GB)";Expression={[math]::Round(($_.Used/1GB + $_.Free/1GB), 2)}}, @{Name="PercentFree";Expression={[math]::Round(($_.Free/($_.Used + $_.Free))*100, 2)}} } else { $disks = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3" -CimSession $CimSession | Select-Object DeviceID, @{Name="FreeSpace(GB)";Expression={[math]::Round($_.FreeSpace/1GB, 2)}}, @{Name="TotalSpace(GB)";Expression={[math]::Round(($_.FreeSpace/1GB + $_.Size/1GB), 2)}}, @{Name="PercentFree";Expression={[math]::Round(($_.FreeSpace/($_.FreeSpace + $_.Size))*100, 2)}} } if ($ExportHTML) { Start-HtmlTable @("Drive", "Free Space (GB)", "Total Space (GB)", "Percent Free") } foreach ($disk in $disks) { $color = "Green" $status = "Good" if ($disk."PercentFree" -lt 20) { $color = "Yellow"; $status = "Warning" } if ($disk."PercentFree" -lt 10) { $color = "Red"; $status = "Critical" } Write-ColorOutput "Drive $($disk.Name): $($disk.'FreeSpace(GB)') GB free of $($disk.'TotalSpace(GB)') GB ($($disk.PercentFree)% free)" $color $status $ComputerName if ($ExportHTML) { Add-HtmlTableRow @($disk.Name, $disk.'FreeSpace(GB)', $disk.'TotalSpace(GB)', "$($disk.PercentFree)%") $status } } if ($ExportHTML) { End-HtmlTable } } catch { Write-ColorOutput "Error checking disk space on ${ComputerName}: $_" "Red" "Critical" $ComputerName } } function Get-CPUUsage { param ( [Parameter(Mandatory=$false)] [string]$ComputerName = $env:COMPUTERNAME, [Parameter(Mandatory=$false)] [Microsoft.Management.Infrastructure.CimSession]$CimSession = $null ) Write-ColorOutput "`nChecking CPU Usage..." "Yellow" $ComputerName try { Write-ColorOutput "Taking 5 CPU utilization samples..." "Gray" $ComputerName $samples = @() if ($ExportHTML) { Start-HtmlTable @("Sample", "CPU Usage (%)") } for ($i = 1; $i -le 5; $i++) { if (-not $CimSession -and $ComputerName -eq $env:COMPUTERNAME) { $counterData = Get-Counter -Counter "\Processor(_Total)\% Processor Time" -ErrorAction Stop $cpuValue = [math]::Round($counterData.CounterSamples[0].CookedValue, 2) } else { $counterData = Get-CimInstance -ClassName Win32_PerfRawData_PerfOS_Processor -CimSession $CimSession -Filter "Name='_Total'" $cpuValue = [math]::Round($counterData.PercentProcessorTime, 2) } $samples += $cpuValue Write-ColorOutput " Sample $i of 5: $cpuValue%" "Gray" $ComputerName if ($ExportHTML) { Add-HtmlTableRow @("Sample $i", "$cpuValue%") } Start-Sleep -Seconds 1 } if ($ExportHTML) { End-HtmlTable } # Calculate average CPU usage $avgCpu = ($samples | Measure-Object -Average).Average $avgCpu = [math]::Round($avgCpu, 2) # Calculate min and max values $minCpu = ($samples | Measure-Object -Minimum).Minimum $maxCpu = ($samples | Measure-Object -Maximum).Maximum # Set color based on average CPU usage $color = "Green" $status = "Good" if ($avgCpu -gt 70) { $color = "Yellow"; $status = "Warning" } if ($avgCpu -gt 90) { $color = "Red"; $status = "Critical" } Write-ColorOutput "`nCPU Usage Results:" "White" $ComputerName Write-ColorOutput " Average: $avgCpu%" $color $status $ComputerName Write-ColorOutput " Minimum: $minCpu%" "Gray" $ComputerName Write-ColorOutput " Maximum: $maxCpu%" "Gray" $ComputerName $processCount = (Get-Process).Count Write-ColorOutput " Total Processes: $processCount" "White" $ComputerName # Get top processes by CPU Write-ColorOutput "`nTop Processes by CPU Usage:" "White" $ComputerName if (-not $CimSession -and $ComputerName -eq $env:COMPUTERNAME) { $numProcs = [Environment]::ProcessorCount $processes = Get-CimInstance Win32_PerfFormattedData_PerfProc_Process | Where-Object { $_.Name -notmatch '_Total|Idle' } | Select-Object IDProcess, @{Name='ProcessName';Expression={$_.Name -replace '#\d+$', ''}}, @{Name='PID';Expression={$_.IDProcess}}, @{Name='CPU_Percent';Expression={[math]::Round($_.PercentProcessorTime / $numProcs, 2)}} | Sort-Object -Property CPU_Percent -Descending | Select-Object -First 10 } else { $numProcs = (Get-CimInstance Win32_ComputerSystem -CimSession $CimSession).NumberOfLogicalProcessors $processes = Get-CimInstance Win32_PerfFormattedData_PerfProc_Process -CimSession $CimSession | Where-Object { $_.Name -notmatch '_Total|Idle' } | Select-Object IDProcess, @{Name='ProcessName';Expression={$_.Name -replace '#\d+$', ''}}, @{Name='PID';Expression={$_.IDProcess}}, @{Name='CPU_Percent';Expression={[math]::Round($_.PercentProcessorTime / $numProcs, 2)}} | Sort-Object -Property CPU_Percent -Descending | Select-Object -First 10 } $processes | Format-Table -AutoSize | Out-String | ForEach-Object { Write-ColorOutput $_ "White" "Info" $ComputerName } if ($ExportHTML) { Start-HtmlTable @("ProcessName", "PID", "CPU_Percent") foreach ($proc in $processes) { Add-HtmlTableRow @($proc.ProcessName, $proc.PID, "$($proc.CPU_Percent)%") "Info" } End-HtmlTable } if ($ExportHTML) { Start-HtmlTable @("Measurement", "Value", "Status") $cpuStatus = "Good" if ($avgCpu -gt 90) { $cpuStatus = "Critical" } elseif ($avgCpu -gt 70) { $cpuStatus = "Warning" } Add-HtmlTableRow @("Average CPU Usage", "$avgCpu%", $cpuStatus) $status Add-HtmlTableRow @("Minimum CPU Usage", "$minCpu%", "Info") Add-HtmlTableRow @("Maximum CPU Usage", "$maxCpu%", "Info") End-HtmlTable } } catch { Write-ColorOutput "Error checking CPU usage on ${ComputerName}: $_" "Red" "Critical" $ComputerName } } function Get-MemoryUsage { param ( [Parameter(Mandatory=$false)] [string]$ComputerName = $env:COMPUTERNAME, [Parameter(Mandatory=$false)] [Microsoft.Management.Infrastructure.CimSession]$CimSession = $null ) Write-ColorOutput "`nChecking Memory Usage..." "Yellow" $ComputerName try { if (-not $CimSession -and $ComputerName -eq $env:COMPUTERNAME) { $os = Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop } else { $os = Get-CimInstance -ClassName Win32_OperatingSystem -CimSession $CimSession -ErrorAction Stop } $totalMemoryGB = [math]::Round($os.TotalVisibleMemorySize / 1MB, 2) $freeMemoryGB = [math]::Round($os.FreePhysicalMemory / 1MB, 2) $usedMemoryGB = [math]::Round($totalMemoryGB - $freeMemoryGB, 2) $percentUsed = [math]::Round(($usedMemoryGB / $totalMemoryGB) * 100, 2) $color = "Green" $status = "Good" if ($percentUsed -gt 70) { $color = "Yellow"; $status = "Warning" } if ($percentUsed -gt 90) { $color = "Red"; $status = "Critical" } Write-ColorOutput "Total Memory: $totalMemoryGB GB" "White" $ComputerName Write-ColorOutput "Used Memory: $usedMemoryGB GB ($percentUsed%)" $color $status $ComputerName Write-ColorOutput "Free Memory: $freeMemoryGB GB" "White" $ComputerName $pageFileUsage = Get-CimInstance -ClassName Win32_PageFileUsage | Select-Object -First 1 if ($pageFileUsage) { $pageFileTotal = $pageFileUsage.AllocatedBaseSize $pageFileUsed = $pageFileUsage.CurrentUsage $pageFilePercent = if ($pageFileTotal -gt 0) { [math]::Round(($pageFileUsed / $pageFileTotal) * 100, 2) } else { 0 } $pageFileColor = "Green" $pageFileStatus = "Good" if ($pageFilePercent -gt 70) { $pageFileColor = "Yellow"; $pageFileStatus = "Warning" } if ($pageFilePercent -gt 90) { $pageFileColor = "Red"; $pageFileStatus = "Critical" } Write-ColorOutput "Page File Usage: $pageFileUsed MB / $pageFileTotal MB ($pageFilePercent%)" $pageFileColor $pageFileStatus $ComputerName } # Get top processes by memory with percentage Write-ColorOutput "`nTop Processes by Memory Usage:" "White" $ComputerName if (-not $CimSession -and $ComputerName -eq $env:COMPUTERNAME) { $processes = Get-Process | Sort-Object -Property WorkingSet -Descending | Select-Object -First 10 -Property ProcessName, Id, @{Name="Memory(MB)";Expression={[math]::Round($_.WorkingSet / 1MB, 2)}}, @{Name="Memory(%)";Expression={[math]::Round(($_.WorkingSet / ($os.TotalVisibleMemorySize * 1KB)) * 100, 2)}} } else { $totalMemBytes = $os.TotalVisibleMemorySize * 1KB $processes = Get-CimInstance -ClassName Win32_Process -CimSession $CimSession | Sort-Object -Property WorkingSet -Descending | Select-Object -First 10 -Property Name, ProcessId, @{Name="Memory(MB)";Expression={[math]::Round($_.WorkingSetSize / 1MB, 2)}}, @{Name="Memory(%)";Expression={[math]::Round(($_.WorkingSetSize / $totalMemBytes) * 100, 2)}} } $processes | Format-Table -AutoSize | Out-String | ForEach-Object { Write-ColorOutput $_ "White" "Info" $ComputerName } if ($ExportHTML) { Start-HtmlTable @("ProcessName", "Id", "Memory(MB)", "Memory(%)") foreach ($proc in $processes) { Add-HtmlTableRow @($proc.ProcessName, $proc.Id, $proc.'Memory(MB)', "$($proc.'Memory(%)')%") "Info" } End-HtmlTable } if ($ExportHTML) { Start-HtmlTable @("Measurement", "Value", "Status") $memStatus = "Good" if ($percentUsed -gt 90) { $memStatus = "Critical" } elseif ($percentUsed -gt 70) { $memStatus = "Warning" } Add-HtmlTableRow @("Total Memory", "$totalMemoryGB GB", "Info") Add-HtmlTableRow @("Used Memory", "$usedMemoryGB GB ($percentUsed%)", $memStatus) $status Add-HtmlTableRow @("Free Memory", "$freeMemoryGB GB", "Info") if ($pageFileUsage) { Add-HtmlTableRow @("Page File Usage", "$pageFileUsed MB / $pageFileTotal MB ($pageFilePercent%)", $pageFileStatus) $pageFileStatus } End-HtmlTable # Add a simple memory usage visualization $barColor = "#2e8b57" if ($percentUsed -gt 70) { $barColor = "#ff9900" } if ($percentUsed -gt 90) { $barColor = "#cc3300" } $script:htmlReport += @"
$percentUsed%
Memory Usage
"@ } } catch { Write-ColorOutput "Error checking memory usage on ${ComputerName}: $_" "Red" "Critical" $ComputerName } } function Test-NetworkConnectivity { param ( [Parameter(Mandatory=$false)] [string]$ComputerName = $env:COMPUTERNAME, [Parameter(Mandatory=$false)] [Microsoft.Management.Infrastructure.CimSession]$CimSession = $null ) Write-ColorOutput "`nTesting Network Connectivity..." "Yellow" $ComputerName $allPassed = $true try { if (-not $CimSession -and $ComputerName -eq $env:COMPUTERNAME) { $networkAdapters = Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration -Filter "IPEnabled=True" -ErrorAction Stop } else { $networkAdapters = Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration -Filter "IPEnabled=True" -CimSession $CimSession -ErrorAction Stop } if ($networkAdapters) { $gateway = $networkAdapters | ForEach-Object { $_.DefaultIPGateway } | Where-Object { $_ -ne $null } | Select-Object -First 1 $dnsServers = $networkAdapters | ForEach-Object { $_.DNSServerSearchOrder } | Where-Object { $_ -ne $null } | Select-Object -First 2 if ($gateway) { Write-ColorOutput "Testing Gateway connectivity..." "White" $ComputerName try { $ping = Test-Connection -ComputerName $gateway -Count 2 -ErrorAction Stop $avgTime = [math]::Round(($ping | Measure-Object -Property ResponseTime -Average).Average, 2) Write-ColorOutput " Gateway - PASS" "Green" $ComputerName Write-ColorOutput " Gateway IP: $gateway" "White" $ComputerName Write-ColorOutput " Avg Response: $avgTime ms" "Gray" $ComputerName } catch { Write-ColorOutput " Gateway - FAIL" "Red" $ComputerName Write-ColorOutput " Gateway IP: $gateway" "White" $ComputerName Write-ColorOutput " Error: $_" "Red" $ComputerName $allPassed = $false } } else { Write-ColorOutput " Gateway - FAIL" "Red" $ComputerName Write-ColorOutput " No gateway configured" "Red" $ComputerName $allPassed = $false } if ($dnsServers) { Write-ColorOutput "`nTesting DNS servers..." "White" $ComputerName foreach ($dns in $dnsServers) { try { $ping = Test-Connection -ComputerName $dns -Count 2 -ErrorAction Stop $avgTime = [math]::Round(($ping | Measure-Object -Property ResponseTime -Average).Average, 2) Write-ColorOutput " DNS - PASS" "Green" $ComputerName Write-ColorOutput " DNS IP: $dns" "White" $ComputerName Write-ColorOutput " Avg Response: $avgTime ms" "Gray" $ComputerName } catch { Write-ColorOutput " DNS - FAIL" "Red" $ComputerName Write-ColorOutput " DNS IP: $dns" "White" $ComputerName Write-ColorOutput " Error: $_" "Red" $ComputerName $allPassed = $false } } } else { Write-ColorOutput " DNS - FAIL" "Red" $ComputerName Write-ColorOutput " No DNS servers configured" "Red" $ComputerName $allPassed = $false } Write-ColorOutput "`nTesting external connectivity (8.8.8.8)..." "White" $ComputerName try { $ping = Test-Connection -ComputerName "8.8.8.8" -Count 2 -ErrorAction Stop $avgTime = [math]::Round(($ping | Measure-Object -Property ResponseTime -Average).Average, 2) Write-ColorOutput " External - PASS" "Green" $ComputerName Write-ColorOutput " Target: 8.8.8.8" "White" $ComputerName Write-ColorOutput " Avg Response: $avgTime ms" "Gray" $ComputerName } catch { Write-ColorOutput " External - FAIL" "Red" $ComputerName Write-ColorOutput " Target: 8.8.8.8" "White" $ComputerName Write-ColorOutput " Error: $_" "Red" $ComputerName $allPassed = $false } } else { Write-ColorOutput " Network connectivity - FAIL" "Red" $ComputerName Write-ColorOutput " No IP-enabled network adapters found" "Red" $ComputerName $allPassed = $false } } catch { Write-ColorOutput " Network connectivity - FAIL" "Red" $ComputerName Write-ColorOutput " Error: $_" "Red" $ComputerName $allPassed = $false } if ($allPassed) { Write-ColorOutput "`nNetwork connectivity test: SUCCESS" "Green" $ComputerName } else { Write-ColorOutput "`nNetwork connectivity test: FAILED" "Red" $ComputerName } } function Test-PortExhaustion { param ( [Parameter(Mandatory=$false)] [string]$ComputerName = $env:COMPUTERNAME, [Parameter(Mandatory=$false)] [Microsoft.Management.Infrastructure.CimSession]$CimSession = $null ) Write-ColorOutput "`nChecking for Port Exhaustion..." "Yellow" $ComputerName try { if (-not $CimSession -and $ComputerName -eq $env:COMPUTERNAME) { $netstatOutput = netstat -ano | Out-String $maxUserPorts = (Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" -Name "MaxUserPort" -ErrorAction SilentlyContinue).MaxUserPort if (-not $maxUserPorts) { $maxUserPorts = 16383 } } else { # Get MaxUserPort from registry via CIM $regKey = "SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" $maxUserPorts = (Get-CimInstance -ClassName StdRegProv -Namespace "root\cimv2" -CimSession $CimSession -ErrorAction SilentlyContinue).GetDWordValue(2147483650, $regKey, "MaxUserPort").uValue if (-not $maxUserPorts) { $maxUserPorts = 16383 } # Get TCP connections via CIM $tcpConnections = Get-CimInstance -ClassName MSFT_NetTCPConnection -Namespace "root\standardcimv2" -CimSession $CimSession -ErrorAction SilentlyContinue $netstatOutput = $tcpConnections | Format-Table -AutoSize | Out-String } $connections = $netstatOutput -split "`r`n" | Where-Object { $_ -match "TCP" } $totalConnections = @($connections).Count $ephemeralStates = @("ESTABLISHED", "TIME_WAIT", "CLOSE_WAIT") $ephemeralConnections = @($connections | Where-Object { $parts = $_.Trim() -split "\s+" if ($parts.Count -ge 4) { $parts[3] -in $ephemeralStates } else { $false } }) $ephemeralCount = $ephemeralConnections.Count $establishedCount = @($ephemeralConnections | Where-Object { $_ -match 'ESTABLISHED' }).Count $timeWaitCount = @($ephemeralConnections | Where-Object { $_ -match 'TIME_WAIT' }).Count $closeWaitCount = @($ephemeralConnections | Where-Object { $_ -match 'CLOSE_WAIT' }).Count $portUsagePercent = [math]::Round(($ephemeralCount / $maxUserPorts) * 100, 2) $color = "Green" $status = "PASS" if ($ephemeralCount -gt 5000) { $color = "Red"; $status = "FAIL" } elseif ($ephemeralCount -ge 2500) { $color = "Yellow"; $status = "WARNING" } Write-ColorOutput "TCP connections: $totalConnections" "White" $ComputerName Write-ColorOutput "Ephemeral port connections: $ephemeralCount" $color $status $ComputerName Write-ColorOutput " ESTABLISHED: $establishedCount" "White" $ComputerName Write-ColorOutput " TIME_WAIT: $timeWaitCount" "White" $ComputerName Write-ColorOutput " CLOSE_WAIT: $closeWaitCount" "White" $ComputerName Write-ColorOutput "Max User Ports: $maxUserPorts" "White" $ComputerName Write-ColorOutput "Port Exhaustion: $status" $color $ComputerName if ($status -eq "FAIL") { Write-ColorOutput "WARNING: High number of ephemeral connections detected. Port exhaustion may occur!" "Red" $ComputerName } elseif ($status -eq "WARNING") { Write-ColorOutput "WARNING: Ephemeral connection count approaching threshold. Monitor closely." "Yellow" $ComputerName } } catch { Write-ColorOutput "Error checking port exhaustion on ${ComputerName}: $_" "Red" $ComputerName } } function Get-SystemUptime { param ( [Parameter(Mandatory=$false)] [string]$ComputerName = $env:COMPUTERNAME, [Parameter(Mandatory=$false)] [Microsoft.Management.Infrastructure.CimSession]$CimSession = $null ) Write-ColorOutput "`nChecking System Uptime..." "Yellow" $ComputerName try { $bootUpTime = if (-not $CimSession -and $ComputerName -eq $env:COMPUTERNAME) { (Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop).LastBootUpTime } else { (Get-CimInstance -ClassName Win32_OperatingSystem -CimSession $CimSession -ErrorAction Stop).LastBootUpTime } # Handle both DateTime (from CIM) and string (from legacy WMI) types if ($bootUpTime -is [DateTime]) { $lastBootTime = $bootUpTime } else { # Parse WMI datetime format (yyyyMMddHHmmss.mmmmmm+UUU) $year = [int]$bootUpTime.Substring(0, 4) $month = [int]$bootUpTime.Substring(4, 2) $day = [int]$bootUpTime.Substring(6, 2) $hour = [int]$bootUpTime.Substring(8, 2) $minute = [int]$bootUpTime.Substring(10, 2) $second = [int]$bootUpTime.Substring(12, 2) $lastBootTime = New-Object DateTime($year, $month, $day, $hour, $minute, $second) } $uptime = (Get-Date) - $lastBootTime $formattedUptime = "{0} days, {1} hours, {2} minutes, {3} seconds" -f ` $uptime.Days, $uptime.Hours, $uptime.Minutes, $uptime.Seconds Write-ColorOutput "System Last Boot: $($lastBootTime.ToString('yyyy-MM-dd HH:mm:ss'))" "White" $ComputerName Write-ColorOutput "System Uptime: $formattedUptime" "White" $ComputerName } catch { Write-ColorOutput "Error checking system uptime on ${ComputerName}: $_" "Red" $ComputerName } } function Get-TopProcessesByMemory { param ( [Parameter(Mandatory=$false)] [string]$ComputerName = $env:COMPUTERNAME, [Parameter(Mandatory=$false)] [Microsoft.Management.Infrastructure.CimSession]$CimSession = $null ) Write-ColorOutput "`nGetting Top Processes by Memory Usage..." "Yellow" $ComputerName try { if (-not $CimSession -and $ComputerName -eq $env:COMPUTERNAME) { $os = Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop $totalMemBytes = $os.TotalVisibleMemorySize * 1KB $processes = Get-Process | Sort-Object -Property WorkingSet -Descending | Select-Object -First 10 -Property ProcessName, Id, @{Name="Memory(MB)";Expression={[math]::Round($_.WorkingSet / 1MB, 2)}}, @{Name="Memory(%)";Expression={[math]::Round(($_.WorkingSet / $totalMemBytes) * 100, 2)}} } else { $os = Get-CimInstance -ClassName Win32_OperatingSystem -CimSession $CimSession -ErrorAction Stop $totalMemBytes = $os.TotalVisibleMemorySize * 1KB $processes = Get-CimInstance -ClassName Win32_Process -CimSession $CimSession | Sort-Object -Property WorkingSet -Descending | Select-Object -First 10 -Property Name, ProcessId, @{Name="Memory(MB)";Expression={[math]::Round($_.WorkingSetSize / 1MB, 2)}}, @{Name="Memory(%)";Expression={[math]::Round(($_.WorkingSetSize / $totalMemBytes) * 100, 2)}} } $processes | Format-Table -AutoSize | Out-String | ForEach-Object { Write-ColorOutput $_ "White" "Info" $ComputerName } } catch { Write-ColorOutput "Error getting top memory processes on ${ComputerName}: $_" "Red" $ComputerName } } function Get-TopProcessesByCPU { param ( [Parameter(Mandatory=$false)] [string]$ComputerName = $env:COMPUTERNAME, [Parameter(Mandatory=$false)] [Microsoft.Management.Infrastructure.CimSession]$CimSession = $null ) Write-ColorOutput "`nGetting Top Processes by CPU Usage..." "Yellow" $ComputerName try { if (-not $CimSession -and $ComputerName -eq $env:COMPUTERNAME) { $numProcs = [Environment]::ProcessorCount $processes = Get-CimInstance Win32_PerfFormattedData_PerfProc_Process | Where-Object { $_.Name -notmatch '_Total|Idle' } | Select-Object IDProcess, @{Name='ProcessName';Expression={$_.Name -replace '#\d+$', ''}}, @{Name='PID';Expression={$_.IDProcess}}, @{Name='CPU_Percent';Expression={[math]::Round($_.PercentProcessorTime / $numProcs, 2)}} | Sort-Object -Property CPU_Percent -Descending | Select-Object -First 10 } else { $numProcs = (Get-CimInstance Win32_ComputerSystem -CimSession $CimSession).NumberOfLogicalProcessors $processes = Get-CimInstance Win32_PerfFormattedData_PerfProc_Process -CimSession $CimSession | Where-Object { $_.Name -notmatch '_Total|Idle' } | Select-Object IDProcess, @{Name='ProcessName';Expression={$_.Name -replace '#\d+$', ''}}, @{Name='PID';Expression={$_.IDProcess}}, @{Name='CPU_Percent';Expression={[math]::Round($_.PercentProcessorTime / $numProcs, 2)}} | Sort-Object -Property CPU_Percent -Descending | Select-Object -First 10 } $processes | Format-Table -AutoSize | Out-String | ForEach-Object { Write-ColorOutput $_ "White" "Info" $ComputerName } } catch { Write-ColorOutput "Error getting top CPU processes on ${ComputerName}: $_" "Red" $ComputerName } } function Get-ServicesStatus { param ( [Parameter(Mandatory=$false)] [string]$ComputerName = $env:COMPUTERNAME, [Parameter(Mandatory=$false)] [Microsoft.Management.Infrastructure.CimSession]$CimSession = $null ) Write-ColorOutput "`nChecking Critical Windows Services..." "Yellow" $ComputerName $criticalServices = @( "wuauserv", # Windows Update "WinDefend", # Windows Defender "wscsvc", # Security Center "Dhcp", # DHCP Client "Dnscache", # DNS Client "LanmanServer", # Server "LanmanWorkstation", # Workstation "nsi", # Network Store Interface Service "W32Time", # Windows Time "eventlog" # Windows Event Log ) foreach ($serviceName in $criticalServices) { try { if (-not $CimSession -and $ComputerName -eq $env:COMPUTERNAME) { $service = Get-Service -Name $serviceName -ErrorAction Stop } else { $service = Get-CimInstance -ClassName Win32_Service -Filter "Name='$serviceName'" -CimSession $CimSession -ErrorAction Stop } $color = "Green" $statusLevel = "Good" if ($service.Status -ne "Running") { $color = "Red"; $statusLevel = "Warning" } Write-ColorOutput "$($service.DisplayName): $($service.Status)" $color $statusLevel $ComputerName } catch { Write-ColorOutput "Error checking service $serviceName on $ComputerName" "Red" $ComputerName } } } function Get-EventLogErrors { param ( [Parameter(Mandatory=$false)] [string]$ComputerName = $env:COMPUTERNAME, [Parameter(Mandatory=$false)] [Microsoft.Management.Infrastructure.CimSession]$CimSession = $null ) Write-ColorOutput "`nScanning Windows Event Logs for errors (past 24 hours)..." "Yellow" $ComputerName $startTime = (Get-Date).AddHours(-24) $criticalLogs = @("System", "Application") $errorCount = 0 foreach ($logName in $criticalLogs) { Write-ColorOutput "Scanning $logName log..." "Gray" $ComputerName try { if (-not $CimSession -and $ComputerName -eq $env:COMPUTERNAME) { $errors = Get-EventLog -LogName $logName -EntryType Error -After $startTime -ErrorAction SilentlyContinue } else { $allEvents = Get-CimInstance -ClassName Win32_NTLogEvent -Filter "Logfile='$logName'" -CimSession $CimSession -ErrorAction SilentlyContinue $errors = $allEvents | Where-Object { $_.Type -eq 1 } | ForEach-Object { $dt = [System.Management.ManagementDateTimeConverter]::ToDateTime($_.TimeGenerated) if ($dt -ge $startTime) { $_ } } } if ($errors -and $errors.Count -gt 0) { $errorCount += $errors.Count $topErrors = $errors | Group-Object -Property Source | Sort-Object -Property Count -Descending | Select-Object -First 3 foreach ($errorGroup in $topErrors) { $latestError = $errors | Where-Object { $_.Source -eq $errorGroup.Name } | Sort-Object -Property TimeGenerated -Descending | Select-Object -First 1 $messagePreview = if ($latestError.Message.Length -gt 100) { $latestError.Message.Substring(0, 97) + "..." } else { $latestError.Message } Write-ColorOutput "[$logName] $($errorGroup.Name) (EventID: $($latestError.EventID)): $($errorGroup.Count) occurrences" "Red" $ComputerName Write-ColorOutput " Last occurred: $($latestError.TimeGenerated)" "Gray" $ComputerName Write-ColorOutput " $messagePreview" "Gray" $ComputerName } } } catch { Write-ColorOutput " Unable to retrieve events from $logName log on ${ComputerName}: $_" "Yellow" $ComputerName } } # Display summary count if ($errorCount -gt 0) { Write-ColorOutput "Found $errorCount Error events in the past 24 hours" "Red" $ComputerName } else { Write-ColorOutput "No Error events found in the past 24 hours" "Green" $ComputerName } } function Get-WindowsUpdateStatus { param ( [Parameter(Mandatory=$false)] [string]$ComputerName = $env:COMPUTERNAME, [Parameter(Mandatory=$false)] [Microsoft.Management.Infrastructure.CimSession]$CimSession = $null ) Write-ColorOutput "`nChecking Windows Update Status..." "Yellow" $ComputerName # Get update service status try { if (-not $CimSession -and $ComputerName -eq $env:COMPUTERNAME) { $wuauserv = Get-Service -Name "wuauserv" -ErrorAction SilentlyContinue } else { $wuauserv = Get-CimInstance -ClassName Win32_Service -Filter "Name='wuauserv'" -CimSession $CimSession -ErrorAction SilentlyContinue } if ($wuauserv) { $color = "Green" if ($wuauserv.Status -ne "Running") { $color = "Red" } Write-ColorOutput "Windows Update Service: $($wuauserv.Status)" $color $ComputerName if ($wuauserv.Status -ne "Running") { Write-ColorOutput "WARNING: Windows Update service is not running!" "Red" $ComputerName } } else { Write-ColorOutput "Windows Update Service: Status unknown" "Red" $ComputerName } # Try COM object approach for more detailed update info if ($IncludeUpdates) { try { $updateSession = New-Object -ComObject Microsoft.Update.Session $updateSearcher = $updateSession.CreateUpdateSearcher() # Check for pending updates $pendingUpdates = $updateSearcher.Search("IsInstalled=0") # Display Pending Updates if ($pendingUpdates.Updates.Count -gt 0) { Write-ColorOutput "`nPending Updates: $($pendingUpdates.Updates.Count)" "Yellow" $ComputerName $count = 1 foreach ($update in $pendingUpdates.Updates) { Write-ColorOutput "$count. $($update.Title)" "White" $ComputerName $count++ # Limit to top 5 to avoid flooding console if ($count -gt 5 -and $pendingUpdates.Updates.Count -gt 5) { Write-ColorOutput " (and $($pendingUpdates.Updates.Count - 5) more...)" "Gray" $ComputerName break } } } else { Write-ColorOutput "`nNo pending Windows updates found." "Green" $ComputerName } } catch { Write-ColorOutput "Limited Windows Update information available: $_" "Red" $ComputerName } } } catch { Write-ColorOutput "Error checking Windows Update status on ${ComputerName}: $_" "Red" $ComputerName } } function Show-Help { Write-ColorOutput "`n===== HELP =====" "Magenta" Write-ColorOutput "This script performs various system health checks to diagnose performance issues." "White" Write-ColorOutput "`nAvailable Command-line Parameters:" "Cyan" Write-ColorOutput " -ComputerName Target computer(s) to check (comma-separated)" "White" Write-ColorOutput " -Credential Credentials for remote access (use Get-Credential)" "White" Write-ColorOutput " -LogToFile Saves results to a log file" "White" Write-ColorOutput " -LogPath Specifies custom log directory (default: Desktop\PCHealthChecks)" "White" Write-ColorOutput " -Silent Suppresses console output (use with -LogToFile)" "White" Write-ColorOutput " -ExportHTML Creates an HTML report of the results" "White" Write-ColorOutput " -RunAllChecks Runs all checks automatically without user interaction" "White" Write-ColorOutput " -IncludeNetwork Includes external network connectivity tests" "White" Write-ColorOutput " -IncludeUpdates Checks for pending Windows updates" "White" Write-ColorOutput " -ThrottleLimit Max concurrent connections (default: 5)" "White" Write-ColorOutput " -Timeout Connection timeout in seconds (default: 30)" "White" Write-ColorOutput " -Help Displays this help information" "White" Write-ColorOutput "`nExample Usage:" "Cyan" Write-ColorOutput " .\PCHealthCheck.ps1 -ComputerName PC01 -RunAllChecks -LogToFile" "White" Write-ColorOutput " .\PCHealthCheck.ps1 -ComputerName PC01,PC02 -ExportHTML" "White" Write-ColorOutput " .\PCHealthCheck.ps1 -ComputerName PC01 -Credential (Get-Credential)" "White" Write-ColorOutput "`nPress any key to return to the menu..." "Yellow" $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") } function Show-LogFiles { Write-ColorOutput "`nAvailable Log Files:" "Yellow" try { if (Test-Path -Path $LogPath) { $logs = Get-ChildItem -Path $LogPath -Filter "PCHealthCheck_*.log" | Sort-Object -Property LastWriteTime -Descending if ($logs.Count -gt 0) { $counter = 1 foreach ($log in $logs) { Write-Host "$counter. $($log.Name) - $($log.LastWriteTime)" -ForegroundColor White $counter++ # Limit to top 10 to avoid flooding console if ($counter -gt 10 -and $logs.Count -gt 10) { Write-Host " (and $($logs.Count - 10) more...)" -ForegroundColor Gray break } } Write-ColorOutput "`nLog location: $LogPath" "Cyan" } else { Write-ColorOutput "No log files found." "Yellow" } } else { Write-ColorOutput "Log directory does not exist yet. Run some checks first." "Yellow" } } catch { Write-ColorOutput "Error listing log files: $_" "Red" } } function Get-AutomaticServicesNotRunning { param ( [Parameter(Mandatory=$false)] [string]$ComputerName = $env:COMPUTERNAME, [Parameter(Mandatory=$false)] [Microsoft.Management.Infrastructure.CimSession]$CimSession = $null ) Write-ColorOutput "`nChecking Automatic Services Not Running..." "Yellow" $ComputerName try { if (-not $CimSession -and $ComputerName -eq $env:COMPUTERNAME) { $services = Get-Service | Where-Object { $_.StartType -eq "Automatic" -and $_.Status -ne "Running" } | Sort-Object DisplayName } else { $services = Get-CimInstance -ClassName Win32_Service -CimSession $CimSession | Where-Object { $_.StartMode -eq "Auto" -and $_.State -ne "Running" } | Sort-Object Name } if ($services.Count -eq 0) { Write-ColorOutput "All automatic services are running." "Green" "Good" $ComputerName } else { Write-ColorOutput "Found $($services.Count) automatic service(s) not running:" "Red" "Warning" $ComputerName foreach ($service in $services) { $serviceName = if (-not $CimSession -and $ComputerName -eq $env:COMPUTERNAME) { $service.DisplayName } else { $service.Name } Write-ColorOutput " - $serviceName" "Red" "Warning" $ComputerName } } } catch { Write-ColorOutput "Error checking automatic services on ${ComputerName}: $_" "Red" "Critical" $ComputerName } } function Run-AllChecks { param ( [Parameter(Mandatory=$false)] [string]$ComputerName = $env:COMPUTERNAME ) Write-ColorOutput "`n===== Running All System Checks =====" "Cyan" $ComputerName # Create CIM session for remote computers $cimSession = $null if ($ComputerName -ne $env:COMPUTERNAME) { $cimSession = Get-CimSessionForComputer -ComputerName $ComputerName -Credential $Credential } try { # Run all check functions with CIM session Get-DiskSpace -ComputerName $ComputerName -CimSession $cimSession Get-MemoryUsage -ComputerName $ComputerName -CimSession $cimSession Get-CPUUsage -ComputerName $ComputerName -CimSession $cimSession Test-NetworkConnectivity -ComputerName $ComputerName -CimSession $cimSession Test-PortExhaustion -ComputerName $ComputerName -CimSession $cimSession Get-SystemUptime -ComputerName $ComputerName -CimSession $cimSession Get-ServicesStatus -ComputerName $ComputerName -CimSession $cimSession Get-EventLogErrors -ComputerName $ComputerName -CimSession $cimSession Get-WindowsUpdateStatus -ComputerName $ComputerName -CimSession $cimSession Get-AutomaticServicesNotRunning -ComputerName $ComputerName -CimSession $cimSession Check-PendingReboot -ComputerName $ComputerName -CimSession $cimSession Check-NetworkAdapters -ComputerName $ComputerName -CimSession $cimSession Write-ColorOutput "`nSystem Check Complete!" "Cyan" $ComputerName if ($LogToFile) { Write-ColorOutput "Results saved to: $logFile" "Cyan" $ComputerName } if ($ExportHTML) { $reportPath = Save-HtmlReport -ComputerName $ComputerName if (-not $Silent) { Write-Host "`nHTML report generated: $reportPath" -ForegroundColor Green } } } finally { # Clean up CIM session if ($cimSession) { Remove-CimSession $cimSession } } } function Check-PendingReboot { param ( [Parameter(Mandatory=$false)] [string]$ComputerName = $env:COMPUTERNAME, [Parameter(Mandatory=$false)] [Microsoft.Management.Infrastructure.CimSession]$CimSession = $null ) Write-ColorOutput "`nChecking Pending Reboot Status..." "Yellow" $ComputerName $rebootPending = $false $rebootReasons = @() try { if (-not $CimSession -and $ComputerName -eq $env:COMPUTERNAME) { # Check Component Based Servicing reboot pending $csbPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending" if (Test-Path $csbPath) { $rebootPending = $true $rebootReasons += "Component Based Servicing (CBS)" } # Check Windows Update reboot required $wuPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" if (Test-Path $wuPath) { $rebootPending = $true $rebootReasons += "Windows Update" } # Check pending file rename operations $pendingRenamePath = "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations" $pendingRename = Get-ItemProperty -Path $pendingRenamePath -Name "PendingFileRenameOperations" -ErrorAction SilentlyContinue if ($pendingRename -and $pendingRename.PendingFileRenameOperations) { $rebootPending = $true $rebootReasons += "Pending File Rename Operations" } # Check Server Manager reboot attempts (if present) $smPath = "HKLM:\SOFTWARE\Microsoft\ServerManager\CurrentRebootAttempts" if (Test-Path $smPath) { $rebootPending = $true $rebootReasons += "Server Manager Reboot Attempt" } } else { # Remote CIM session - use StdRegProv $regKey = "SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing" $regProv = Get-CimInstance -ClassName StdRegProv -Namespace "root\cimv2" -CimSession $CimSession -ErrorAction SilentlyContinue if ($regProv) { # Check CBS RebootPending $csbValue = $regProv.GetStringValue(2147483650, "$regKey\RebootPending", "") if ($csbValue -and $csbValue.sValue -eq "1") { $rebootPending = $true $rebootReasons += "Component Based Servicing (CBS)" } # Check Windows Update RebootRequired $wuKey = "SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update" $wuValue = $regProv.GetStringValue(2147483650, "$wuKey\RebootRequired", "") if ($wuValue -and $wuValue.sValue -eq "1") { $rebootPending = $true $rebootReasons += "Windows Update" } # Check pending file rename operations $sessionKey = "SYSTEM\CurrentControlSet\Control\Session Manager" $renameValue = $regProv.GetMultiStringValue(2147483650, $sessionKey, "PendingFileRenameOperations") if ($renameValue.sValue -and $renameValue.sValue.Count -gt 0) { $rebootPending = $true $rebootReasons += "Pending File Rename Operations" } # Check Server Manager $smKey = "SOFTWARE\Microsoft\ServerManager" $smValue = $regProv.GetDWORDValue(2147483650, "$smKey\CurrentRebootAttempts", "") if ($smValue -and $smValue.uValue -gt 0) { $rebootPending = $true $rebootReasons += "Server Manager Reboot Attempt" } } } if ($rebootPending) { Write-ColorOutput "PENDING REBOOT DETECTED!" "Red" "Warning" $ComputerName Write-ColorOutput "Reboot required by:" "Yellow" $ComputerName foreach ($reason in $rebootReasons) { Write-ColorOutput " - $reason" "Yellow" $ComputerName } } else { Write-ColorOutput "No pending reboot detected" "Green" "Good" $ComputerName } } catch { Write-ColorOutput "Error checking pending reboot status on ${ComputerName}: $_" "Red" "Critical" $ComputerName } } function Check-NetworkAdapters { param ( [Parameter(Mandatory=$false)] [string]$ComputerName = $env:COMPUTERNAME, [Parameter(Mandatory=$false)] [Microsoft.Management.Infrastructure.CimSession]$CimSession = $null ) Write-ColorOutput "`nChecking Network Adapters..." "Yellow" $ComputerName try { if (-not $CimSession -and $ComputerName -eq $env:COMPUTERNAME) { $adapters = Get-CimInstance -ClassName Win32_NetworkAdapter -ErrorAction Stop | Where-Object { $_.PhysicalAdapter -eq $true -or $_.PhysicalAdapter -eq $null } | Sort-Object Name } else { $adapters = Get-CimInstance -ClassName Win32_NetworkAdapter -CimSession $CimSession -ErrorAction Stop | Where-Object { $_.PhysicalAdapter -eq $true -or $_.PhysicalAdapter -eq $null } | Sort-Object Name } Write-ColorOutput "`nActive Network Adapters:" "White" $ComputerName foreach ($adapter in $adapters) { if ($adapter.NetConnectionStatus -eq 2) { $status = "Connected" $statusColor = "Green" } else { $status = "Disconnected" $statusColor = "Red" } $speed = if ($adapter.Speed) { [math]::Round($adapter.Speed / 1GB, 2) } else { 0 } $speedStr = if ($speed -gt 0) { "$speed Gbps" } else { "Unknown" } Write-ColorOutput "`nAdapter: $($adapter.Name)" "White" $ComputerName Write-ColorOutput " Description: $($adapter.Description)" "White" $ComputerName Write-ColorOutput " Status: $status" $statusColor $ComputerName Write-ColorOutput " Speed: $speedStr" "White" $ComputerName Write-ColorOutput " MAC Address: $($adapter.MACAddress)" "White" $ComputerName # Get IP configuration if (-not $CimSession -and $ComputerName -eq $env:COMPUTERNAME) { $ipConfig = Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration -Filter "InterfaceIndex=$($adapter.InterfaceIndex)" -ErrorAction SilentlyContinue } else { $ipConfig = Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration -CimSession $CimSession -Filter "InterfaceIndex=$($adapter.InterfaceIndex)" -ErrorAction SilentlyContinue } if ($ipConfig) { if ($ipConfig.IPAddress) { Write-ColorOutput " IP Addresses:" "White" $ComputerName foreach ($ip in $ipConfig.IPAddress) { $ipType = if ($ip -like "*:*") { "IPv6" } else { "IPv4" } Write-ColorOutput " ${ipType}: $ip" "White" $ComputerName } } if ($ipConfig.DefaultIPGateway) { Write-ColorOutput " Default Gateway: $($ipConfig.DefaultIPGateway -join ', ')" "White" $ComputerName } if ($ipConfig.DNSServerSearchOrder) { Write-ColorOutput " DNS Servers: $($ipConfig.DNSServerSearchOrder -join ', ')" "White" $ComputerName } } } # Throughput monitoring (1-second sample) Write-ColorOutput "`nNetwork Throughput (1-second sample):" "White" $ComputerName if (-not $CimSession -and $ComputerName -eq $env:COMPUTERNAME) { $perfData = Get-CimInstance -ClassName Win32_PerfFormattedData_Tcpip_NetworkInterface -ErrorAction SilentlyContinue } else { $perfData = Get-CimInstance -ClassName Win32_PerfFormattedData_Tcpip_NetworkInterface -CimSession $CimSession -ErrorAction SilentlyContinue } if ($perfData) { foreach ($iface in $perfData) { $bytesSent = [math]::Round($iface.BytesSentPerSec / 1MB, 2) $bytesRecv = [math]::Round($iface.BytesReceivedPerSec / 1MB, 2) Write-ColorOutput "$($iface.Name): Sent $bytesSent MB/s, Received $bytesRecv MB/s" "White" $ComputerName } } else { Write-ColorOutput " Unable to retrieve throughput data" "Gray" $ComputerName } } catch { Write-ColorOutput "Error checking network adapters on ${ComputerName}: $_" "Red" "Critical" $ComputerName } } # Main program # Run command-line mode if switches are used if (($RunAllChecks) -or ($Silent)) { if (($LogToFile) -or ($ExportHTML)) { if ($ComputerName) { foreach ($computer in $ComputerName) { Show-Banner -ComputerName $computer Run-AllChecks -ComputerName $computer } } else { Show-Banner Run-AllChecks } exit } } # Otherwise run in interactive mode Show-Banner $exitRequested = $false while (-not $exitRequested) { Show-Menu $choice = Read-Host "Enter your choice" switch ($choice.ToUpper()) { "1" { Get-DiskSpace } "2" { Get-CPUUsage } "3" { Get-MemoryUsage } "4" { Test-NetworkConnectivity } "5" { Test-PortExhaustion } "6" { Get-SystemUptime } "7" { Get-TopProcessesByMemory } "8" { Get-TopProcessesByCPU } "9" { Get-ServicesStatus } "10" { Get-EventLogErrors } "11" { Get-WindowsUpdateStatus } "12" { Run-AllChecks } "13" { Check-PendingReboot } "14" { Check-NetworkAdapters } "H" { Show-Help } "L" { Show-LogFiles } "E" { # Generate and open HTML report $originalExportHTML = $ExportHTML $ExportHTML = $true Run-AllChecks $ExportHTML = $originalExportHTML } "Q" { Write-ColorOutput "Exiting script..." "Cyan" $exitRequested = $true } default { Write-ColorOutput "Invalid choice. Please try again." "Red" "Critical" } } if (-not $exitRequested) { Write-ColorOutput "`nPress any key to continue..." "Yellow" $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") Clear-Host Show-Banner } }