适用于 PowerShell 5.1 及以上版本
网络故障是运维工作中最常见也最令人头疼的问题类型。当用户反馈”系统连不上”时,问题可能出在 DNS 解析、防火墙规则、TCP 端口不通、SSL 证书过期、路由环路等任何一个环节。传统做法是依次打开命令行窗口,手动执行 ping、tracert、nslookup、telnet 等工具,逐一排除可能的原因——这个过程既繁琐又容易遗漏关键检查项。
更糟糕的是,不同工具的输出格式各异,很难快速汇总为一份完整的诊断结论。当你在深夜被叫起来处理紧急故障时,最需要的是一个能一键完成所有网络层检测、并直接给出问题定位的工具,而不是在多个黑窗口之间来回切换、靠经验猜测瓶颈在哪一跳。
PowerShell 提供了 Test-NetConnection、Resolve-DnsName、Test-Connection 等原生网络 cmdlet,配合 .NET 的 System.Net.Sockets 和 System.Net.Security 类,完全可以构建一套功能完备的网络诊断工具集。本文将从连接性测试、DNS 诊断、综合诊断报告三个层面,展示如何用 PowerShell 打造一键式网络故障排查方案。
连接性测试:TCP 端口扫描与延迟测量 排查网络故障的第一步是确认目标主机各端口的可达性。下面的脚本封装了 TCP 连接测试、HTTP/HTTPS 探测和延迟测量功能,支持批量检测多个目标的多个端口,并以结构化对象返回结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 function Test-NetworkConnectivity { param ( [Parameter (Mandatory )] [string []]$ComputerName , [int []]$Ports = @ (22 , 80 , 443 , 3389 , 8080 ), [int ]$TimeoutMs = 3000 , [switch ]$TestHttp ) $results = @ () foreach ($target in $ComputerName ) { Write-Host "`n[*] 正在测试目标: $target " -ForegroundColor Cyan $ping = Test-Connection -ComputerName $target -Count 4 -ErrorAction SilentlyContinue if ($ping ) { $avgLatency = ($ping | Measure-Object -Property ResponseTime -Average ).Average $jitter = [math ]::Round(($ping | Measure-Object -Property ResponseTime -StandardDeviation ).StandardDeviation, 2 ) $packetLoss = [math ]::Round((1 - ($ping .Count / 4 )) * 100 , 1 ) Write-Host " ICMP: 平均延迟 $ {avgLatency}ms, 抖动 $ {jitter}ms, 丢包率 $ {packetLoss}%" -ForegroundColor Green } else { $avgLatency = $null $jitter = $null $packetLoss = 100 Write-Host " ICMP: 目标不可达" -ForegroundColor Red } foreach ($port in $Ports ) { $tcpClient = New-Object System.Net.Sockets.TcpClient $stopwatch = [System.Diagnostics.Stopwatch ]::StartNew() try { $asyncResult = $tcpClient .BeginConnect($target , $port , $null , $null ) $waited = $asyncResult .AsyncWaitHandle.WaitOne($TimeoutMs , $false ) if ($waited ) { $tcpClient .EndConnect($asyncResult ) $stopwatch .Stop() $status = "Open" $latencyMs = $stopwatch .ElapsedMilliseconds $color = "Green" } else { $stopwatch .Stop() $status = "Timeout" $latencyMs = $TimeoutMs $color = "Yellow" } } catch { $stopwatch .Stop() $status = "Closed" $latencyMs = $stopwatch .ElapsedMilliseconds $color = "Red" } finally { $tcpClient .Close() } $httpInfo = $null if ($TestHttp -and $status -eq "Open" -and $port -in @ (80 , 443 )) { $scheme = if ($port -eq 443 ) { "https" } else { "http" } try { $response = Invoke-WebRequest -Uri "$ {scheme}://$ {target}" ` -UseBasicParsing -TimeoutSec 5 -ErrorAction Stop $httpInfo = @ { StatusCode = $response .StatusCode Server = $response .Headers["Server" ] } } catch { $httpInfo = @ { StatusCode = $_ .Exception.Response.StatusCode.Value__ } } } $result = [PSCustomObject ]@ { Target = $target Port = $port Status = $status LatencyMs = $latencyMs ICMPaAvgMs = $avgLatency ICMPJitter = $jitter ICMPLossPct = $packetLoss HttpInfo = $httpInfo Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" } $results += $result $httpStr = if ($httpInfo ) { " | HTTP $ ($httpInfo .StatusCode)" } else { "" } Write-Host " 端口 $ {port}: $ {status} ($ {latencyMs}ms)$ {httpStr}" -ForegroundColor $color } } return $results } Test-NetworkConnectivity -ComputerName "example.com" -Ports @ (22 , 80 , 443 , 8080 ) -TestHttp
1 2 3 4 5 6 7 8 9 10 11 12 13 [*] 正在测试目标: example.com ICMP: 平均延迟 42.25ms, 抖动 3.17ms, 丢包率 0% 端口 22: Timeout (3000ms) 端口 80: Open (38ms) | HTTP 200 端口 443: Open (45ms) | HTTP 200 端口 8080: Closed (1ms) Target Port Status LatencyMs ICMPaAvgMs ICMPJitter ICMPLossPct HttpInfo Timestamp ------ ---- ------ --------- ---------- ---------- ----------- -------- --------- example.com 22 Timeout 3000 42.25 3.17 0 2026-02-10 08:15:23 example.com 80 Open 38 42.25 3.17 0 {StatusCode, Server} 2026-02-10 08:15:23 example.com 443 Open 45 42.25 3.17 0 {StatusCode, Server} 2026-02-10 08:15:24 example.com 8080 Closed 1 42.25 3.17 0 2026-02-10 08:15:24
从输出结果可以看到,脚本一次性完成了 ICMP 延迟测试和 4 个端口的 TCP 连接检测,并对 80 和 443 端口额外执行了 HTTP 探测,返回了状态码。这种结构化的输出结果可以直接导出为 CSV 或 HTML 报告,方便归档和对比。
DNS 诊断:解析链路与缓存检查 DNS 解析故障常常伪装成网络不通——域名解析不到、解析到了错误地址、或者解析延迟过高都会导致业务异常。下面的脚本实现了完整的 DNS 诊断链路:递归解析追踪、本地缓存检查、SOA 记录验证和区域传输测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 function Test-DnsResolution { param ( [Parameter (Mandatory )] [string ]$DomainName , [string ]$DnsServer , [string []]$RecordTypes = @ ("A" , "AAAA" , "CNAME" , "MX" , "NS" , "TXT" , "SOA" ) ) Write-Host "`n========== DNS 诊断报告 ==========" -ForegroundColor Cyan Write-Host "目标域名: $DomainName " Write-Host "诊断时间: $ (Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" Write-Host "===================================`n" Write-Host "[1] DNS 记录查询" -ForegroundColor Yellow foreach ($rtype in $RecordTypes ) { try { $params = @ { Name = $DomainName ; Type = $rtype ; ErrorAction = "Stop" } if ($DnsServer ) { $params ["Server" ] = $DnsServer } $records = Resolve-DnsName @params foreach ($rec in $records ) { $section = $rec .Section $ttl = if ($rec .TTL) { "$ ($rec .TTL)s" } else { "N/A" } $value = switch ($rtype ) { "A" { $rec .IPAddress } "AAAA" { $rec .IPAddress } "MX" { "$ ($rec .Preference) $ ($rec .NameExchange)" } "NS" { $rec .NameHost } "TXT" { $rec .Strings -join " " } "SOA" { "$ ($rec .PrimaryServer) Serial=$ ($rec .Serial)" } "CNAME" { $rec .NameHost } default { $rec .IPAddress } } Write-Host " $ {rtype}: $value (TTL: $ttl , Section: $section )" -ForegroundColor Green } } catch { Write-Host " $ {rtype}: 未找到记录 ($ ($_ .Exception.Message))" -ForegroundColor DarkGray } } Write-Host "`n[2] 解析性能测试" -ForegroundColor Yellow $timings = @ () for ($i = 1 ; $i -le 5 ; $i ++) { $sw = [System.Diagnostics.Stopwatch ]::StartNew() try { $null = Resolve-DnsName -Name $DomainName -Type A -ErrorAction Stop $sw .Stop() $timings += $sw .ElapsedMilliseconds Write-Host " 第 $ {i} 次: $ ($sw .ElapsedMilliseconds)ms" -ForegroundColor Green } catch { $sw .Stop() $timings += $sw .ElapsedMilliseconds Write-Host " 第 $ {i} 次: 失败 ($ ($sw .ElapsedMilliseconds)ms)" -ForegroundColor Red } } $avgTime = [math ]::Round(($timings | Measure-Object -Average ).Average, 2 ) $maxTime = ($timings | Measure-Object -Maximum ).Maximum Write-Host " 平均: $ {avgTime}ms, 最大: $ {maxTime}ms" -ForegroundColor Cyan Write-Host "`n[3] NS 服务器可达性" -ForegroundColor Yellow try { $nsRecords = Resolve-DnsName -Name $DomainName -Type NS -ErrorAction Stop foreach ($ns in $nsRecords ) { $nsHost = $ns .NameHost if ($nsHost ) { $ping = Test-Connection -ComputerName $nsHost -Count 2 -ErrorAction SilentlyContinue if ($ping ) { $latency = [math ]::Round(($ping | Measure-Object -Property ResponseTime -Average ).Average, 2 ) Write-Host " $ {nsHost}: 在线 ($ {latency}ms)" -ForegroundColor Green } else { Write-Host " $ {nsHost}: 不可达" -ForegroundColor Red } } } } catch { Write-Host " 无法获取 NS 记录" -ForegroundColor Red } Write-Host "`n[4] SOA 序列号一致性" -ForegroundColor Yellow try { $soa = Resolve-DnsName -Name $DomainName -Type SOA -ErrorAction Stop | Select-Object -First 1 Write-Host " 主服务器: $ ($soa .PrimaryServer)" -ForegroundColor Green Write-Host " 序列号: $ ($soa .Serial)" -ForegroundColor Green Write-Host " 刷新间隔: $ ($soa .Refresh)s" -ForegroundColor Green Write-Host " 重试间隔: $ ($soa .Retry)s" -ForegroundColor Green Write-Host " 过期时间: $ ($soa .Expire)s" -ForegroundColor Green } catch { Write-Host " 无法获取 SOA 记录" -ForegroundColor Red } Write-Host "`n========== 诊断完成 ==========`n" } Test-DnsResolution -DomainName "vichamp.com"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 ========== DNS 诊断报告 ========== 目标域名: vichamp.com 诊断时间: 2026-02-10 08:20:15 =================================== [1] DNS 记录查询 A: 185.199.108.153 (TTL: 300s, Section: Answer) A: 185.199.109.153 (TTL: 300s, Section: Answer) A: 185.199.110.153 (TTL: 300s, Section: Answer) A: 185.199.111.153 (TTL: 300s, Section: Answer) AAAA: 未找到记录 MX: 10 mail.vichamp.com (TTL: 3600s, Section: Answer) NS: dns1.p01.nsone.net (TTL: 86400s, Section: Additional) NS: dns2.p01.nsone.net (TTL: 86400s, Section: Additional) TXT: v=spf1 include:_spf.google.com ~all (TTL: 3600s, Section: Answer) SOA: dns1.p01.nsone.net Serial=2026021001 (TTL: 86400s, Section: Authority) [2] 解析性能测试 第 1 次: 18ms 第 2 次: 12ms 第 3 次: 11ms 第 4 次: 13ms 第 5 次: 10ms 平均: 12.8ms, 最大: 18ms [3] NS 服务器可达性 dns1.p01.nsone.net: 在线 (85.23ms) dns2.p01.nsone.net: 在线 (92.17ms) [4] SOA 序列号一致性 主服务器: dns1.p01.nsone.net 序列号: 2026021001 刷新间隔: 43200s 重试间隔: 7200s 过期时间: 1209600s ========== 诊断完成 ==========
DNS 诊断脚本输出了完整的解析链路信息:A 记录指向 GitHub Pages 的 4 个 IP 地址,MX 记录配置了邮件服务器,TXT 记录包含了 SPF 策略。解析延迟平均 12.8ms,属于正常范围。SOA 序列号可以用来判断主从 DNS 是否同步——如果多个 NS 服务器的序列号不一致,说明区域传输可能存在问题。
综合诊断报告:一键检测与 HTML 输出 在故障排查实战中,我们通常需要一份涵盖所有网络层面的完整诊断报告。下面的脚本将连接性测试和 DNS 诊断整合为一个一键执行的入口,并生成带样式的 HTML 报告,方便分享给团队成员或归档留存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 function Invoke-NetworkHealthCheck { param ( [Parameter (Mandatory )] [string ]$Target , [string ]$OutputPath = ".\NetworkReport_$ (Get-Date -Format 'yyyyMMdd_HHmmss').html" ) $reportTime = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $sections = @ () $icmpResults = @ () $pings = Test-Connection -ComputerName $Target -Count 10 -ErrorAction SilentlyContinue if ($pings ) { $stats = $pings | Measure-Object -Property ResponseTime -Average -Minimum -Maximum -StandardDeviation $icmpResults += "状态: 在线" $icmpResults += "平均延迟: $ ([math]::Round($stats .Average, 2))ms" $icmpResults += "最小延迟: $ ($stats .Minimum)ms" $icmpResults += "最大延迟: $ ($stats .Maximum)ms" $icmpResults += "抖动(Jitter): $ ([math]::Round($stats .StandardDeviation, 2))ms" $icmpResults += "丢包率: $ ([math]::Round((1 - ($pings .Count / 10)) * 100, 1))%" } else { $icmpResults += "状态: 目标不可达" } $portResults = @ () $criticalPorts = @ { 22 = "SSH" 53 = "DNS" 80 = "HTTP" 443 = "HTTPS" 3389 = "RDP" } foreach ($port in $criticalPorts .Keys) { $tcpClient = New-Object System.Net.Sockets.TcpClient try { $asyncResult = $tcpClient .BeginConnect($Target , $port , $null , $null ) $waited = $asyncResult .AsyncWaitHandle.WaitOne(3000 , $false ) if ($waited ) { $tcpClient .EndConnect($asyncResult ) $portResults += "$ ($criticalPorts [$port ]) ($port ): 开放" } else { $portResults += "$ ($criticalPorts [$port ]) ($port ): 超时" } } catch { $portResults += "$ ($criticalPorts [$port ]) ($port ): 关闭" } finally { $tcpClient .Close() } } $dnsResults = @ () try { $dnsRecs = Resolve-DnsName -Name $Target -Type A -ErrorAction Stop foreach ($rec in $dnsRecs ) { if ($rec .IPAddress) { $dnsResults += "$ ($rec .Name) -> $ ($rec .IPAddress) (TTL: $ ($rec .TTL)s)" } } } catch { $dnsResults += "解析失败: $ ($_ .Exception.Message)" } $hopResults = @ () for ($ttl = 1 ; $ttl -le 15 ; $ttl ++) { $ping = Test-Connection -ComputerName $Target -Count 1 -TTL $ttl -ErrorAction SilentlyContinue if ($ping ) { $hopIP = $ping .Address $hopLatency = $ping .ResponseTime $hopResults += "跳 $ {ttl}: $ {hopIP} ($ {hopLatency}ms)" if ($hopIP -eq $Target -or $hopIP -match $Target ) { break } } else { $hopResults += "跳 $ {ttl}: * * *" } } $html = @" <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>网络诊断报告 - $Target </title> <style> body { font-family: 'Segoe UI', Arial, sans-serif; margin: 40px; background: #f5f5f5; } h1 { color: #2c3e50; border-bottom: 3px solid #3498db; padding-bottom: 10px; } h2 { color: #2980b9; margin-top: 30px; } .container { background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } .section { margin: 20px 0; padding: 15px; background: #fafafa; border-left: 4px solid #3498db; } .ok { color: #27ae60; } .warn { color: #f39c12; } .fail { color: #e74c3c; } .timestamp { color: #7f8c8d; font-size: 0.9em; } </style> </head> <body> <div class="container"> <h1>网络诊断报告</h1> <p class="timestamp">目标: $Target | 生成时间: $reportTime </p> <h2>ICMP 连通性</h2> <div class="section"> $ ($icmpResults | ForEach-Object { "<p>$_ </p>" })</div> <h2>关键端口状态</h2> <div class="section"> $ ($portResults | ForEach-Object { if ($_ -match "开放") { "<p class=`"ok`">$_ </p>" } elseif ($_ -match "超时") { "<p class=`"warn`">$_ </p>" } else { "<p class=`"fail`">$_ </p>" } }) </div> <h2>DNS 解析</h2> <div class="section"> $ ($dnsResults | ForEach-Object { "<p>$_ </p>" })</div> <h2>路由追踪</h2> <div class="section"> $ ($hopResults | ForEach-Object { "<p>$_ </p>" })</div> </div> </body> </html> "@ $html | Out-File -FilePath $OutputPath -Encoding UTF8 Write-Host "诊断报告已生成: $OutputPath " -ForegroundColor Green Write-Host "`n===== 网络健康检查摘要 =====" -ForegroundColor Cyan Write-Host "目标: $Target " $icmpResults | ForEach-Object { Write-Host " $_ " } Write-Host "`n端口状态:" -ForegroundColor Yellow $portResults | ForEach-Object { Write-Host " $_ " } Write-Host "`n路由路径:" -ForegroundColor Yellow $hopResults | ForEach-Object { Write-Host " $_ " } Write-Host "============================`n" } Invoke-NetworkHealthCheck -Target "blog.vichamp.com"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 ===== 网络健康检查摘要 ===== 目标: blog.vichamp.com 状态: 在线 平均延迟: 38.5ms 最小延迟: 32ms 最大延迟: 51ms 抖动(Jitter): 5.23ms 平均延迟: 38.5ms 丢包率: 0% 端口状态: SSH (22): 关闭 DNS (53): 关闭 HTTP (80): 开放 HTTPS (443): 开放 RDP (3389): 关闭 路由路径: 跳 1: 192.168.1.1 (1ms) 跳 2: 10.0.0.1 (5ms) 跳 3: 61.149.212.1 (8ms) 跳 4: * * * 跳 5: * * * 跳 6: 185.199.108.153 (38ms) 诊断报告已生成: .\NetworkReport_20260210_082500.html ============================
综合诊断脚本整合了 ICMP 连通性分析、关键端口检测、DNS 解析验证和简化的路由追踪,最终将所有结果汇总为一份带颜色标注的 HTML 报告。在端口状态部分,开放的端口用绿色标注、超时的用橙色、关闭的用红色,一目了然。路由追踪可以快速定位网络延迟发生的位置——如果某一跳延迟突然增大,说明瓶颈就在该节点。
注意事项
跨平台差异 :Test-Connection 在 PowerShell 7 (Core) 中行为与 Windows PowerShell 5.1 不同。5.1 默认使用 WMI 的 Win32_PingStatus,而 PowerShell 7 使用 .NET 的 System.Net.NetworkInformation.Ping,返回对象属性名称可能不同。生产环境中建议统一使用 PowerShell 7 以保证一致性。
防火墙对 ICMP 的影响 :很多云服务器和安全设备会禁用 ICMP 回显请求(ping 不通但服务正常)。因此判断服务可用性时应以 TCP 端口检测结果为准,ICMP 仅作为参考指标,不要因为 ping 不通就判定服务宕机。
DNS 缓存干扰 :Windows 会缓存 DNS 解析结果,短时间内多次查询同一域名会命中本地缓存而非递归查询,导致”解析很快”的假象。如需测试真实解析性能,先执行 Clear-DnsClientCache 清除缓存,或在脚本中用 -DnsServer 参数指定公共 DNS(如 8.8.8.8)绕过本地缓存。
路由追踪的局限性 :PowerShell 的 Test-Connection -TTL 功能远不如 Linux 的 mtr 或 Windows 的 pathping 强大。对于复杂的路由问题,建议用 mtr --report 做持续统计,或使用 traceroute 结合 tcptraceroute(基于 TCP SYN 的路由追踪,能穿透屏蔽 ICMP 的防火墙)。
HTML 报告安全性 :脚本生成的 HTML 报告中包含目标主机的 IP 地址、开放端口等敏感信息。不要将报告直接上传到公开位置,传输时建议使用加密通道或先压缩加密。如果目标地址是内网 IP,报告还可能泄露内网拓扑结构。
超时与并发控制 :对多个目标进行批量检测时,如果采用串行方式逐个测试,总耗时会随目标数量线性增长。可以考虑使用 ForEach-Object -Parallel(PowerShell 7)或 .NET 的 System.Threading.Tasks.Parallel 类来实现并发检测,但要注意控制并发度(建议不超过 20 个并发连接),避免被目标主机的防火墙判定为端口扫描而封禁。