Files
Powershell/HealthCheck/PCHealthCheck.ps1
T
Kevin Simmons d276b8fe95 Add Invoke-PCRemediation script with full help documentation
- Complete comment-based help with 5 examples
- Author: Kevin Simmons
- Fixed spacing issues in parameter assignments
- Improved error handling with -ErrorAction Stop
- Supports local/remote CIM sessions (DCOM and WinRM)
- Interactive menu and silent execution modes
- HTML report generation support
2026-10-04 14:40:36 -05:00

1655 lines
68 KiB
PowerShell

<#=================================================================================
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 <name> -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 = @"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PC Health Check Report</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 20px;
background-color: #f0f0f0;
color: #333;
}
.container {
max-width: 1000px;
margin: 0 auto;
background-color: #f8f8f8;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
h1 {
color: #0066cc;
padding-bottom: 10px;
border-bottom: 1px solid #ccc;
}
h2 {
color: #0066cc;
margin-top: 25px;
font-weight: bold;
}
.report-meta {
background-color: #e8f0f8;
padding: 10px;
border-radius: 5px;
margin-bottom: 20px;
}
.section {
margin-bottom: 30px;
border-left: 4px solid #0066cc;
padding-left: 15px;
}
.good {
color: #2e8b57;
font-weight: bold;
}
.warning {
color: #ff9900;
font-weight: bold;
}
.critical {
color: #cc3300;
font-weight: bold;
}
table {
width: 100%;
border-collapse: collapse;
margin: 15px 0;
}
th, td {
padding: 8px;
text-align: left;
border-bottom: 1px solid #ddd;
}
th {
background-color: #e8f0f8;
font-weight: bold;
}
tr:hover {
background-color: #f1f1f1;
}
</style>
</head>
<body>
<div class="container">
<h1>PC Health Check Report</h1>
<div class="report-meta">
<p><strong>System:</strong> $env:COMPUTERNAME</p>
<p><strong>User:</strong> $env:USERNAME</p>
<p><strong>Date:</strong> $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')</p>
</div>
"@
}
# 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 += "<div class='section'><h2>[$ComputerName] $sectionTitle</h2>"
$script:currentSection = $callerName
$script:htmlReport += "<p>$Message</p>"
}
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 += "</div>"
}
$script:htmlReport += "<div class='section'><h2>[$ComputerName] $callerName</h2>"
$script:currentSection = $callerName
$script:htmlReport += "<p>$Message</p>"
}
else {
# Regular message within current section
if ($cssClass -ne "info") {
$script:htmlReport += "<p class='$cssClass'>[$ComputerName] $Message</p>"
} else {
$script:htmlReport += "<p>[$ComputerName] $Message</p>"
}
}
}
function Start-HtmlTable {
param (
[Parameter(Mandatory=$true)]
[string[]]$Headers
)
if ($ExportHTML) {
$headerRow = $Headers | ForEach-Object { "<th>$_</th>" }
$script:htmlReport += "<table><tr>$headerRow</tr>"
}
}
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 { "<td>$_</td>" }
$script:htmlReport += "<tr class='$cssClass'>$cellRows</tr>"
}
}
function End-HtmlTable {
if ($ExportHTML) {
$script:htmlReport += "</table>"
}
}
function Save-HtmlReport {
param (
[Parameter(Mandatory=$false)]
[string]$ComputerName = $env:COMPUTERNAME
)
if ($ExportHTML) {
# Close any open section
if ($script:currentSection) {
$script:htmlReport += "</div>"
}
# Add footer and close HTML
$script:htmlReport += @"
<div class="section">
<h2>Report Summary</h2>
<p>PC Health Check completed on $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')</p>
<p>Target Computer: $ComputerName</p>
</div>
</div>
</body>
</html>
"@
# 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 += @"
<div style="margin-top: 15px; margin-bottom: 15px;">
<div style="background-color: #f0f0f0; border-radius: 5px; height: 30px; width: 100%; overflow: hidden;">
<div style="background-color: $barColor; height: 100%; width: $percentUsed%; text-align: center; color: white; line-height: 30px;">
$percentUsed%
</div>
</div>
<div style="font-size: 12px; text-align: center; margin-top: 5px;">Memory Usage</div>
</div>
"@
}
}
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
}
}