workbuddy.xpcool.com/公司电脑网络自测.ps1

260 lines
13 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ============================================================
# 公司电脑网络与设备自测脚本
# 用途:验证公司电脑能否走通「远程串流家里的电脑」所需条件
# 用法:先在开始菜单打开 Windows PowerShell再执行
# powershell -ExecutionPolicy Bypass -File "公司电脑网络自测.ps1"
# 结果:屏幕输出 + 同目录下「网络自测结果_时间戳.txt」
# ============================================================
$ErrorActionPreference = 'Continue'
$resultFile = Join-Path $PSScriptRoot ('网络自测结果_' + (Get-Date -Format 'yyyyMMdd_HHmmss') + '.txt')
# 注意PowerShell 变量名不区分大小写,这里刻意避开 $r / $R 这类易冲突的名字
$Lines = New-Object System.Collections.ArrayList
function Add-Line {
param([string]$Text)
[void]$Lines.Add($Text)
Write-Host $Text
}
function Add-Section {
param([string]$Title)
Add-Line ''
Add-Line ('===== ' + $Title + ' =====')
}
# 用绝对路径调用系统命令:某些受管控环境的 PATH 会被裁剪,直接写 ping / netsh 会找不到
$Sys32 = Join-Path $env:SystemRoot 'System32'
$pingExe = Join-Path $Sys32 'ping.exe'
$netshExe = Join-Path $Sys32 'netsh.exe'
# 强制走 IPv6 的 TCP 连通性测试ICMPv6 常被系统/网关默认拦截,所以必须用 TCP 判断)
function Test-TcpIPv6 {
param([string]$Address, [int]$Port, [int]$TimeoutMs = 5000)
$c = $null
try {
$c = New-Object System.Net.Sockets.TcpClient ([System.Net.Sockets.AddressFamily]::InterNetworkV6)
$iar = $c.BeginConnect($Address, $Port, $null, $null)
$ok = $iar.AsyncWaitHandle.WaitOne($TimeoutMs, $false)
if ($ok -and $c.Connected) { return $true }
return $false
} catch { return $false }
finally { if ($c) { $c.Close() } }
}
# 把外部命令输出安全地拼成一行文本(命令无输出时返回空串,避免 Join 抛异常)
function Get-NativeText {
param($Output)
return (@($Output) -join ' ')
}
Add-Line ('公司电脑网络自测 ' + (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'))
# ---------- 1. 设备与显示(决定解码能力与串流分辨率) ----------
Add-Section '1. 设备与显示'
try {
$cpu = Get-CimInstance Win32_Processor | Select-Object -First 1
Add-Line ('CPU : ' + $cpu.Name.Trim())
$cs = Get-CimInstance Win32_ComputerSystem
Add-Line ('内存 : ' + [math]::Round($cs.TotalPhysicalMemory / 1GB, 1) + ' GB')
$os = Get-CimInstance Win32_OperatingSystem
Add-Line ('系统 : ' + $os.Caption + ' build ' + $os.BuildNumber)
foreach ($g in (Get-CimInstance Win32_VideoController)) {
Add-Line ('显卡 : ' + $g.Name + ' 驱动 ' + $g.DriverVersion +
' 当前 ' + $g.CurrentHorizontalResolution + 'x' + $g.CurrentVerticalResolution + '@' + $g.CurrentRefreshRate + 'Hz')
}
Add-Line '提示 : 若屏幕是 120/144Hz请到「设置-系统-屏幕-高级显示」把刷新率手动调到最高'
}
catch { Add-Line ('读取设备信息失败: ' + $_.Exception.Message) }
# ---------- 2. IPv6 能力(最关键的一项:家里只有公网 IPv6 可直连) ----------
Add-Section '2. IPv6 能力(关键)'
try {
$v6 = Get-NetIPAddress -AddressFamily IPv6 -ErrorAction SilentlyContinue |
Where-Object { $_.IPAddress -notlike 'fe80*' -and $_.IPAddress -ne '::1' -and $_.PrefixOrigin -ne 'WellKnown' }
if ($v6) {
foreach ($a in $v6) { Add-Line ('本机 IPv6 : ' + $a.IPAddress + ' (' + $a.InterfaceAlias + ')') }
} else {
Add-Line '本机 IPv6 : 无 -> 公司是纯 IPv4 网络,家里 IPv6 直连走不通,只能靠打洞或中转'
}
# 用 TCP 而不是 ping 来判断ICMPv6 默认会被 Windows 防火墙拦掉ping 不通不代表 IPv6 不可用
$v6Targets = @(
@{ Name = '阿里公共DNS v6 (2400:3200::1:53)'; Addr = '2400:3200::1'; Port = 53 },
@{ Name = '电信DNS v6 (240e:4c:4008::1:53)'; Addr = '240e:4c:4008::1'; Port = 53 }
)
foreach ($t in $v6Targets) {
if (Test-TcpIPv6 -Address $t.Addr -Port $t.Port) {
Add-Line ('IPv6 出网 : ' + $t.Name + ' -> 通(公司有可用的 IPv6')
} else {
Add-Line ('IPv6 出网 : ' + $t.Name + ' -> 不通')
}
}
Add-Line '说明 : 只要有任意一条通,说明公司网络能跑 IPv6家里的 IPv6 直连方案就有戏'
}
catch { Add-Line ('IPv6 检测失败: ' + $_.Exception.Message) }
# ---------- 3. 代理与审计软件初筛(决定会不会被拦) ----------
Add-Section '3. 代理与审计软件初筛'
try {
$wOut = & $netshExe winhttp show proxy
$w = (Get-NativeText $wOut) -replace '\s+', ' '
Add-Line ('WinHTTP 代理 : ' + $w.Trim())
$envProxy = @()
if ($env:HTTP_PROXY) { $envProxy += ('HTTP_PROXY=' + $env:HTTP_PROXY) }
if ($env:HTTPS_PROXY) { $envProxy += ('HTTPS_PROXY=' + $env:HTTPS_PROXY) }
if ($envProxy.Count -gt 0) { Add-Line ('环境变量代理 : ' + ($envProxy -join '; ')) } else { Add-Line '环境变量代理 : 无' }
$ieProxy = Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' -ErrorAction SilentlyContinue
if ($ieProxy) {
Add-Line ('系统代理 : 开关=' + $ieProxy.ProxyEnable + ' 服务器=' + $ieProxy.ProxyServer + ' PAC=' + $ieProxy.AutoConfigURL)
}
$keywords = 'Sangfor|深信服|QiAnXin|奇安信|360|TianQing|天擎|NSFOCUS|绿盟|EDR|Sentinel|CrowdStrike|Symantec|McAfee|Sophos|ESET|Kaspersky|卡巴|Huorong|火绒|IP-guard|亿赛通|天锐|网神|亚信|安恒|Forcepoint|Zscaler|Netskope|Umbrella|AnyConnect'
$hit = @()
$hit += Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.Name -match $keywords }
$hit += Get-Service -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -match $keywords }
if ($hit.Count -gt 0) {
Add-Line '命中管控/安全类软件(可能拦截远控或加密流量):'
$hit | Select-Object -First 12 | ForEach-Object { Add-Line (' - ' + $_.Name) }
} else {
Add-Line '未发现明显的安全审计类软件'
}
}
catch { Add-Line ('代理/审计检测失败: ' + $_.Exception.Message) }
# ---------- 4. 出站 TCP 端口自由度(是否只放行 80/443 ----------
Add-Section '4. 出站 TCP 端口自由度'
$tcpTargets = @(
@{ Name = 'github.com:22 (SSH)'; Host = 'github.com'; Port = 22 },
@{ Name = 'github.com:443 (HTTPS)'; Host = 'github.com'; Port = 443 },
@{ Name = '114.114.114.114:53 (TCP DNS)'; Host = '114.114.114.114'; Port = 53 },
@{ Name = '114.114.114.114:8080'; Host = '114.114.114.114'; Port = 8080 }
)
foreach ($t in $tcpTargets) {
try {
$c = Test-NetConnection -ComputerName $t.Host -Port $t.Port -InformationLevel Quiet -WarningAction SilentlyContinue
if ($c) { Add-Line ($t.Name.PadRight(28) + ' -> ') } else { Add-Line ($t.Name.PadRight(28) + ' -> 不通或被拦') }
} catch { Add-Line ($t.Name.PadRight(28) + ' -> 检测异常') }
}
Add-Line '说明 : 若只有 443 非标端口不通说明公司做了端口白名单串流要依赖打洞或中转'
# ---------- 5. UDP 出站能力(串流视频走 UDP最关键的一测 ----------
Add-Section '5. UDP 出站能力关键'
# 5.1 DNS over UDP 53最基础的 UDP 通断验证
try {
$udp = New-Object System.Net.Sockets.UdpClient
$udp.Client.ReceiveTimeout = 3000
$srv = New-Object System.Net.IPEndPoint ([System.Net.IPAddress]::Parse('114.114.114.114'), 53)
$q = [byte[]](0xAB,0xCD, 0x01,0x00, 0x00,0x01, 0x00,0x00, 0x00,0x00, 0x00,0x00,
0x02,0x71,0x71, 0x03,0x63,0x6F,0x6D, 0x00, 0x00,0x01, 0x00,0x01)
[void]$udp.Send($q, $q.Length, $srv)
$remote = New-Object System.Net.IPEndPoint ([System.Net.IPAddress]::Any, 0)
$resp = $udp.Receive([ref]$remote)
$udp.Close()
Add-Line ('UDP/53 (114.114.114.114) -> 收到 ' + $resp.Length + ' 字节')
}
catch { Add-Line ('UDP/53 -> 不通或被拦 : ' + $_.Exception.Message) }
# 5.2 STUN验证任意高位 UDP 端口能否出去,并取出 NAT 映射出的公网地址
function Invoke-StunProbe {
param([string]$Server, [int]$Port = 3478, [int]$TimeoutMs = 3000)
$res = New-Object psobject -Property @{ Server = ($Server + ':' + $Port); Ok = $false; Mapped = ''; LocalPort = 0; Err = '' }
try {
$ip = ([System.Net.Dns]::GetHostAddresses($Server) |
Where-Object { $_.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork } |
Select-Object -First 1)
if (-not $ip) { throw 'DNS 解析失败' }
$udp = New-Object System.Net.Sockets.UdpClient 0
$udp.Client.ReceiveTimeout = $TimeoutMs
$res.LocalPort = $udp.Client.LocalEndPoint.Port
$ep = New-Object System.Net.IPEndPoint ($ip, $Port)
$pkt = New-Object byte[] 20
$pkt[0] = 0x00; $pkt[1] = 0x01
$pkt[4] = 0x21; $pkt[5] = 0x12; $pkt[6] = 0xA4; $pkt[7] = 0x42
$rnd = New-Object System.Random
for ($i = 8; $i -lt 20; $i++) { $pkt[$i] = [byte]$rnd.Next(0, 256) }
[void]$udp.Send($pkt, 20, $ep)
$remote = New-Object System.Net.IPEndPoint ([System.Net.IPAddress]::Any, 0)
$resp = $udp.Receive([ref]$remote)
$udp.Close()
$pos = 20
while ($pos + 4 -le $resp.Length) {
# 注意:必须先把 byte 转成 int 再位移,否则 [byte] -shl 8 会溢出归零
$atype = ([int]$resp[$pos] -shl 8) -bor [int]$resp[$pos + 1]
$alen = ([int]$resp[$pos + 2] -shl 8) -bor [int]$resp[$pos + 3]
if ($atype -eq 0x0020 -and $alen -ge 8) {
$b = [byte[]]$resp[($pos + 8)..($pos + 11)]
$b[0] = [byte]($b[0] -bxor 0x21)
$b[1] = [byte]($b[1] -bxor 0x12)
$b[2] = [byte]($b[2] -bxor 0xA4)
$b[3] = [byte]($b[3] -bxor 0x42)
$xp = ((([int]$resp[$pos + 6] -shl 8) -bor [int]$resp[$pos + 7]) -bxor 0x2112)
$res.Mapped = (New-Object System.Net.IPAddress (, $b)).ToString() + ':' + $xp
break
}
elseif ($atype -eq 0x0001 -and $alen -ge 8) {
$b = [byte[]]$resp[($pos + 8)..($pos + 11)]
$mp = (([int]$resp[$pos + 6] -shl 8) -bor [int]$resp[$pos + 7])
$res.Mapped = (New-Object System.Net.IPAddress (, $b)).ToString() + ':' + $mp
break
}
$pos += 4 + $alen + ((4 - ($alen % 4)) % 4)
}
$res.Ok = $true
} catch { $res.Err = $_.Exception.Message }
return $res
}
$stunServers = @('stun.miwifi.com', 'stun.qq.com', 'stun.hitv.com')
$mappedPorts = @()
foreach ($s in $stunServers) {
$r = Invoke-StunProbe -Server $s
if ($r.Ok) {
Add-Line ('STUN ' + $r.Server.PadRight(24) + ' -> 本地端口 ' + $r.LocalPort + ' 映射为 ' + $r.Mapped)
if ($r.Mapped) { $mappedPorts += ($r.Mapped -split ':')[-1] }
} else {
Add-Line ('STUN ' + $r.Server.PadRight(24) + ' -> 不通 : ' + $r.Err)
}
}
if ($mappedPorts.Count -ge 2) {
$uniq = ($mappedPorts | Sort-Object -Unique).Count
if ($uniq -eq 1) {
Add-Line 'NAT 判断 : 多次映射端口一致 -> 锥形 NATP2P 打洞成功率高好消息'
} else {
Add-Line 'NAT 判断 : 每次映射端口都不同 -> 对称 NATP2P 打洞较难需要中继兜底'
}
}
Add-Line '说明 : STUN 能通 = 高位 UDP 端口可出网这是 Sunshine/Moonlight 直连的前提'
# ---------- 6. 到家里的链路(核心结论所在) ----------
Add-Section '6. 到家里的链路核心'
# 家里主机的公网 IPv6若网关重新拨号 / 前缀变化,需要重新获取后替换)
$homeV6 = '240e:338:263:3600:e591:cb62:3503:8a23'
# 家里的运营商出口 IPv4100.64 开头 = 运营商级 NAT无法直连这是正常现象
$homeV4 = ''
if ($homeV6 -ne '') {
# 先 TCP 再 pingTCP 通即可确认入站放行ping 不通多半只是 ICMPv6 被拦,不必惊慌
# 端口 38443 对应家里临时监听;正式启用 Sunshine 后应改成 47989 / 47990
if (Test-TcpIPv6 -Address $homeV6 -Port 38443) {
Add-Line '家里 IPv6:38443 -> IPv6 入站放行可以走最优路径IPv6 直连'
} else {
Add-Line '家里 IPv6:38443 -> 不通可能网关 IPv6 防火墙拦了入站或家里测试监听没开'
}
$pOut = & $pingExe -6 -n 2 -w 2000 $homeV6
$p = Get-NativeText $pOut
if ($p -match 'Reply from|来自') { Add-Line 'ping6 家里 -> ' } else { Add-Line 'ping6 家里 -> 不通ICMPv6 常被默认拦截仅供参考' }
} else { Add-Line '未填写家里 IPv6跳过' }
if ($homeV4 -ne '') {
$pOut = & $pingExe -n 2 -w 2000 $homeV4
$p = Get-NativeText $pOut
if ($p -match 'Reply from|来自') { Add-Line 'ping4 家里出口 -> ' } else { Add-Line 'ping4 家里出口 -> 不通被运营商 NAT 属正常' }
} else { Add-Line '未填写家里 IPv4跳过' }
# ---------- 收尾 ----------
Add-Section '自测完成'
Add-Line '把结果文件发给 AI 助手据此决定走 IPv6 直连 / 打洞 / 中转'
Add-Line ('结果已保存: ' + $resultFile)
try { $Lines | Out-File -FilePath $resultFile -Encoding UTF8 } catch { Write-Host ('写文件失败: ' + $_.Exception.Message) }
Write-Host ''
Write-Host ('结果文件' + $resultFile)