# 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() } }