## 为什么需要它
`system.net.apply` 是两阶段提交(未确认自动回滚),但**万一回滚本身也失败**
(本次真机事故就是:变更脚本把地址删了却没加上),机器会落到 APIPA(169.254.x.x) 而彻底失联。
那时远程已无路可走,只能本地恢复 —— 这个脚本就是为那一天准备的。
## 能力
- `-ListOnly`:先看清各网卡状态/地址/默认路由(含「已连接但无地址」的失联特征标注)
- 自动识别失联网卡(已连接但无有效 IPv4),也可 -Interface 指定
- 带 -Ip/-PrefixLength/-Gateway/-Dns 恢复静态;不带 -Ip 则切回 DHCP
- **安全顺序**(与 agent 内实现一致):先加新地址 → 清旧地址与残留默认路由 → 补默认路由
- 自检并给出下一步命令(含可直接复制的 Mac 侧 connect 命令)
## 用法
powershell -ExecutionPolicy Bypass -File rescue-network.ps1 -ListOnly
powershell -ExecutionPolicy Bypass -File rescue-network.ps1 -Interface 'Ethernet' `
-Ip 192.168.1.100 -PrefixLength 24 -Gateway 192.168.0.1
powershell -ExecutionPolicy Bypass -File rescue-network.ps1 -Interface 'Ethernet' # 切回 DHCP
150 lines
7.2 KiB
PowerShell
150 lines
7.2 KiB
PowerShell
<#
|
||
.SYNOPSIS
|
||
nodeagent 网络救援脚本 —— 被控端因网络变更失联后的本地恢复。
|
||
|
||
.DESCRIPTION
|
||
背景:`system.net.apply` 是两阶段提交(未确认自动回滚),但**如果回滚本身也失败**
|
||
(例如变更脚本把地址删掉了却没加上),机器可能落到 APIPA(169.254.x.x)而彻底失联。
|
||
这个脚本就是为那一天准备的:在**被控端本机**以管理员运行,把网络恢复到可用状态。
|
||
|
||
它做的事:
|
||
1. 列出所有网卡与当前地址(先看清现状)
|
||
2. 找出「有物理连接、但没有有效 IPv4」的网卡(典型的失联状态)
|
||
3. 把指定/自动识别的网卡设回给定静态地址,或切回 DHCP
|
||
4. 用**原生 NetTCPIP cmdlet** 按安全顺序执行:
|
||
先加地址 → 再清其它地址与残留默认路由 → 最后补默认路由
|
||
(顺序颠倒会踩 Windows 的「already exists」而再次失败 —— 真机事故根因)
|
||
|
||
.PARAMETER Interface
|
||
网卡别名(如「Ethernet」)。省略则自动选取「已连接但无有效 IPv4」的那块。
|
||
|
||
.PARAMETER Ip / PrefixLength / Gateway / Dns
|
||
要恢复的静态配置;省略 Ip 则切回 DHCP。
|
||
|
||
.PARAMETER ListOnly
|
||
只看现状,不做任何变更。
|
||
|
||
.EXAMPLE
|
||
# 先看现状
|
||
powershell -ExecutionPolicy Bypass -File rescue-network.ps1 -ListOnly
|
||
|
||
.EXAMPLE
|
||
# 恢复为原静态地址(最常用)
|
||
powershell -ExecutionPolicy Bypass -File rescue-network.ps1 `
|
||
-Interface 'Ethernet' -Ip 192.168.1.100 -PrefixLength 24 -Gateway 192.168.0.1
|
||
|
||
.EXAMPLE
|
||
# 切回 DHCP(家里路由器会重新分配地址)
|
||
powershell -ExecutionPolicy Bypass -File rescue-network.ps1 -Interface 'Ethernet'
|
||
#>
|
||
[CmdletBinding()]
|
||
param(
|
||
[string]$Interface,
|
||
[string]$Ip,
|
||
[int]$PrefixLength = 24,
|
||
[string]$Gateway,
|
||
[string[]]$Dns,
|
||
[switch]$ListOnly
|
||
)
|
||
|
||
$ErrorActionPreference = 'Continue'
|
||
|
||
function Write-Step($m) { Write-Host "[*] $m" -ForegroundColor Cyan }
|
||
function Write-Ok($m) { Write-Host "[+] $m" -ForegroundColor Green }
|
||
function Write-Warn2($m) { Write-Host "[!] $m" -ForegroundColor Yellow }
|
||
function Write-Err2($m) { Write-Host "[x] $m" -ForegroundColor Red }
|
||
|
||
Write-Host ""
|
||
Write-Host " nodeagent 网络救援" -ForegroundColor White
|
||
Write-Host " ------------------" -ForegroundColor DarkGray
|
||
|
||
# ---------- 1. 现状 ----------
|
||
Write-Step "当前网卡与地址:"
|
||
$adapters = Get-NetAdapter | Sort-Object ifIndex
|
||
foreach ($a in $adapters) {
|
||
$addrs = @(Get-NetIPAddress -InterfaceIndex $a.ifIndex -AddressFamily IPv4 -ErrorAction SilentlyContinue |
|
||
Where-Object { $_.PrefixOrigin -ne 'WellKnown' } | ForEach-Object { "$($_.IPAddress)/$($_.PrefixLength)" })
|
||
$dhcp = (Get-NetIPInterface -InterfaceIndex $a.ifIndex -AddressFamily IPv4 -ErrorAction SilentlyContinue).Dhcp
|
||
$mark = if ($a.Status -eq 'Up' -and $addrs.Count -eq 0) { ' <== 已连接但无地址(疑似失联)' } else { '' }
|
||
Write-Host (" {0,-22} {1,-8} dhcp={2,-8} {3}{4}" -f $a.Name, $a.Status, $dhcp, ($addrs -join ', '), $mark)
|
||
}
|
||
|
||
$gw = @(Get-NetRoute -AddressFamily IPv4 -DestinationPrefix '0.0.0.0/0' -ErrorAction SilentlyContinue |
|
||
ForEach-Object { "$($_.NextHop) (ifIndex $($_.ifIndex))" })
|
||
# 注意:PowerShell 5.1 的 if 不能当表达式内联在括号里 —— 必须先算好再拼(写错会直接语法错误)
|
||
$gwText = if ($gw.Count -gt 0) { $gw -join ', ' } else { '(无 —— 这就是回包发不出去的原因)' }
|
||
Write-Step ("默认路由: " + $gwText)
|
||
|
||
if ($ListOnly) { Write-Host ""; Write-Ok "仅查看模式,未做任何变更"; exit 0 }
|
||
|
||
# ---------- 2. 选目标网卡 ----------
|
||
if (-not $Interface) {
|
||
$cand = $adapters | Where-Object {
|
||
$_.Status -eq 'Up' -and
|
||
@(Get-NetIPAddress -InterfaceIndex $_.ifIndex -AddressFamily IPv4 -ErrorAction SilentlyContinue |
|
||
Where-Object { $_.PrefixOrigin -ne 'WellKnown' }).Count -eq 0
|
||
} | Select-Object -First 1
|
||
if (-not $cand) {
|
||
$cand = $adapters | Where-Object { $_.Status -eq 'Up' } | Select-Object -First 1
|
||
}
|
||
if (-not $cand) { Write-Err2 "找不到可用的已连接网卡,请用 -Interface 指定"; exit 1 }
|
||
$Interface = $cand.Name
|
||
Write-Warn2 "未指定网卡,自动选用:$Interface"
|
||
}
|
||
|
||
if (-not (Get-NetAdapter -Name $Interface -ErrorAction SilentlyContinue)) {
|
||
Write-Err2 "网卡不存在:$Interface"; exit 1
|
||
}
|
||
|
||
# ---------- 3. 执行 ----------
|
||
if (-not $Ip) {
|
||
Write-Step "切回 DHCP:$Interface"
|
||
Set-NetIPInterface -InterfaceAlias $Interface -Dhcp Enabled -ErrorAction SilentlyContinue
|
||
Set-DnsClientServerAddress -InterfaceAlias $Interface -ResetServerAddresses -ErrorAction SilentlyContinue
|
||
Write-Ok "已切回 DHCP(稍等几秒应拿到地址)"
|
||
} else {
|
||
Write-Step "恢复静态地址 $Ip/$PrefixLength (网卡 $Interface)"
|
||
Set-NetIPInterface -InterfaceAlias $Interface -Dhcp Disabled -ErrorAction SilentlyContinue
|
||
|
||
# ⚠️ 顺序不可颠倒:先加新地址(过渡期链路不断),再清旧地址/残留路由,最后补路由。
|
||
# 颠倒会触发 Windows 的「实例已存在」→ 地址加不上 → 再次失联(真机事故根因)。
|
||
New-NetIPAddress -InterfaceAlias $Interface -IPAddress $Ip -PrefixLength $PrefixLength -ErrorAction SilentlyContinue | Out-Null
|
||
|
||
Get-NetRoute -InterfaceAlias $Interface -DestinationPrefix '0.0.0.0/0' -ErrorAction SilentlyContinue |
|
||
Remove-NetRoute -Confirm:$false -ErrorAction SilentlyContinue
|
||
|
||
Get-NetIPAddress -InterfaceAlias $Interface -AddressFamily IPv4 -ErrorAction SilentlyContinue |
|
||
Where-Object { $_.IPAddress -ne $Ip -and $_.PrefixOrigin -ne 'WellKnown' } |
|
||
ForEach-Object { Remove-NetIPAddress -IPAddress $_.IPAddress -InterfaceAlias $Interface -Confirm:$false -ErrorAction SilentlyContinue }
|
||
|
||
if ($Gateway) {
|
||
New-NetRoute -InterfaceAlias $Interface -DestinationPrefix '0.0.0.0/0' -NextHop $Gateway -ErrorAction SilentlyContinue | Out-Null
|
||
}
|
||
if ($Dns -and $Dns.Count -gt 0) {
|
||
Set-DnsClientServerAddress -InterfaceAlias $Interface -ServerAddresses $Dns -ErrorAction SilentlyContinue
|
||
}
|
||
}
|
||
|
||
Start-Sleep -Seconds 3
|
||
|
||
# ---------- 4. 自检 ----------
|
||
Write-Step "自检:"
|
||
$now = @(Get-NetIPAddress -InterfaceAlias $Interface -AddressFamily IPv4 -ErrorAction SilentlyContinue |
|
||
Where-Object { $_.PrefixOrigin -ne 'WellKnown' } | ForEach-Object { "$($_.IPAddress)/$($_.PrefixLength)" })
|
||
$nowGw = @(Get-NetRoute -InterfaceAlias $Interface -AddressFamily IPv4 -DestinationPrefix '0.0.0.0/0' -ErrorAction SilentlyContinue |
|
||
ForEach-Object { $_.NextHop })
|
||
|
||
if ($now.Count -gt 0) {
|
||
Write-Ok ("地址: " + ($now -join ', '))
|
||
if ($Ip -and $nowGw.Count -eq 0) {
|
||
Write-Warn2 "没有默认路由 —— 只能同网段互访;请补 -Gateway"
|
||
} elseif ($nowGw.Count -gt 0) {
|
||
Write-Ok ("默认路由: " + ($nowGw -join ', '))
|
||
}
|
||
Write-Host ""
|
||
Write-Ok "网络已恢复。可在 Mac 上执行: nodeagent connect $($now[0].Split('/')[0]) --port 8765 --key <PSK> --insecure"
|
||
} else {
|
||
Write-Err2 "仍然没有有效地址 —— 请检查网线/交换机,或尝试: rescue-network.ps1 -Interface '$Interface'(不带 -Ip,切回 DHCP)"
|
||
exit 1
|
||
}
|