Files
Ping_monitor/Ping_Monitor.ps1
2025-03-20 09:26:40 +01:00

439 lines
16 KiB
PowerShell

# Define the IP addresses to monitor
$addresses = @()
$addressDefinitions = @(
"1.1.1.1",
"1.0.0.1",
"8.8.8.8-9" # Using range notation for cleaner definition
)
# Set up logging configuration - Use absolute path to avoid system32 issues
$scriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path
$logFolder = Join-Path $scriptPath "LogsPing"
$logFile = Join-Path $logFolder "network_monitor.log"
$statLogFile = Join-Path $logFolder "statistics.log"
$csvLogFile = Join-Path $logFolder "ping_data.csv"
$errorLogFile = Join-Path $logFolder "errors.log"
# Create log directory if it doesn't exist
if (-not (Test-Path $logFolder)) {
try {
New-Item -Path $logFolder -ItemType Directory -Force | Out-Null
Write-Host "Created log directory: $logFolder" -ForegroundColor Green
} catch {
Write-Host "Error creating log directory: $_" -ForegroundColor Red
exit 1
}
}
# Initialize log files with headers - Use streamwriter for lower memory footprint
try {
if (-not (Test-Path $csvLogFile)) {
# Ensure directory exists
$csvDir = Split-Path -Parent $csvLogFile
if (-not (Test-Path $csvDir)) {
New-Item -Path $csvDir -ItemType Directory -Force | Out-Null
}
$csvStream = [System.IO.StreamWriter]::new($csvLogFile)
$csvStream.WriteLine("Timestamp,IPAddress,Status,ResponseTime,SuccessRate")
$csvStream.Close()
$csvStream.Dispose()
Write-Host "Created CSV log file: $csvLogFile" -ForegroundColor Green
}
} catch {
Write-Host "Error creating CSV log file: $_" -ForegroundColor Red
}
# Prepare log streamwriters - more memory efficient than Out-File
$logStream = $null
$statLogStream = $null
$errorLogStream = $null
$csvLogStream = $null
# Function to write to log file with timestamp - using streamwriter for efficiency
function Write-Log {
param(
[Parameter(Mandatory=$true)]
[string]$Message,
[string]$LogPath = $logFile,
[switch]$NoConsole
)
$timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
$logLine = "$timestamp - $Message"
# Use streamwriter instead of Out-File with error handling
try {
# Ensure directory exists
$logDir = Split-Path -Parent $LogPath
if (-not (Test-Path $logDir)) {
New-Item -Path $logDir -ItemType Directory -Force | Out-Null
}
$stream = [System.IO.StreamWriter]::new($LogPath, $true)
$stream.WriteLine($logLine)
$stream.Close()
$stream.Dispose()
} catch {
# Fall back to console output if file writing fails
Write-Host "Failed to write to log file $LogPath. Error: $_" -ForegroundColor Red
}
if (-not $NoConsole) {
Write-Host $logLine
}
}
# Log script start
Write-Log "Network monitoring script started"
# Set script end time (24 hours from now)
$endTime = (Get-Date).AddHours(24)
Write-Log "Script will automatically stop at $($endTime.ToString('yyyy-MM-dd HH:mm:ss'))"
# Process IP address definitions more efficiently
foreach ($addr in $addressDefinitions) {
if ($addr -match "(\d+\.\d+\.\d+\.)(\d+)-(\d+)") {
$prefix = $matches[1]
$start = [int]$matches[2]
$end = [int]$matches[3]
for ($i = $start; $i -le $end; $i++) {
$addresses += "$prefix$i"
}
} else {
$addresses += $addr
}
}
# Log monitored addresses
Write-Log "Monitoring IP addresses: $($addresses -join ', ')"
# Initialize statistics storage - Use simpler data structures for memory efficiency
$sampleSize = 60 # Maximum number of samples to keep
$stats = @{}
foreach ($addr in $addresses) {
# Use fixed-size arrays instead of queue for better memory efficiency
$stats[$addr] = @{
MinTime = [int]::MaxValue
MaxTime = 0
SampleArray = New-Object 'int[]' $sampleSize # Fixed-size array
SamplePosition = 0 # Current position in array
SampleCount = 0 # Current number of samples (up to sampleSize)
SuccessCount = 0
FailCount = 0
LastStatus = $null
LastLoggedStats = [DateTime]::MinValue
}
}
# More efficient mean calculation for fixed array
function Get-ArrayMean {
param(
[int[]]$Array,
[int]$Count,
[int]$Max
)
if ($Count -eq 0) { return 0 }
$sum = 0
$actualCount = [Math]::Min($Count, $Max)
for ($i = 0; $i -lt $actualCount; $i++) {
$sum += $Array[$i]
}
return $sum / $actualCount
}
# Function to clear the console and position cursor
function Reset-Display {
Clear-Host
$host.UI.RawUI.CursorPosition = @{X=0; Y=0}
}
# Function to format number with fixed width - optimized to reduce string operations
function Format-Number {
param(
[Parameter(Mandatory=$true)]
[double]$Number,
[int]$Width = 6,
[int]$Decimals = 1
)
if ($Number -eq [int]::MaxValue) {
return ' ' * $Width
}
$formatted = [string]::Format("{0:F$Decimals}", $Number)
$padding = $Width - $formatted.Length
if ($padding -gt 0) {
return (' ' * $padding) + $formatted
}
return $formatted
}
# Function to log status changes - streamwriter based
function Write-StatusLog {
param(
[string]$Address,
[bool]$OldStatus,
[bool]$NewStatus
)
$statusText = if ($NewStatus) { "UP" } else { "DOWN" }
$oldStatusText = if ($OldStatus) { "UP" } else { "DOWN" }
$logMessage = "$Address status changed from $oldStatusText to $statusText"
Write-Log $logMessage
Write-Host "`n$logMessage" -ForegroundColor Yellow
}
# Function to log ping data to CSV - using streamwriter for efficiency
function Write-PingDataLog {
param(
[string]$Address,
[bool]$Status,
[int]$ResponseTime,
[double]$SuccessRate
)
$statusText = if ($Status) { "UP" } else { "DOWN" }
$timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
try {
$stream = [System.IO.StreamWriter]::new($csvLogFile, $true)
$stream.WriteLine("$timestamp,$Address,$statusText,$ResponseTime,$SuccessRate")
$stream.Close()
$stream.Dispose()
} catch {
Write-Host "Error writing to CSV log file: $_" -ForegroundColor Red
}
}
# Function to log statistics periodically - streamwriter based
function Write-StatisticsLog {
param(
[string]$Address,
[hashtable]$Statistics,
[int]$IntervalMinutes = 15
)
$now = Get-Date
if (($now - $Statistics.LastLoggedStats).TotalMinutes -ge $IntervalMinutes) {
$totalRequests = $Statistics.SuccessCount + $Statistics.FailCount
$successRate = if ($totalRequests -gt 0) {
[math]::Round(($Statistics.SuccessCount / $totalRequests) * 100, 1)
} else { 0 }
$meanTime = [math]::Round((Get-ArrayMean -Array $Statistics.SampleArray -Count $Statistics.SampleCount -Max $sampleSize), 1)
$statsMessage = "$Address Stats - Min: $($Statistics.MinTime)ms, Max: $($Statistics.MaxTime)ms, " +
"Mean: ${meanTime}ms, Success Rate: ${successRate}%, " +
"Success Count: $($Statistics.SuccessCount), Fail Count: $($Statistics.FailCount)"
Write-Log -Message $statsMessage -LogPath $statLogFile -NoConsole
$Statistics.LastLoggedStats = $now
}
}
# Function to handle errors - streamwriter based
function Write-ErrorLog {
param(
[Parameter(Mandatory=$true)]
[System.Management.Automation.ErrorRecord]$ErrorRecord,
[string]$Context = "General"
)
$errorMessage = "[$Context] $($ErrorRecord.Exception.GetType().Name): $($ErrorRecord.Exception.Message)"
# More memory efficient than Format-List
$errorDetails = "Exception: $($ErrorRecord.Exception)"
Write-Log -Message $errorMessage -LogPath $errorLogFile -NoConsole
Write-Log -Message "Details: $errorDetails" -LogPath $errorLogFile -NoConsole
Write-Host "Error: $errorMessage" -ForegroundColor Red
}
# Function to display time remaining
function Format-TimeRemaining {
param([TimeSpan]$TimeSpan)
return "{0:D2}:{1:D2}:{2:D2}" -f $TimeSpan.Hours, $TimeSpan.Minutes, $TimeSpan.Seconds
}
# Memory cleanup function - call periodically
function Invoke-MemoryCleanup {
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
[System.GC]::Collect()
}
# Display script information
Write-Host "Network Monitoring Tool" -ForegroundColor Cyan
Write-Host "======================" -ForegroundColor Cyan
Write-Host "Log Directory: $logFolder" -ForegroundColor Yellow
Write-Host "Monitoring these IPs: $($addresses -join ', ')" -ForegroundColor Yellow
Write-Host "Press Enter to continue or Ctrl+C to exit..." -ForegroundColor Green
$null = Read-Host
# Main monitoring loop
try {
$pingInterval = 1 # seconds between pings
$statsLogInterval = 15 # minutes between logging statistics
$csvLogFrequency = 5 # log every Nth ping to CSV to avoid huge files
$memoryCleanupInterval = 100 # Garbage collect every N pings
$pingCounter = 0
# Create ping object once, outside the loop
$ping = New-Object System.Net.NetworkInformation.Ping
Write-Log "Monitoring started with ping interval of ${pingInterval}s and stats logging every ${statsLogInterval} minutes"
while ($true) {
# Check if 24 hours have elapsed
$currentTime = Get-Date
if ($currentTime -ge $endTime) {
Write-Log "24-hour monitoring period completed. Stopping the script."
break
}
$timeRemaining = $endTime - $currentTime
Reset-Display
$currentTimeFormatted = $currentTime.ToString("HH:mm:ss")
Write-Host "Network Monitor - Last Updated: $currentTimeFormatted" -ForegroundColor Cyan
Write-Host "Time remaining: $(Format-TimeRemaining -TimeSpan $timeRemaining)" -ForegroundColor Cyan
Write-Host "Press Ctrl+C to exit`n" -ForegroundColor DarkGray
# Header with fixed widths
Write-Host "IP Address Status Min Max Mean Success% Fails Trend" -ForegroundColor Yellow
Write-Host "--------------- -------- ----- ----- ----- -------- ------ -------" -ForegroundColor Yellow
$pingCounter++
# Force garbage collection periodically
if ($pingCounter % $memoryCleanupInterval -eq 0) {
Invoke-MemoryCleanup
}
foreach ($addr in $addresses) {
try {
$result = $ping.Send($addr, 1000) # 1 second timeout
$status = $result.Status -eq 'Success'
$responseTime = if ($status) { $result.RoundtripTime } else { 0 }
} catch {
$status = $false
$responseTime = 0
Write-ErrorLog -ErrorRecord $_ -Context "Ping to $addr"
}
if ($status) {
$stats[$addr].SuccessCount++
# Update sample array using fixed position tracking
$position = $stats[$addr].SamplePosition
$stats[$addr].SampleArray[$position] = $responseTime
$stats[$addr].SamplePosition = ($position + 1) % $sampleSize
if ($stats[$addr].SampleCount -lt $sampleSize) {
$stats[$addr].SampleCount++
}
# Update min/max
if ($stats[$addr].MinTime -eq [int]::MaxValue -or $responseTime -lt $stats[$addr].MinTime) {
$stats[$addr].MinTime = $responseTime
}
if ($responseTime -gt $stats[$addr].MaxTime) {
$stats[$addr].MaxTime = $responseTime
}
$statusText = "UP"
$color = "Green"
} else {
$stats[$addr].FailCount++
$statusText = "DOWN"
$color = "Red"
}
# Calculate statistics
$totalRequests = $stats[$addr].SuccessCount + $stats[$addr].FailCount
$successRate = if ($totalRequests -gt 0) {
[math]::Round(($stats[$addr].SuccessCount / $totalRequests) * 100, 1)
} else { 0 }
$meanTime = [math]::Round((Get-ArrayMean -Array $stats[$addr].SampleArray -Count $stats[$addr].SampleCount -Max $sampleSize), 1)
# Check for status changes and log them
if ($null -ne $stats[$addr].LastStatus -and $stats[$addr].LastStatus -ne $status) {
Write-StatusLog -Address $addr -OldStatus $stats[$addr].LastStatus -NewStatus $status
}
$stats[$addr].LastStatus = $status
# Log statistics periodically
Write-StatisticsLog -Address $addr -Statistics $stats[$addr] -IntervalMinutes $statsLogInterval
# Log to CSV (but not every ping to avoid huge files)
if ($pingCounter % $csvLogFrequency -eq 0) {
Write-PingDataLog -Address $addr -Status $status -ResponseTime $responseTime -SuccessRate $successRate
}
# Calculate trend more efficiently - FIXED: Using ASCII characters instead of Unicode
$trend = "-" # Default to neutral (horizontal line)
if ($stats[$addr].SampleCount -ge 5) {
$samples = $stats[$addr].SampleArray
$count = $stats[$addr].SampleCount
$position = $stats[$addr].SamplePosition
# Get first sample (oldest of last 5)
$firstIndex = ($position - 5 + $sampleSize) % $sampleSize
$first = $samples[$firstIndex]
# Get last sample (newest)
$lastIndex = ($position - 1 + $sampleSize) % $sampleSize
$last = $samples[$lastIndex]
$slope = $last - $first
if ([Math]::Abs($slope) -lt 5) {
$trend = "-" # Neutral
} elseif ($slope -lt 0) {
$trend = "v" # Down arrow
} else {
$trend = "^" # Up arrow
}
}
# Format output fields - efficient string formatting
$ipField = $addr.PadRight(15)
$statusField = $statusText.PadRight(8)
$minField = Format-Number -Number $stats[$addr].MinTime -Width 5 -Decimals 0
$maxField = Format-Number -Number $stats[$addr].MaxTime -Width 5 -Decimals 0
$meanField = Format-Number -Number $meanTime -Width 5 -Decimals 1
$successField = Format-Number -Number $successRate -Width 8 -Decimals 1
$failsField = $stats[$addr].FailCount.ToString().PadLeft(6)
# Display the line
Write-Host $ipField -NoNewline
Write-Host $statusField -NoNewline -ForegroundColor $color
Write-Host "$minField $maxField $meanField $successField $failsField $trend"
}
Start-Sleep -Seconds $pingInterval
}
} catch {
# Log any unhandled exceptions
Write-ErrorLog -ErrorRecord $_ -Context "Main monitoring loop"
} finally {
# Final garbage collection and cleanup
Invoke-MemoryCleanup
# Cleanup when script is interrupted
$endMessage = "Monitoring stopped at $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Write-Log $endMessage
Write-Host "`n$endMessage" -ForegroundColor Cyan
# Dispose of ping object
if ($ping -ne $null) {
$ping.Dispose()
}
}