commit 311499546a36178fde4a0ec677f8c7f3741739f3 Author: Kevin Simmons Date: Sun Oct 4 14:10:27 2026 -0500 Initial import diff --git a/HealthCheck/PCHealthCheck.ps1 b/HealthCheck/PCHealthCheck.ps1 new file mode 100644 index 0000000..a7852d0 --- /dev/null +++ b/HealthCheck/PCHealthCheck.ps1 @@ -0,0 +1,1656 @@ +<#================================================================================= +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: Enterprise Admin + 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 +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" + if ($service.Status -ne "Running") { $color = "Red" } + + Write-ColorOutput "$($service.DisplayName): $($service.Status)" $color $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 + Write-Host "Would you like to open the report now? (Y/N)" -ForegroundColor Yellow + $openReport = Read-Host + if ($openReport -like "Y*") { + Start-Process $reportPath + } + } + } + } + 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.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.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 -or $adapter.NetConnectionStatus -eq 7) { + $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 + $ExportHTML = $true + Run-AllChecks + $ExportHTML = $false + } + "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 + } +}