4942 words
25 minutes
Network Performance Monitoring on Windows - Enterprise Implementation Guide

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

Resources#

Network Performance Monitoring on Windows - Enterprise Implementation Guide
https://mranv.pages.dev/posts/network-performance-monitoring-windows/
Author
Anubhav Gain
Published at
2025-01-29
License
CC BY-NC-SA 4.0