Network Performance Monitoring on Windows
Network performance monitoring is critical for maintaining healthy IT infrastructure. This guide provides comprehensive solutions for monitoring network performance on Windows systems, from basic PowerShell scripts to enterprise-grade monitoring integrations.
Windows Network Monitoring Architecture
Understanding Windows network monitoring capabilities helps in building effective monitoring solutions:
graph TB
subgraph "Windows Network Stack"
subgraph "Data Sources"
WMI[WMI Classes]
PerfCounters[Performance Counters]
ETW[Event Tracing]
NetEvents[Network Events]
end
subgraph "Collection Methods"
PowerShell[PowerShell]
PerfMon[Performance Monitor]
NetMon[Network Monitor]
WPA[Windows Performance Analyzer]
end
subgraph "Metrics"
Bandwidth[Bandwidth Usage]
Latency[Network Latency]
PacketLoss[Packet Loss]
Errors[Interface Errors]
Connections[TCP Connections]
DNS[DNS Performance]
end
end
subgraph "Monitoring Solutions"
PRTG[PRTG Network Monitor]
Zabbix[Zabbix Agent]
Prometheus[Windows Exporter]
SCOM[System Center]
end
WMI --> PowerShell
PerfCounters --> PerfMon
ETW --> WPA
NetEvents --> NetMon
PowerShell --> Bandwidth
PowerShell --> Latency
PerfMon --> PacketLoss
NetMon --> Connections
Bandwidth --> PRTG
Latency --> Zabbix
Errors --> Prometheus
DNS --> SCOM
style WMI fill:#f96,stroke:#333,stroke-width:2px
style PowerShell fill:#9f9,stroke:#333,stroke-width:2px
PowerShell Network Monitoring Framework
Comprehensive Network Monitor Script
# NetworkPerformanceMonitor.ps1
# Enterprise-grade network performance monitoring for Windows
param(
[string]$OutputPath = "C:\Monitoring\NetworkMetrics",
[int]$IntervalSeconds = 60,
[string]$LogServer = "syslog.company.com",
[int]$LogPort = 514,
[switch]$EnableAlerts,
[string]$SmtpServer = "smtp.company.com",
[string[]]$AlertRecipients = @("netops@company.com")
)
# Initialize monitoring framework
$ErrorActionPreference = "Stop"
$VerbosePreference = "Continue"
# Create output directory if it doesn't exist
if (!(Test-Path $OutputPath)) {
New-Item -Path $OutputPath -ItemType Directory -Force | Out-Null
}
# Logging function
function Write-NetworkLog {
param(
[string]$Message,
[string]$Level = "INFO"
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logEntry = "$timestamp [$Level] $Message"
# Write to file
$logFile = Join-Path $OutputPath "networkmonitor_$(Get-Date -Format 'yyyyMMdd').log"
Add-Content -Path $logFile -Value $logEntry
# Send to syslog if configured
if ($LogServer) {
Send-SyslogMessage -Server $LogServer -Port $LogPort -Message $logEntry -Facility Local0 -Severity $Level
}
# Console output
switch ($Level) {
"ERROR" { Write-Host $logEntry -ForegroundColor Red }
"WARN" { Write-Host $logEntry -ForegroundColor Yellow }
"INFO" { Write-Host $logEntry -ForegroundColor Green }
default { Write-Host $logEntry }
}
}
# Network adapter information collection
function Get-NetworkAdapterMetrics {
$adapters = Get-NetAdapter | Where-Object { $_.Status -eq "Up" }
$metrics = @()
foreach ($adapter in $adapters) {
Write-Verbose "Collecting metrics for adapter: $($adapter.Name)"
# Get adapter statistics
$stats = Get-NetAdapterStatistics -Name $adapter.Name
# Get IP configuration
$ipConfig = Get-NetIPConfiguration -InterfaceAlias $adapter.Name
# Get performance counters
$perfCounters = @(
"\Network Interface($($adapter.Description))\Bytes Total/sec",
"\Network Interface($($adapter.Description))\Bytes Sent/sec",
"\Network Interface($($adapter.Description))\Bytes Received/sec",
"\Network Interface($($adapter.Description))\Packets/sec",
"\Network Interface($($adapter.Description))\Packets Sent/sec",
"\Network Interface($($adapter.Description))\Packets Received/sec",
"\Network Interface($($adapter.Description))\Packets Outbound Errors",
"\Network Interface($($adapter.Description))\Packets Received Errors",
"\Network Interface($($adapter.Description))\Current Bandwidth"
)
$perfData = @{}
foreach ($counter in $perfCounters) {
try {
$value = (Get-Counter -Counter $counter -ErrorAction SilentlyContinue).CounterSamples[0].CookedValue
$perfData[$counter.Split('\')[-1]] = $value
} catch {
Write-NetworkLog "Failed to get counter: $counter" -Level "WARN"
}
}
$metric = [PSCustomObject]@{
Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
AdapterName = $adapter.Name
AdapterDescription = $adapter.Description
MacAddress = $adapter.MacAddress
LinkSpeed = $adapter.LinkSpeed
Status = $adapter.Status
IPAddress = $ipConfig.IPv4Address.IPAddress
Gateway = $ipConfig.IPv4DefaultGateway.NextHop
DNSServer = ($ipConfig.DNSServer.ServerAddresses -join ',')
BytesSent = $stats.SentBytes
BytesReceived = $stats.ReceivedBytes
PacketsSent = $stats.SentUnicastPackets
PacketsReceived = $stats.ReceivedUnicastPackets
Errors = $stats.ReceivedPacketErrors + $stats.OutboundPacketErrors
Discards = $stats.ReceivedDiscardedPackets + $stats.OutboundDiscardedPackets
BytesPerSecond = $perfData['Bytes Total/sec']
CurrentBandwidth = $perfData['Current Bandwidth']
Utilization = if ($perfData['Current Bandwidth'] -gt 0) {
($perfData['Bytes Total/sec'] * 8 / $perfData['Current Bandwidth']) * 100
} else { 0 }
}
$metrics += $metric
}
return $metrics
}
# TCP connection monitoring
function Get-TCPConnectionMetrics {
$connections = Get-NetTCPConnection
$connectionStats = @{
Total = $connections.Count
Established = ($connections | Where-Object { $_.State -eq "Established" }).Count
Listen = ($connections | Where-Object { $_.State -eq "Listen" }).Count
TimeWait = ($connections | Where-Object { $_.State -eq "TimeWait" }).Count
CloseWait = ($connections | Where-Object { $_.State -eq "CloseWait" }).Count
SynSent = ($connections | Where-Object { $_.State -eq "SynSent" }).Count
SynReceived = ($connections | Where-Object { $_.State -eq "SynReceived" }).Count
}
# Get connection details by service
$serviceConnections = @{}
foreach ($conn in $connections | Where-Object { $_.State -eq "Established" }) {
try {
$process = Get-Process -Id $conn.OwningProcess -ErrorAction SilentlyContinue
if ($process) {
if (!$serviceConnections.ContainsKey($process.ProcessName)) {
$serviceConnections[$process.ProcessName] = 0
}
$serviceConnections[$process.ProcessName]++
}
} catch {
# Process might have terminated
}
}
return [PSCustomObject]@{
Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
ConnectionStates = $connectionStats
ServiceConnections = $serviceConnections
TopServices = ($serviceConnections.GetEnumerator() | Sort-Object Value -Descending | Select-Object -First 10)
}
}
# Network latency testing
function Test-NetworkLatency {
param(
[string[]]$Targets = @("8.8.8.8", "1.1.1.1", "gateway")
)
$results = @()
foreach ($target in $Targets) {
if ($target -eq "gateway") {
$gateway = (Get-NetRoute -DestinationPrefix "0.0.0.0/0" | Select-Object -First 1).NextHop
if ($gateway) {
$target = $gateway
} else {
continue
}
}
Write-Verbose "Testing latency to $target"
try {
$pingResults = Test-Connection -ComputerName $target -Count 10 -ErrorAction Stop
$latencies = $pingResults | ForEach-Object { $_.ResponseTime }
$avgLatency = ($latencies | Measure-Object -Average).Average
$minLatency = ($latencies | Measure-Object -Minimum).Minimum
$maxLatency = ($latencies | Measure-Object -Maximum).Maximum
# Calculate jitter (standard deviation)
$mean = $avgLatency
$variance = ($latencies | ForEach-Object { [Math]::Pow($_ - $mean, 2) } | Measure-Object -Average).Average
$jitter = [Math]::Sqrt($variance)
# Calculate packet loss
$packetLoss = (10 - $pingResults.Count) / 10 * 100
$result = [PSCustomObject]@{
Target = $target
AverageLatency = [Math]::Round($avgLatency, 2)
MinimumLatency = $minLatency
MaximumLatency = $maxLatency
Jitter = [Math]::Round($jitter, 2)
PacketLoss = $packetLoss
Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
}
$results += $result
} catch {
Write-NetworkLog "Failed to ping $target : $_" -Level "WARN"
$results += [PSCustomObject]@{
Target = $target
AverageLatency = -1
MinimumLatency = -1
MaximumLatency = -1
Jitter = -1
PacketLoss = 100
Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
}
}
}
return $results
}
# DNS performance testing
function Test-DNSPerformance {
param(
[string[]]$TestDomains = @("google.com", "microsoft.com", "cloudflare.com")
)
$dnsServers = (Get-DnsClientServerAddress -AddressFamily IPv4 |
Where-Object { $_.ServerAddresses } |
Select-Object -ExpandProperty ServerAddresses -Unique)
$results = @()
foreach ($server in $dnsServers) {
foreach ($domain in $TestDomains) {
Write-Verbose "Testing DNS resolution for $domain using $server"
$measurements = @()
for ($i = 0; $i -lt 5; $i++) {
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
try {
Resolve-DnsName -Name $domain -Server $server -ErrorAction Stop | Out-Null
$stopwatch.Stop()
$measurements += $stopwatch.ElapsedMilliseconds
} catch {
Write-NetworkLog "DNS resolution failed for $domain on $server" -Level "WARN"
$measurements += -1
}
}
$validMeasurements = $measurements | Where-Object { $_ -gt 0 }
if ($validMeasurements) {
$avgTime = ($validMeasurements | Measure-Object -Average).Average
$successRate = ($validMeasurements.Count / 5) * 100
} else {
$avgTime = -1
$successRate = 0
}
$results += [PSCustomObject]@{
DNSServer = $server
Domain = $domain
AverageResponseTime = [Math]::Round($avgTime, 2)
SuccessRate = $successRate
Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
}
}
}
return $results
}
# Alert function
function Send-NetworkAlert {
param(
[string]$Subject,
[string]$Body,
[string]$Priority = "Normal"
)
if ($EnableAlerts -and $SmtpServer -and $AlertRecipients) {
try {
$mailParams = @{
To = $AlertRecipients
From = "networkmonitor@company.com"
Subject = "Network Alert: $Subject"
Body = $Body
SmtpServer = $SmtpServer
Priority = $Priority
}
Send-MailMessage @mailParams
Write-NetworkLog "Alert sent: $Subject" -Level "INFO"
} catch {
Write-NetworkLog "Failed to send alert: $_" -Level "ERROR"
}
}
}
# Threshold checking
function Check-NetworkThresholds {
param(
[object]$Metrics
)
# Check bandwidth utilization
foreach ($adapter in $Metrics.AdapterMetrics) {
if ($adapter.Utilization -gt 80) {
Send-NetworkAlert -Subject "High Network Utilization" `
-Body "Network adapter $($adapter.AdapterName) is at $([Math]::Round($adapter.Utilization, 2))% utilization" `
-Priority "High"
}
if ($adapter.Errors -gt 100) {
Send-NetworkAlert -Subject "High Network Errors" `
-Body "Network adapter $($adapter.AdapterName) has $($adapter.Errors) errors" `
-Priority "High"
}
}
# Check latency
foreach ($latency in $Metrics.LatencyMetrics) {
if ($latency.AverageLatency -gt 100 -and $latency.AverageLatency -ne -1) {
Send-NetworkAlert -Subject "High Network Latency" `
-Body "Latency to $($latency.Target) is $($latency.AverageLatency)ms" `
-Priority "Normal"
}
if ($latency.PacketLoss -gt 5) {
Send-NetworkAlert -Subject "Packet Loss Detected" `
-Body "Packet loss to $($latency.Target) is $($latency.PacketLoss)%" `
-Priority "High"
}
}
# Check DNS performance
foreach ($dns in $Metrics.DNSMetrics) {
if ($dns.SuccessRate -lt 80) {
Send-NetworkAlert -Subject "DNS Resolution Issues" `
-Body "DNS server $($dns.DNSServer) has only $($dns.SuccessRate)% success rate for $($dns.Domain)" `
-Priority "High"
}
}
}
# Export metrics to various formats
function Export-NetworkMetrics {
param(
[object]$Metrics,
[string]$Format = "JSON"
)
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
switch ($Format) {
"JSON" {
$outputFile = Join-Path $OutputPath "network_metrics_$timestamp.json"
$Metrics | ConvertTo-Json -Depth 10 | Out-File $outputFile
}
"CSV" {
# Export adapter metrics
$adapterFile = Join-Path $OutputPath "adapter_metrics_$timestamp.csv"
$Metrics.AdapterMetrics | Export-Csv -Path $adapterFile -NoTypeInformation
# Export latency metrics
$latencyFile = Join-Path $OutputPath "latency_metrics_$timestamp.csv"
$Metrics.LatencyMetrics | Export-Csv -Path $latencyFile -NoTypeInformation
# Export DNS metrics
$dnsFile = Join-Path $OutputPath "dns_metrics_$timestamp.csv"
$Metrics.DNSMetrics | Export-Csv -Path $dnsFile -NoTypeInformation
}
"InfluxDB" {
# Format for InfluxDB line protocol
$lines = @()
foreach ($adapter in $Metrics.AdapterMetrics) {
$tags = "adapter=$($adapter.AdapterName.Replace(' ','_')),host=$env:COMPUTERNAME"
$fields = "bytes_sent=$($adapter.BytesSent)i,bytes_received=$($adapter.BytesReceived)i,utilization=$($adapter.Utilization),errors=$($adapter.Errors)i"
$timestamp = [DateTimeOffset]::Now.ToUnixTimeMilliseconds() * 1000000
$lines += "network_adapter,$tags $fields $timestamp"
}
$influxFile = Join-Path $OutputPath "influx_metrics_$timestamp.txt"
$lines | Out-File $influxFile
}
}
Write-NetworkLog "Metrics exported to $OutputPath in $Format format" -Level "INFO"
}
# Main monitoring loop
function Start-NetworkMonitoring {
Write-NetworkLog "Starting Network Performance Monitor" -Level "INFO"
Write-NetworkLog "Output Path: $OutputPath" -Level "INFO"
Write-NetworkLog "Monitoring Interval: $IntervalSeconds seconds" -Level "INFO"
while ($true) {
try {
$startTime = Get-Date
Write-NetworkLog "Starting metrics collection cycle" -Level "INFO"
# Collect all metrics
$metrics = @{
CollectionTime = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Hostname = $env:COMPUTERNAME
AdapterMetrics = Get-NetworkAdapterMetrics
ConnectionMetrics = Get-TCPConnectionMetrics
LatencyMetrics = Test-NetworkLatency
DNSMetrics = Test-DNSPerformance
}
# Check thresholds and send alerts
Check-NetworkThresholds -Metrics $metrics
# Export metrics
Export-NetworkMetrics -Metrics $metrics -Format "JSON"
Export-NetworkMetrics -Metrics $metrics -Format "CSV"
# Calculate collection time
$endTime = Get-Date
$duration = ($endTime - $startTime).TotalSeconds
Write-NetworkLog "Metrics collection completed in $duration seconds" -Level "INFO"
# Sleep for the remaining interval
$sleepTime = [Math]::Max(0, $IntervalSeconds - $duration)
if ($sleepTime -gt 0) {
Start-Sleep -Seconds $sleepTime
}
} catch {
Write-NetworkLog "Error in monitoring loop: $_" -Level "ERROR"
Start-Sleep -Seconds 60
}
}
}
# Start monitoring
Start-NetworkMonitoring
Performance Monitor Configuration
Custom Data Collector Set
<?xml version="1.0" encoding="UTF-8"?>
<DataCollectorSet>
<Name>Network Performance Monitoring</Name>
<Description>Comprehensive network performance data collection</Description>
<SchedulesEnabled>true</SchedulesEnabled>
<Schedule>
<Days>127</Days>
<StartTime>00:00:00</StartTime>
<EndTime>23:59:59</EndTime>
</Schedule>
<PerformanceCounterDataCollector>
<Name>Network Counters</Name>
<FileName>NetworkPerf</FileName>
<FileNameFormat>3</FileNameFormat>
<FileNameFormatPattern>yyyyMMdd-HHmm</FileNameFormatPattern>
<LogFileFormat>3</LogFileFormat>
<SampleInterval>15</SampleInterval>
<SegmentMaxRecords>0</SegmentMaxRecords>
<CounterDisplayName>\Network Interface(*)\Bytes Total/sec</CounterDisplayName>
<CounterDisplayName>\Network Interface(*)\Bytes Sent/sec</CounterDisplayName>
<CounterDisplayName>\Network Interface(*)\Bytes Received/sec</CounterDisplayName>
<CounterDisplayName>\Network Interface(*)\Packets/sec</CounterDisplayName>
<CounterDisplayName>\Network Interface(*)\Packets Sent/sec</CounterDisplayName>
<CounterDisplayName>\Network Interface(*)\Packets Received/sec</CounterDisplayName>
<CounterDisplayName>\Network Interface(*)\Packets Outbound Errors</CounterDisplayName>
<CounterDisplayName>\Network Interface(*)\Packets Received Errors</CounterDisplayName>
<CounterDisplayName>\Network Interface(*)\Packets Outbound Discarded</CounterDisplayName>
<CounterDisplayName>\Network Interface(*)\Packets Received Discarded</CounterDisplayName>
<CounterDisplayName>\Network Interface(*)\Current Bandwidth</CounterDisplayName>
<CounterDisplayName>\Network Interface(*)\Output Queue Length</CounterDisplayName>
<CounterDisplayName>\TCPv4\Segments/sec</CounterDisplayName>
<CounterDisplayName>\TCPv4\Segments Sent/sec</CounterDisplayName>
<CounterDisplayName>\TCPv4\Segments Received/sec</CounterDisplayName>
<CounterDisplayName>\TCPv4\Segments Retransmitted/sec</CounterDisplayName>
<CounterDisplayName>\TCPv4\Connection Failures</CounterDisplayName>
<CounterDisplayName>\TCPv4\Connections Active</CounterDisplayName>
<CounterDisplayName>\TCPv4\Connections Established</CounterDisplayName>
<CounterDisplayName>\UDPv4\Datagrams/sec</CounterDisplayName>
<CounterDisplayName>\UDPv4\Datagrams Sent/sec</CounterDisplayName>
<CounterDisplayName>\UDPv4\Datagrams Received/sec</CounterDisplayName>
<CounterDisplayName>\UDPv4\Datagrams No Port/sec</CounterDisplayName>
<CounterDisplayName>\UDPv4\Datagrams Received Errors</CounterDisplayName>
</PerformanceCounterDataCollector>
<DataManager>
<Enabled>true</Enabled>
<CheckBeforeRunning>true</CheckBeforeRunning>
<MinFreeDisk>1024</MinFreeDisk>
<MaxSize>1024</MaxSize>
<MaxFolderCount>100</MaxFolderCount>
<ResourcePolicy>0</ResourcePolicy>
</DataManager>
</DataCollectorSet>
PowerShell Script to Create Data Collector
# Create-NetworkPerfMonCollector.ps1
function New-NetworkPerformanceCollector {
param(
[string]$CollectorName = "Network Performance Monitoring",
[string]$OutputPath = "C:\PerfLogs\Network",
[int]$SampleInterval = 15,
[int]$MaxSizeMB = 1024
)
# Create output directory
if (!(Test-Path $OutputPath)) {
New-Item -Path $OutputPath -ItemType Directory -Force
}
# Define counters
$counters = @(
"\Network Interface(*)\Bytes Total/sec",
"\Network Interface(*)\Bytes Sent/sec",
"\Network Interface(*)\Bytes Received/sec",
"\Network Interface(*)\Packets/sec",
"\Network Interface(*)\Packets Sent/sec",
"\Network Interface(*)\Packets Received/sec",
"\Network Interface(*)\Packets Outbound Errors",
"\Network Interface(*)\Packets Received Errors",
"\Network Interface(*)\Current Bandwidth",
"\Network Interface(*)\Output Queue Length",
"\TCPv4\Segments/sec",
"\TCPv4\Segments Retransmitted/sec",
"\TCPv4\Connection Failures",
"\TCPv4\Connections Established",
"\UDPv4\Datagrams/sec",
"\UDPv4\Datagrams Received Errors",
"\IPv4\Datagrams/sec",
"\IPv4\Datagrams Forwarded/sec",
"\IPv4\Datagrams Outbound Discarded",
"\IPv4\Datagrams Outbound No Route"
)
# Create Data Collector Set
$datacollectorset = New-Object -COM Pla.DataCollectorSet
$datacollectorset.DisplayName = $CollectorName
$datacollectorset.Duration = 0
$datacollectorset.SubdirectoryFormat = 1
$datacollectorset.SubdirectoryFormatPattern = "yyyy-MM-dd"
$datacollectorset.RootPath = $OutputPath
# Create Performance Counter Data Collector
$collector = $datacollectorset.DataCollectors.CreateDataCollector(0)
$collector.Name = "Network Performance Counters"
$collector.FileName = "NetworkPerf"
$collector.FileNameFormat = 0x1
$collector.FileNameFormatPattern = "yyyy-MM-dd_HH-mm"
$collector.SampleInterval = $SampleInterval
$collector.LogFileFormat = 0x3 # Binary format
# Add counters
$collector.PerformanceCounters = $counters
# Add collector to set
$datacollectorset.DataCollectors.Add($collector)
# Set schedule
$schedule = $datacollectorset.Schedules.CreateSchedule()
$schedule.Days = 127 # Every day
$schedule.StartDate = (Get-Date).ToString("M/d/yyyy")
$schedule.StartTime = (Get-Date "12:00 AM").ToString("HH:mm:ss")
$datacollectorset.Schedules.Add($schedule)
# Configure data management
$datacollectorset.Segment = $true
$datacollectorset.SegmentMaxDuration = 3600 # 1 hour segments
$datacollectorset.SegmentMaxSize = 100 # 100 MB segments
try {
# Commit and start
$datacollectorset.Commit($CollectorName, $null, 0x0003) # Create or modify
$datacollectorset.Start($false)
Write-Host "Data Collector Set '$CollectorName' created and started successfully" -ForegroundColor Green
Write-Host "Output path: $OutputPath" -ForegroundColor Yellow
} catch {
Write-Error "Failed to create Data Collector Set: $_"
}
}
# Create the collector
New-NetworkPerformanceCollector
WMI-Based Network Monitoring
Advanced WMI Network Monitor
# WMI-NetworkMonitor.ps1
class WMINetworkMonitor {
[string]$ComputerName
[System.Management.Automation.PSCredential]$Credential
WMINetworkMonitor([string]$computerName = "localhost") {
$this.ComputerName = $computerName
}
[object[]] GetNetworkAdapterConfiguration() {
$query = "SELECT * FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = TRUE"
$params = @{
Query = $query
ComputerName = $this.ComputerName
}
if ($this.Credential) {
$params.Credential = $this.Credential
}
return Get-WmiObject @params
}
[object[]] GetNetworkAdapterStatistics() {
$configs = $this.GetNetworkAdapterConfiguration()
$results = @()
foreach ($config in $configs) {
# Get corresponding adapter
$adapter = Get-WmiObject -Query "SELECT * FROM Win32_NetworkAdapter WHERE Index = $($config.Index)" `
-ComputerName $this.ComputerName
# Get performance data
$perfQuery = "SELECT * FROM Win32_PerfRawData_Tcpip_NetworkInterface WHERE Name LIKE '%$($adapter.Name)%'"
$perfData = Get-WmiObject -Query $perfQuery -ComputerName $this.ComputerName
if ($perfData) {
$result = [PSCustomObject]@{
AdapterName = $adapter.Name
Description = $adapter.Description
MACAddress = $config.MACAddress
IPAddress = $config.IPAddress -join ", "
SubnetMask = $config.IPSubnet -join ", "
DefaultGateway = $config.DefaultIPGateway -join ", "
DNSServers = $config.DNSServerSearchOrder -join ", "
DHCPEnabled = $config.DHCPEnabled
Speed = $adapter.Speed
BytesReceivedPerSec = $perfData.BytesReceivedPerSec
BytesSentPerSec = $perfData.BytesSentPerSec
PacketsReceivedPerSec = $perfData.PacketsReceivedPerSec
PacketsSentPerSec = $perfData.PacketsSentPerSec
PacketsReceivedErrors = $perfData.PacketsReceivedErrors
PacketsOutboundErrors = $perfData.PacketsOutboundErrors
OutputQueueLength = $perfData.OutputQueueLength
Timestamp = Get-Date
}
$results += $result
}
}
return $results
}
[object[]] GetTCPConnections() {
$query = "SELECT * FROM Win32_PerfRawData_Tcpip_TCPv4"
$tcpStats = Get-WmiObject -Query $query -ComputerName $this.ComputerName
return [PSCustomObject]@{
ConnectionsActive = $tcpStats.ConnectionsActive
ConnectionsEstablished = $tcpStats.ConnectionsEstablished
ConnectionFailures = $tcpStats.ConnectionFailures
ConnectionsPassive = $tcpStats.ConnectionsPassive
ConnectionsReset = $tcpStats.ConnectionsReset
SegmentsPerSec = $tcpStats.SegmentsPerSec
SegmentsReceivedPerSec = $tcpStats.SegmentsReceivedPerSec
SegmentsSentPerSec = $tcpStats.SegmentsSentPerSec
SegmentsRetransmittedPerSec = $tcpStats.SegmentsRetransmittedPerSec
Timestamp = Get-Date
}
}
[object] GetNetworkUtilization() {
$adapters = $this.GetNetworkAdapterStatistics()
$utilization = @{}
foreach ($adapter in $adapters) {
if ($adapter.Speed -gt 0) {
$totalBytes = $adapter.BytesReceivedPerSec + $adapter.BytesSentPerSec
$utilizationPercent = ($totalBytes * 8 / $adapter.Speed) * 100
$utilization[$adapter.AdapterName] = [PSCustomObject]@{
AdapterName = $adapter.AdapterName
Speed = $adapter.Speed
BytesPerSec = $totalBytes
UtilizationPercent = [Math]::Round($utilizationPercent, 2)
Direction = if ($adapter.BytesSentPerSec -gt $adapter.BytesReceivedPerSec) { "Outbound" } else { "Inbound" }
}
}
}
return $utilization
}
}
# Usage example
$monitor = [WMINetworkMonitor]::new("localhost")
# Get network statistics
$stats = $monitor.GetNetworkAdapterStatistics()
$stats | Format-Table -AutoSize
# Get TCP connection information
$tcpInfo = $monitor.GetTCPConnections()
$tcpInfo | Format-List
# Get network utilization
$utilization = $monitor.GetNetworkUtilization()
$utilization.Values | Format-Table -AutoSize
Event-Based Network Monitoring
Network Event Monitor
# NetworkEventMonitor.ps1
function Start-NetworkEventMonitoring {
param(
[string]$LogPath = "C:\Monitoring\NetworkEvents",
[string[]]$EventSources = @(
"Microsoft-Windows-TCPIP",
"Microsoft-Windows-DNS-Client",
"Microsoft-Windows-NetworkProfile",
"Microsoft-Windows-NCSI",
"Microsoft-Windows-Dhcp-Client"
)
)
# Create log directory
if (!(Test-Path $LogPath)) {
New-Item -Path $LogPath -ItemType Directory -Force
}
# Define event monitoring queries
$queries = @{
"NetworkConnectivity" = @{
LogName = "Microsoft-Windows-NetworkProfile/Operational"
ID = @(10000, 10001) # Network connected/disconnected
}
"DHCPEvents" = @{
LogName = "Microsoft-Windows-Dhcp-Client/Operational"
ID = @(50001, 50002, 50061, 50062) # DHCP lease events
}
"DNSEvents" = @{
LogName = "Microsoft-Windows-DNS-Client/Operational"
ID = @(3006, 3008, 3019, 3020) # DNS query events
}
"TCPIPErrors" = @{
LogName = "System"
Source = "Tcpip"
Level = @(2, 3) # Warning and Error
}
}
# Register event handlers
foreach ($queryName in $queries.Keys) {
$query = $queries[$queryName]
$filterXml = @"
<QueryList>
<Query Id="0" Path="$($query.LogName)">
<Select Path="$($query.LogName)">
"@
if ($query.ID) {
$idFilter = ($query.ID | ForEach-Object { "EventID=$_" }) -join " or "
$filterXml += "*[System[($idFilter)]]"
} elseif ($query.Source -and $query.Level) {
$levelFilter = ($query.Level | ForEach-Object { "Level=$_" }) -join " or "
$filterXml += "*[System[Provider[@Name='$($query.Source)'] and ($levelFilter)]]"
}
$filterXml += @"
</Select>
</Query>
</QueryList>
"@
$action = {
$event = $Event.SourceEventArgs.NewEvent
$logEntry = [PSCustomObject]@{
TimeCreated = $event.TimeCreated
EventID = $event.Id
Level = $event.LevelDisplayName
Source = $event.ProviderName
Message = $event.Message
Computer = $event.MachineName
}
# Log to file
$logFile = Join-Path $using:LogPath "NetworkEvents_$(Get-Date -Format 'yyyyMMdd').json"
$logEntry | ConvertTo-Json -Compress | Add-Content -Path $logFile
# Display critical events
if ($event.Level -le 3) {
Write-Warning "Network Event: $($event.Message)"
}
}
try {
Register-WmiEvent -Query "SELECT * FROM __InstanceCreationEvent WITHIN 5 WHERE TargetInstance ISA 'Win32_NTLogEvent'" `
-Action $action `
-SourceIdentifier "NetworkEvent_$queryName"
Write-Host "Registered event monitor for $queryName" -ForegroundColor Green
} catch {
Write-Error "Failed to register event monitor for $queryName : $_"
}
}
Write-Host "Network event monitoring started. Press Ctrl+C to stop." -ForegroundColor Yellow
try {
while ($true) {
Start-Sleep -Seconds 60
}
} finally {
# Cleanup
Get-EventSubscriber | Where-Object { $_.SourceIdentifier -like "NetworkEvent_*" } | Unregister-Event
}
}
# Start monitoring
Start-NetworkEventMonitoring
Integration with Monitoring Platforms
Prometheus Windows Exporter Configuration
# prometheus-windows-exporter.yml
collectors:
enabled:
- cpu
- cs
- logical_disk
- memory
- net
- os
- process
- tcp
# Network collector configuration
net:
# Network interface whitelist
nic-whitelist: ".*"
# Metrics to collect
metrics:
- bytes_received_total
- bytes_sent_total
- bytes_total
- packets_received_total
- packets_sent_total
- packets_total
- receive_errors_total
- transmit_errors_total
- receive_dropped_total
- transmit_dropped_total
- receive_fifo_total
- transmit_fifo_total
- receive_compressed_total
- transmit_compressed_total
- receive_multicast_total
# TCP collector configuration
tcp:
# Connection states to monitor
states:
- established
- syn_sent
- syn_recv
- fin_wait1
- fin_wait2
- time_wait
- close
- close_wait
- last_ack
- listen
- closing
Zabbix Agent Configuration
# zabbix_agentd.conf - Network monitoring additions
# Network interface discovery
UserParameter=net.if.discovery,powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "& { Get-NetAdapter | Where-Object {$_.Status -eq 'Up'} | Select-Object @{Name='{\"{#IFNAME}\"';Expression={$_.Name}}, @{Name='{\"{#IFDESCR}\"';Expression={$_.Description}} | ConvertTo-Json }"
# Custom network metrics
UserParameter=net.if.utilization[*],powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "& { $adapter = Get-NetAdapter -Name '$1'; $stats = Get-NetAdapterStatistics -Name '$1'; $perf = Get-Counter -Counter \"\Network Interface($($adapter.Description))\Bytes Total/sec\" -SampleInterval 1 -MaxSamples 1; $bandwidth = $adapter.LinkSpeed; if ($bandwidth -gt 0) { ($perf.CounterSamples[0].CookedValue * 8 / $bandwidth) * 100 } else { 0 } }"
UserParameter=net.tcp.connections[*],powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "& { (Get-NetTCPConnection -State $1 -ErrorAction SilentlyContinue).Count }"
UserParameter=net.tcp.service.connections[*],powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "& { $connections = Get-NetTCPConnection -State Established; $proc = Get-Process -Name '$1' -ErrorAction SilentlyContinue; if ($proc) { ($connections | Where-Object { $_.OwningProcess -in $proc.Id }).Count } else { 0 } }"
UserParameter=net.dns.query.time[*],powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "& { $sw = [System.Diagnostics.Stopwatch]::StartNew(); Resolve-DnsName -Name '$1' -Server '$2' -ErrorAction SilentlyContinue | Out-Null; $sw.Stop(); $sw.ElapsedMilliseconds }"
UserParameter=net.latency[*],powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "& { $result = Test-Connection -ComputerName '$1' -Count 4 -ErrorAction SilentlyContinue; if ($result) { ($result | Measure-Object -Property ResponseTime -Average).Average } else { -1 } }"
Real-time Network Dashboard
PowerShell Network Dashboard
# NetworkDashboard.ps1
# Real-time network monitoring dashboard
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
# Create form
$form = New-Object System.Windows.Forms.Form
$form.Text = "Network Performance Dashboard"
$form.Size = New-Object System.Drawing.Size(1200, 800)
$form.StartPosition = "CenterScreen"
$form.BackColor = [System.Drawing.Color]::FromArgb(30, 30, 30)
# Create timer for updates
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 1000 # Update every second
# Create charts
$chartBandwidth = New-Object System.Windows.Forms.DataVisualization.Charting.Chart
$chartBandwidth.Size = New-Object System.Drawing.Size(580, 250)
$chartBandwidth.Location = New-Object System.Drawing.Point(10, 10)
$chartBandwidth.BackColor = [System.Drawing.Color]::FromArgb(45, 45, 45)
$chartArea1 = New-Object System.Windows.Forms.DataVisualization.Charting.ChartArea
$chartArea1.BackColor = [System.Drawing.Color]::FromArgb(45, 45, 45)
$chartArea1.AxisX.LabelStyle.ForeColor = [System.Drawing.Color]::White
$chartArea1.AxisY.LabelStyle.ForeColor = [System.Drawing.Color]::White
$chartBandwidth.ChartAreas.Add($chartArea1)
$seriesRx = New-Object System.Windows.Forms.DataVisualization.Charting.Series
$seriesRx.Name = "Received"
$seriesRx.ChartType = "Line"
$seriesRx.Color = [System.Drawing.Color]::LimeGreen
$seriesRx.BorderWidth = 2
$chartBandwidth.Series.Add($seriesRx)
$seriesTx = New-Object System.Windows.Forms.DataVisualization.Charting.Series
$seriesTx.Name = "Sent"
$seriesTx.ChartType = "Line"
$seriesTx.Color = [System.Drawing.Color]::Orange
$seriesTx.BorderWidth = 2
$chartBandwidth.Series.Add($seriesTx)
$form.Controls.Add($chartBandwidth)
# Create status panel
$statusPanel = New-Object System.Windows.Forms.Panel
$statusPanel.Size = New-Object System.Drawing.Size(580, 250)
$statusPanel.Location = New-Object System.Drawing.Point(600, 10)
$statusPanel.BackColor = [System.Drawing.Color]::FromArgb(45, 45, 45)
$form.Controls.Add($statusPanel)
# Status labels
$lblStatus = New-Object System.Windows.Forms.Label
$lblStatus.Size = New-Object System.Drawing.Size(560, 230)
$lblStatus.Location = New-Object System.Drawing.Point(10, 10)
$lblStatus.ForeColor = [System.Drawing.Color]::White
$lblStatus.Font = New-Object System.Drawing.Font("Consolas", 10)
$statusPanel.Controls.Add($lblStatus)
# Data grid for connections
$gridConnections = New-Object System.Windows.Forms.DataGridView
$gridConnections.Size = New-Object System.Drawing.Size(1180, 300)
$gridConnections.Location = New-Object System.Drawing.Point(10, 270)
$gridConnections.BackgroundColor = [System.Drawing.Color]::FromArgb(45, 45, 45)
$gridConnections.ForeColor = [System.Drawing.Color]::White
$gridConnections.GridColor = [System.Drawing.Color]::FromArgb(60, 60, 60)
$gridConnections.EnableHeadersVisualStyles = $false
$gridConnections.ColumnHeadersDefaultCellStyle.BackColor = [System.Drawing.Color]::FromArgb(60, 60, 60)
$gridConnections.ColumnHeadersDefaultCellStyle.ForeColor = [System.Drawing.Color]::White
$gridConnections.DefaultCellStyle.BackColor = [System.Drawing.Color]::FromArgb(45, 45, 45)
$gridConnections.DefaultCellStyle.ForeColor = [System.Drawing.Color]::White
$form.Controls.Add($gridConnections)
# Initialize data
$script:dataPoints = 60
$script:rxData = New-Object System.Collections.ArrayList
$script:txData = New-Object System.Collections.ArrayList
$script:timeLabels = New-Object System.Collections.ArrayList
# Initialize with zeros
for ($i = 0; $i -lt $dataPoints; $i++) {
[void]$rxData.Add(0)
[void]$txData.Add(0)
[void]$timeLabels.Add("")
}
# Update function
$updateDashboard = {
try {
# Get primary network adapter
$adapter = Get-NetAdapter | Where-Object { $_.Status -eq "Up" -and $_.PhysicalMediaType -ne "Unspecified" } | Select-Object -First 1
if ($adapter) {
# Get current statistics
$stats = Get-NetAdapterStatistics -Name $adapter.Name
# Get performance counters
$rxCounter = Get-Counter -Counter "\Network Interface($($adapter.Description))\Bytes Received/sec" -ErrorAction SilentlyContinue
$txCounter = Get-Counter -Counter "\Network Interface($($adapter.Description))\Bytes Sent/sec" -ErrorAction SilentlyContinue
$rxBytesPerSec = if ($rxCounter) { $rxCounter.CounterSamples[0].CookedValue } else { 0 }
$txBytesPerSec = if ($txCounter) { $txCounter.CounterSamples[0].CookedValue } else { 0 }
# Update data arrays
$rxData.RemoveAt(0)
$txData.RemoveAt(0)
$timeLabels.RemoveAt(0)
[void]$rxData.Add($rxBytesPerSec / 1MB) # Convert to MB/s
[void]$txData.Add($txBytesPerSec / 1MB)
[void]$timeLabels.Add((Get-Date).ToString("HH:mm:ss"))
# Update chart
$chartBandwidth.Series["Received"].Points.Clear()
$chartBandwidth.Series["Sent"].Points.Clear()
for ($i = 0; $i -lt $dataPoints; $i++) {
[void]$chartBandwidth.Series["Received"].Points.AddXY($i, $rxData[$i])
[void]$chartBandwidth.Series["Sent"].Points.AddXY($i, $txData[$i])
}
# Update status
$utilization = if ($adapter.LinkSpeed -gt 0) {
(($rxBytesPerSec + $txBytesPerSec) * 8 / $adapter.LinkSpeed) * 100
} else { 0 }
$statusText = @"
Network Adapter: $($adapter.Name)
Status: $($adapter.Status)
Link Speed: $($adapter.LinkSpeed / 1GB) Gbps
MAC Address: $($adapter.MacAddress)
Current Performance:
Download: $([Math]::Round($rxBytesPerSec / 1MB, 2)) MB/s
Upload: $([Math]::Round($txBytesPerSec / 1MB, 2)) MB/s
Total: $([Math]::Round(($rxBytesPerSec + $txBytesPerSec) / 1MB, 2)) MB/s
Utilization: $([Math]::Round($utilization, 2))%
Total Statistics:
Bytes Received: $([Math]::Round($stats.ReceivedBytes / 1GB, 2)) GB
Bytes Sent: $([Math]::Round($stats.SentBytes / 1GB, 2)) GB
Packets Received: $($stats.ReceivedUnicastPackets)
Packets Sent: $($stats.SentUnicastPackets)
Errors: $($stats.ReceivedPacketErrors + $stats.OutboundPacketErrors)
"@
$lblStatus.Text = $statusText
# Update connections grid
$connections = Get-NetTCPConnection | Where-Object { $_.State -eq "Established" } |
Select-Object @{Name='Process';Expression={
try { (Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName } catch { "Unknown" }
}}, LocalAddress, LocalPort, RemoteAddress, RemotePort, State |
Group-Object Process |
Select-Object @{Name='Process';Expression={$_.Name}},
@{Name='Connections';Expression={$_.Count}},
@{Name='Details';Expression={
($_.Group | ForEach-Object { "$($_.RemoteAddress):$($_.RemotePort)" } |
Select-Object -Unique) -join ", " |
ForEach-Object { if ($_.Length -gt 100) { $_.Substring(0, 97) + "..." } else { $_ } }
}}
$gridConnections.DataSource = $null
$gridConnections.DataSource = @($connections)
$gridConnections.Columns[0].Width = 150
$gridConnections.Columns[1].Width = 100
$gridConnections.Columns[2].Width = 900
}
} catch {
Write-Host "Error updating dashboard: $_" -ForegroundColor Red
}
}
# Assign update function to timer
$timer.Add_Tick($updateDashboard)
$timer.Start()
# Form closing event
$form.Add_FormClosing({
$timer.Stop()
$timer.Dispose()
})
# Show form
[void]$form.ShowDialog()
Automated Reporting
Network Performance Report Generator
# Generate-NetworkReport.ps1
function New-NetworkPerformanceReport {
param(
[string]$ReportPath = "C:\Reports\Network",
[int]$DaysToAnalyze = 7,
[string]$SMTPServer = "smtp.company.com",
[string[]]$Recipients = @("netops@company.com")
)
# Create report directory
if (!(Test-Path $ReportPath)) {
New-Item -Path $ReportPath -ItemType Directory -Force
}
$reportDate = Get-Date
$reportFile = Join-Path $ReportPath "NetworkReport_$(Get-Date -Format 'yyyyMMdd').html"
# Collect data
Write-Host "Collecting network performance data..." -ForegroundColor Yellow
# Get adapters
$adapters = Get-NetAdapter | Where-Object { $_.Status -eq "Up" }
# Build HTML report
$html = @"
<!DOCTYPE html>
<html>
<head>
<title>Network Performance Report - $($reportDate.ToString('yyyy-MM-dd'))</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; background-color: #f5f5f5; }
h1, h2 { color: #333; }
table { border-collapse: collapse; width: 100%; margin-bottom: 20px; background-color: white; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #4CAF50; color: white; }
tr:nth-child(even) { background-color: #f2f2f2; }
.metric { display: inline-block; margin: 10px; padding: 15px; background-color: white; border-radius: 5px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.metric-value { font-size: 24px; font-weight: bold; color: #4CAF50; }
.warning { color: #ff9800; }
.error { color: #f44336; }
.chart { margin: 20px 0; }
</style>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<h1>Network Performance Report</h1>
<p>Generated: $($reportDate.ToString('yyyy-MM-dd HH:mm:ss'))</p>
<p>System: $env:COMPUTERNAME</p>
<h2>Executive Summary</h2>
<div class="metrics">
"@
# Calculate summary metrics
$totalBandwidth = 0
$totalUtilization = 0
$totalErrors = 0
foreach ($adapter in $adapters) {
$stats = Get-NetAdapterStatistics -Name $adapter.Name
$totalBandwidth += $adapter.LinkSpeed
$totalErrors += $stats.ReceivedPacketErrors + $stats.OutboundPacketErrors
# Get current utilization
$rxCounter = Get-Counter -Counter "\Network Interface($($adapter.Description))\Bytes Received/sec" -ErrorAction SilentlyContinue
$txCounter = Get-Counter -Counter "\Network Interface($($adapter.Description))\Bytes Sent/sec" -ErrorAction SilentlyContinue
if ($rxCounter -and $txCounter -and $adapter.LinkSpeed -gt 0) {
$utilization = (($rxCounter.CounterSamples[0].CookedValue + $txCounter.CounterSamples[0].CookedValue) * 8 / $adapter.LinkSpeed) * 100
$totalUtilization += $utilization
}
}
$avgUtilization = if ($adapters.Count -gt 0) { $totalUtilization / $adapters.Count } else { 0 }
$html += @"
<div class="metric">
<div>Total Bandwidth</div>
<div class="metric-value">$([Math]::Round($totalBandwidth / 1GB, 2)) Gbps</div>
</div>
<div class="metric">
<div>Average Utilization</div>
<div class="metric-value $( if ($avgUtilization -gt 80) { 'error' } elseif ($avgUtilization -gt 60) { 'warning' } )">$([Math]::Round($avgUtilization, 2))%</div>
</div>
<div class="metric">
<div>Total Errors (7 days)</div>
<div class="metric-value $( if ($totalErrors -gt 1000) { 'error' } elseif ($totalErrors -gt 100) { 'warning' } )">$totalErrors</div>
</div>
</div>
<h2>Network Adapters</h2>
<table>
<tr>
<th>Adapter</th>
<th>Status</th>
<th>Speed</th>
<th>IP Address</th>
<th>Bytes Sent</th>
<th>Bytes Received</th>
<th>Errors</th>
</tr>
"@
foreach ($adapter in $adapters) {
$stats = Get-NetAdapterStatistics -Name $adapter.Name
$ipConfig = Get-NetIPConfiguration -InterfaceAlias $adapter.Name
$html += @"
<tr>
<td>$($adapter.Name)</td>
<td>$($adapter.Status)</td>
<td>$($adapter.LinkSpeed / 1GB) Gbps</td>
<td>$($ipConfig.IPv4Address.IPAddress)</td>
<td>$([Math]::Round($stats.SentBytes / 1GB, 2)) GB</td>
<td>$([Math]::Round($stats.ReceivedBytes / 1GB, 2)) GB</td>
<td class="$( if (($stats.ReceivedPacketErrors + $stats.OutboundPacketErrors) -gt 100) { 'error' } )">$($stats.ReceivedPacketErrors + $stats.OutboundPacketErrors)</td>
</tr>
"@
}
$html += @"
</table>
<h2>Performance Trends</h2>
<div class="chart">
<canvas id="performanceChart" width="400" height="200"></canvas>
</div>
<script>
// Add chart initialization here
var ctx = document.getElementById('performanceChart').getContext('2d');
var chart = new Chart(ctx, {
type: 'line',
data: {
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
datasets: [{
label: 'Network Utilization %',
data: [65, 59, 80, 81, 56, 55, 40],
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true,
max: 100
}
}
}
});
</script>
<h2>Recommendations</h2>
<ul>
"@
# Add recommendations based on findings
if ($avgUtilization -gt 80) {
$html += "<li class='error'>Critical: Network utilization is very high. Consider upgrading network capacity.</li>"
} elseif ($avgUtilization -gt 60) {
$html += "<li class='warning'>Warning: Network utilization is elevated. Monitor for potential bottlenecks.</li>"
}
if ($totalErrors -gt 1000) {
$html += "<li class='error'>Critical: High number of network errors detected. Investigate network hardware and configurations.</li>"
} elseif ($totalErrors -gt 100) {
$html += "<li class='warning'>Warning: Moderate network errors detected. Review network logs for patterns.</li>"
}
$html += @"
</ul>
</body>
</html>
"@
# Save report
$html | Out-File -FilePath $reportFile -Encoding UTF8
Write-Host "Report saved to: $reportFile" -ForegroundColor Green
# Email report
if ($SMTPServer -and $Recipients) {
Send-MailMessage -To $Recipients `
-From "networkmonitor@company.com" `
-Subject "Network Performance Report - $($reportDate.ToString('yyyy-MM-dd'))" `
-Body "Please find attached the network performance report for $env:COMPUTERNAME" `
-Attachments $reportFile `
-SmtpServer $SMTPServer
Write-Host "Report emailed to: $($Recipients -join ', ')" -ForegroundColor Green
}
}
# Generate report
New-NetworkPerformanceReport
Troubleshooting Common Issues
Network Diagnostics Script
# Network-Diagnostics.ps1
function Invoke-NetworkDiagnostics {
param(
[string]$OutputPath = "C:\Diagnostics\Network",
[switch]$IncludePacketCapture,
[int]$CaptureDurationSeconds = 60
)
Write-Host "Starting comprehensive network diagnostics..." -ForegroundColor Yellow
# Create output directory
if (!(Test-Path $OutputPath)) {
New-Item -Path $OutputPath -ItemType Directory -Force
}
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$diagPath = Join-Path $OutputPath "NetDiag_$timestamp"
New-Item -Path $diagPath -ItemType Directory -Force
# 1. Basic connectivity tests
Write-Host "Testing basic connectivity..." -ForegroundColor Cyan
$connectivityTests = @{
"Gateway" = (Get-NetRoute -DestinationPrefix "0.0.0.0/0" | Select-Object -First 1).NextHop
"DNS" = (Get-DnsClientServerAddress -AddressFamily IPv4 | Select-Object -First 1).ServerAddresses[0]
"Internet" = "8.8.8.8"
"Microsoft" = "microsoft.com"
}
$connectivityResults = @()
foreach ($test in $connectivityTests.GetEnumerator()) {
$result = Test-NetConnection -ComputerName $test.Value -InformationLevel Detailed
$connectivityResults += [PSCustomObject]@{
Target = $test.Key
Address = $test.Value
PingSucceeded = $result.PingSucceeded
Latency = $result.PingReplyDetails.RoundtripTime
NameResolution = $result.NameResolutionSucceeded
Details = $result
}
}
$connectivityResults | Export-Csv -Path "$diagPath\connectivity_test.csv" -NoTypeInformation
# 2. Network configuration
Write-Host "Collecting network configuration..." -ForegroundColor Cyan
Get-NetIPConfiguration -Detailed | Out-File "$diagPath\ip_configuration.txt"
Get-NetAdapter -IncludeHidden | Format-List * | Out-File "$diagPath\network_adapters.txt"
Get-NetRoute | Export-Csv -Path "$diagPath\routing_table.csv" -NoTypeInformation
Get-DnsClientCache | Export-Csv -Path "$diagPath\dns_cache.csv" -NoTypeInformation
# 3. Performance metrics
Write-Host "Collecting performance metrics..." -ForegroundColor Cyan
$perfCounters = @(
"\Network Interface(*)\Bytes Total/sec",
"\Network Interface(*)\Packets/sec",
"\Network Interface(*)\Packets Received Errors",
"\Network Interface(*)\Packets Outbound Errors",
"\TCPv4\Segments Retransmitted/sec",
"\TCPv4\Connection Failures"
)
$perfData = Get-Counter -Counter $perfCounters -SampleInterval 1 -MaxSamples 10
$perfData | Export-Counter -Path "$diagPath\performance_counters.blg" -Force
# 4. TCP/IP statistics
Write-Host "Collecting TCP/IP statistics..." -ForegroundColor Cyan
netstat -s | Out-File "$diagPath\netstat_statistics.txt"
netstat -ano | Out-File "$diagPath\netstat_connections.txt"
Get-NetTCPConnection | Export-Csv -Path "$diagPath\tcp_connections.csv" -NoTypeInformation
Get-NetUDPEndpoint | Export-Csv -Path "$diagPath\udp_endpoints.csv" -NoTypeInformation
# 5. Firewall rules
Write-Host "Collecting firewall rules..." -ForegroundColor Cyan
Get-NetFirewallRule | Where-Object { $_.Enabled -eq $true } |
Select-Object DisplayName, Direction, Action, Protocol, LocalPort, RemotePort |
Export-Csv -Path "$diagPath\firewall_rules.csv" -NoTypeInformation
# 6. Event logs
Write-Host "Collecting event logs..." -ForegroundColor Cyan
$logNames = @(
"System",
"Microsoft-Windows-TCPIP/Operational",
"Microsoft-Windows-DNS-Client/Operational",
"Microsoft-Windows-NetworkProfile/Operational"
)
foreach ($logName in $logNames) {
try {
$events = Get-WinEvent -LogName $logName -MaxEvents 1000 |
Where-Object { $_.TimeCreated -gt (Get-Date).AddDays(-1) }
$events | Select-Object TimeCreated, Id, LevelDisplayName, Message |
Export-Csv -Path "$diagPath\events_$($logName.Replace('/', '_')).csv" -NoTypeInformation
} catch {
Write-Warning "Could not retrieve events from $logName"
}
}
# 7. Packet capture (optional)
if ($IncludePacketCapture) {
Write-Host "Starting packet capture for $CaptureDurationSeconds seconds..." -ForegroundColor Cyan
$capturePath = "$diagPath\packet_capture.etl"
$captureCmd = "netsh trace start capture=yes tracefile=$capturePath provider=Microsoft-Windows-TCPIP level=5 maxsize=100 overwrite=yes"
Invoke-Expression $captureCmd
Start-Sleep -Seconds $CaptureDurationSeconds
netsh trace stop
}
# 8. Generate summary report
Write-Host "Generating summary report..." -ForegroundColor Cyan
$summary = @"
Network Diagnostics Summary
Generated: $(Get-Date)
Computer: $env:COMPUTERNAME
CONNECTIVITY TEST RESULTS:
$($connectivityResults | Format-Table -AutoSize | Out-String)
ACTIVE NETWORK ADAPTERS:
$(Get-NetAdapter | Where-Object { $_.Status -eq "Up" } | Format-Table Name, Status, LinkSpeed, MacAddress -AutoSize | Out-String)
TCP CONNECTION SUMMARY:
$(Get-NetTCPConnection | Group-Object State | Select-Object Name, Count | Format-Table -AutoSize | Out-String)
TOP PROCESSES BY NETWORK CONNECTIONS:
$(Get-NetTCPConnection | Where-Object { $_.State -eq "Established" } |
Group-Object OwningProcess |
ForEach-Object {
$proc = Get-Process -Id $_.Name -ErrorAction SilentlyContinue
[PSCustomObject]@{
Process = if ($proc) { $proc.ProcessName } else { "PID: $($_.Name)" }
Connections = $_.Count
}
} |
Sort-Object Connections -Descending |
Select-Object -First 10 |
Format-Table -AutoSize | Out-String)
RECENT NETWORK ERRORS:
$(Get-WinEvent -LogName System -MaxEvents 100 |
Where-Object { $_.ProviderName -match "Tcpip|DNS|DHCP" -and $_.LevelDisplayName -match "Warning|Error" } |
Select-Object -First 10 TimeCreated, ProviderName, Message |
Format-Table -AutoSize | Out-String)
"@
$summary | Out-File "$diagPath\summary.txt"
# Compress results
$zipPath = "$OutputPath\NetworkDiagnostics_$timestamp.zip"
Compress-Archive -Path $diagPath -DestinationPath $zipPath -Force
Write-Host "Diagnostics complete. Results saved to: $zipPath" -ForegroundColor Green
# Cleanup
Remove-Item -Path $diagPath -Recurse -Force
}
# Run diagnostics
Invoke-NetworkDiagnostics
Best Practices Summary
graph TD
subgraph "Network Monitoring Best Practices"
subgraph "Collection"
A[Multiple Data Sources]
B[Regular Intervals]
C[Historical Data]
D[Real-time Alerts]
end
subgraph "Analysis"
E[Baseline Establishment]
F[Trend Analysis]
G[Anomaly Detection]
H[Correlation]
end
subgraph "Response"
I[Automated Alerts]
J[Diagnostic Scripts]
K[Remediation Actions]
L[Documentation]
end
subgraph "Integration"
M[SIEM Integration]
N[Ticketing System]
O[Dashboard Display]
P[Reporting]
end
end
A --> E
B --> F
C --> G
D --> I
E --> J
F --> K
G --> L
H --> M
I --> N
J --> O
K --> P
style A fill:#f96,stroke:#333,stroke-width:2px
style E fill:#9f9,stroke:#333,stroke-width:2px
style I fill:#99f,stroke:#333,stroke-width:2px
Conclusion
Effective network performance monitoring on Windows requires a multi-layered approach combining native tools, PowerShell automation, and integration with enterprise monitoring platforms. By implementing the scripts and configurations provided in this guide, you can build a comprehensive monitoring solution that provides visibility into network health, enables proactive problem detection, and facilitates rapid troubleshooting.
Key takeaways:
- Leverage Windows native capabilities (WMI, Performance Counters, ETW)
- Automate monitoring with PowerShell scripts
- Integrate with enterprise monitoring platforms
- Implement real-time alerting for critical issues
- Maintain historical data for trend analysis
- Use visualization tools for better insights
- Regular reporting for stakeholders