#!/usr/bin/env pwsh # ═══════════════════════════════════════════════════════════ # Mihomo Kernel Manager — Windows # clash.ps1 | clash.ps1 (交互菜单) | clash.ps1 help # ═══════════════════════════════════════════════════════════ # ─── 路径变量 ─── $ConfigDir = "$env:USERPROFILE\.config\mihomo" $ConfigFile = "$ConfigDir\config.yaml" $PidFile = "$ConfigDir\mihomo.pid" $LogFile = "$ConfigDir\mihomo.log" $ErrLogFile = "$ConfigDir\mihomo.err.log" $LinkFile = "$ConfigDir\source.txt" $SourceDir = "$env:USERPROFILE\Mihomo" $MixedPort = 7890 $ApiPort = 9090 if (-not (Test-Path $ConfigDir)) { New-Item -ItemType Directory -Path $ConfigDir -Force | Out-Null } # ─── 颜色 ─── $e = [char]27 $Bold = "$e[1m"; $Dim = "$e[2m" $Green = "$e[0;32m"; $BoldGreen = "$e[1;32m" $Red = "$e[0;31m"; $BoldRed = "$e[1;31m" $Yellow = "$e[0;33m"; $BoldYellow = "$e[1;33m" $Blue = "$e[0;34m"; $BoldBlue = "$e[1;34m" $Purple = "$e[0;35m"; $BoldPurple = "$e[1;35m" $Cyan = "$e[0;36m"; $BoldCyan = "$e[1;36m" $NC = "$e[0m" $SEP = "$Dim$('─' * 42)$NC" # ─── 输出辅助 ─── function W-Info { Write-Host " ${Cyan}ℹ${NC} $args" } function W-Ok { Write-Host " ${Green}✓${NC} $args" } function W-Warn { Write-Host " ${Yellow}⚠${NC} $args" } function W-Err { Write-Host " ${Red}✗${NC} $args" } function W-Dim { Write-Host " ${Dim}$args${NC}" } function W-Section { Write-Host ""; Write-Host " ${Bold}── $args ──${NC}" } # ─── mihomo 二进制查找 ─── function Get-MihomoBin { $cmd = Get-Command mihomo -ErrorAction SilentlyContinue if ($cmd) { return $cmd.Source } $paths = @( "$env:USERPROFILE\scoop\apps\mihomo\current\mihomo.exe", "$env:USERPROFILE\Mihomo\mihomo.exe", "$env:LOCALAPPDATA\Programs\mihomo\mihomo.exe", "$env:ProgramFiles\mihomo\mihomo.exe" ) foreach ($p in $paths) { if (Test-Path $p) { return $p } } return $null } $Script:MihomoBin = Get-MihomoBin # ─── wintun.dll 检测 ─── function Test-Wintun { if (-not $Script:MihomoBin) { return $false } $dir = Split-Path $Script:MihomoBin -Parent return (Test-Path (Join-Path $dir "wintun.dll")) } # ─── 管理员检测 ─── function Test-Admin { $current = [Security.Principal.WindowsIdentity]::GetCurrent() (New-Object Security.Principal.WindowsPrincipal($current)).IsInRole( [Security.Principal.WindowsBuiltInRole]::Administrator) } # ─── 运行状态 ─── function Test-Running { if (-not (Test-Path $PidFile)) { return $false } $procId = [System.IO.File]::ReadAllText($PidFile).Trim() if (-not $procId) { return $false } $proc = Get-Process -Id $procId -ErrorAction SilentlyContinue return ($null -ne $proc) } # ─── 源文件自动检测 ─── function Get-AutoDetectSource { if (Test-Path $LinkFile) { $linked = [System.IO.File]::ReadAllText($LinkFile).Trim() if (Test-Path $linked) { return $linked } W-Warn "链接文件不存在: $linked,尝试自动检测..." } if (Test-Path $SourceDir) { $latest = Get-ChildItem -Path $SourceDir -File | Where-Object { $_.Extension -in '.yaml', '.yml' } | Sort-Object LastWriteTime -Descending | Select-Object -First 1 if ($latest) { return $latest.FullName } } return $null } # ─── Windows 配置适配(不改源文件)─── function Invoke-WinAdaptConfig { param([string]$Src) if (-not $Src) { $Src = Get-AutoDetectSource } if (-not $Src -or -not (Test-Path $Src)) { W-Err "未找到 YAML 配置文件" W-Dim "请把 .yaml 放到 $SourceDir,或用: clash.ps1 link C:\path\to\file.yaml" return $false } Write-Host -NoNewline " 适配 Windows 配置 ... " Copy-Item $Src $ConfigFile -Force $content = [System.IO.File]::ReadAllText($ConfigFile) # Windows 兼容修复(与 macOS 版逻辑一致) $content = $content -replace '(?m)^\s*device:\s*nikki\s*\r?\n', '' $content = $content -replace '(?m)^\s*auto-redirect:\s*true\s*\r?\n', '' $content = $content -replace '(?m)^(\s*stack:)\s*system', '$1 gvisor' $content = $content -replace ' - tcp://', ' - ' $content = $content -replace ' - udp://', ' - ' [System.IO.File]::WriteAllText($ConfigFile, $content, [System.Text.UTF8Encoding]::new($false)) Write-Host "${Green}✓${NC}" W-Dim "源文件 $Src" W-Dim "适配后 $ConfigFile" return $true } # ─── 管理面板 ─── function Get-UiUrl { $uiDir = "$ConfigDir\ui" if (Test-Path $uiDir) { $dash = Get-ChildItem $uiDir -Directory | Sort-Object Name | Select-Object -First 1 if ($dash) { return "http://127.0.0.1:$ApiPort/ui/$($dash.Name)/" } } return "http://127.0.0.1:$ApiPort/ui" } function Update-UiIndex { $uiDir = "$ConfigDir\ui" if (-not (Test-Path $uiDir)) { return } $dash = Get-ChildItem $uiDir -Directory | Sort-Object Name | Select-Object -First 1 if (-not $dash) { return } $indexFile = "$uiDir\index.html" if (-not (Test-Path $indexFile)) { $html = @" Mihomo Dashboard

正在打开面板 $($dash.Name) ...

"@ [System.IO.File]::WriteAllText($indexFile, $html, [System.Text.UTF8Encoding]::new($false)) W-Info "已生成面板首页跳转: $indexFile" } } # ─── 残留网络状态检测 ─── function Test-NetworkLeftovers { # 1. 残留 mihomo 进程 if (Get-Process -Name mihomo -ErrorAction SilentlyContinue) { return $true } # 2. 残留 wintun 网卡 $adapters = Get-NetAdapter -ErrorAction SilentlyContinue | Where-Object { $_.InterfaceDescription -like "*Wintun*" -or $_.InterfaceDescription -like "*Mihomo*" } if ($adapters) { return $true } # 3. 残留路由(指向 198.18.0.1) $routes = Get-NetRoute -ErrorAction SilentlyContinue | Where-Object { $_.NextHop -eq "198.18.0.1" } if ($routes) { return $true } # 4. DNS 劫持(指向 127.0.0.1 或 198.18.x) $dns = Get-DnsClientServerAddress -ErrorAction SilentlyContinue | Where-Object { $_.ServerAddresses -contains "127.0.0.1" -or ($_.ServerAddresses | Where-Object { $_ -match "^198\.18\." }) } if ($dns) { return $true } return $false } # ─── 网络恢复(TUN/路由/DNS 全面清理)─── function Invoke-NetworkRestore { param([bool]$ShowProgress = $true) if ($ShowProgress) { W-Section "网络恢复" } # 1. 清理残留 mihomo 进程 $procs = Get-Process -Name mihomo -ErrorAction SilentlyContinue if ($procs) { if ($ShowProgress) { W-Warn "发现残留进程,正在清理..." } $procs | Stop-Process -Force -ErrorAction SilentlyContinue Remove-Item $PidFile -Force -ErrorAction SilentlyContinue } # 2. 清理残留 wintun 网卡 $adapters = Get-NetAdapter -ErrorAction SilentlyContinue | Where-Object { $_.InterfaceDescription -like "*Wintun*" -or $_.InterfaceDescription -like "*Mihomo*" } foreach ($adapter in $adapters) { if ($ShowProgress) { Write-Host -NoNewline " 清理残留 TUN $($adapter.Name) ... " } netsh interface set interface "$($adapter.Name)" admin=disable 2>$null | Out-Null if ($ShowProgress) { Write-Host "${Green}✓${NC}" } } # 3. 清理残留路由 $mihomoRoutes = Get-NetRoute -ErrorAction SilentlyContinue | Where-Object { $_.NextHop -eq "198.18.0.1" } foreach ($route in $mihomoRoutes) { Remove-NetRoute -DestinationPrefix $route.DestinationPrefix -NextHop "198.18.0.1" -Confirm:$false -ErrorAction SilentlyContinue } # 4. 刷新 DNS 缓存 if ($ShowProgress) { Write-Host -NoNewline " 刷新 DNS 缓存 ... " } ipconfig /flushdns 2>$null | Out-Null if ($ShowProgress) { Write-Host "${Green}✓${NC}" } # 5. 还原系统 DNS(如果被 mihomo 指到 127.0.0.1 / 198.18.x) $dnsHijacked = Get-DnsClientServerAddress -ErrorAction SilentlyContinue | Where-Object { $_.ServerAddresses -contains "127.0.0.1" -or ($_.ServerAddresses | Where-Object { $_ -match "^198\.18\." }) } if ($dnsHijacked) { if ($ShowProgress) { Write-Host -NoNewline " 还原系统 DNS ... " } foreach ($adapter in $dnsHijacked) { Set-DnsClientServerAddress -InterfaceIndex $adapter.InterfaceIndex -ResetServerAddresses -ErrorAction SilentlyContinue } ipconfig /flushdns 2>$null | Out-Null if ($ShowProgress) { Write-Host "${Green}✓${NC}" } } if ($ShowProgress) { Write-Host "" W-Ok "网络已恢复" } } # ─── 网络诊断 ─── function Invoke-NetworkHealthCheck { $issues = $false W-Section "网络诊断" # DNS 解析 try { $null = Resolve-DnsName baidu.com -ErrorAction Stop W-Ok "DNS 解析正常 (baidu.com)" } catch { W-Err "DNS 解析失败" $issues = $true } # 外网连通 try { $ping = New-Object System.Net.NetworkInformation.Ping $reply = $ping.Send("8.8.8.8", 2000) if ($reply.Status -eq 'Success') { W-Ok "外网连通 (8.8.8.8)" } else { W-Err "外网不可达" $issues = $true } } catch { W-Err "外网不可达" $issues = $true } # TUN 接口检查 $wintunAdapters = Get-NetAdapter -ErrorAction SilentlyContinue | Where-Object { $_.InterfaceDescription -like "*Wintun*" -or $_.InterfaceDescription -like "*Mihomo*" } if (-not $wintunAdapters) { W-Ok "无残留 TUN 接口" } else { W-Warn "发现 $($wintunAdapters.Count) 个残留 TUN 接口" $issues = $true } if ($issues) { W-Warn "网络可能有异常,试试: clash.ps1 cleanup" } else { W-Ok "网络一切正常" } } # ─── 启动 ─── function Invoke-CmdStart { param([string]$Src) $doAdapt = $true if ($Src) { $linked = if (Test-Path $LinkFile) { [System.IO.File]::ReadAllText($LinkFile).Trim() } else { "" } if ($Src -eq $ConfigFile -or $Src -eq $linked) { $doAdapt = $false } } if (-not $Src) { $Src = Get-AutoDetectSource } if ($doAdapt -and $Src -ne $ConfigFile) { $result = Invoke-WinAdaptConfig $Src if (-not $result) { return } } elseif (-not (Test-Path $ConfigFile)) { W-Err "配置文件不存在: $ConfigFile" W-Dim "试试: clash.ps1 link C:\path\to\your.yaml" return } if (Test-Running) { W-Warn "mihomo 已经在运行了" return } # 检查 mihomo 二进制 if (-not $Script:MihomoBin) { W-Err "未找到 mihomo.exe" W-Dim "请先运行 install.ps1 安装,或手动下载 mihomo.exe" return } # 检查 wintun.dll if (-not (Test-Wintun)) { W-Warn "未找到 wintun.dll(TUN 模式需要)" W-Dim "请将 wintun.dll 放在 mihomo.exe 同目录,或运行 install.ps1" return } # 启动前清理残留 if (Test-NetworkLeftovers) { Write-Host "" W-Info "检测到上次残留网络状态,先清理 ..." Invoke-NetworkRestore $false } Write-Host "" Write-Host -NoNewline " 启动中 ... " # 清理旧日志 Remove-Item $LogFile -Force -ErrorAction SilentlyContinue Remove-Item $ErrLogFile -Force -ErrorAction SilentlyContinue # 启动 mihomo(隐藏窗口,重定向输出) $argStr = "-d `"$ConfigDir`" -f `"$ConfigFile`"" $proc = Start-Process -FilePath $Script:MihomoBin ` -ArgumentList $argStr ` -WindowStyle Hidden ` -RedirectStandardOutput $LogFile ` -RedirectStandardError $ErrLogFile ` -PassThru [System.IO.File]::WriteAllText($PidFile, $proc.Id.ToString()) Start-Sleep -Seconds 1 if (Test-Running) { Write-Host "${Green}✓${NC} 已就绪" Update-UiIndex Write-Host "" Write-Host " ${Dim}代理端口 ${NC}${Cyan}127.0.0.1:$MixedPort${NC} ${Dim}(HTTP/SOCKS5)${NC}" Write-Host " ${Dim}API 端口 ${NC}${Cyan}127.0.0.1:$ApiPort${NC}" Write-Host " ${Dim}管理面板 ${NC}${Cyan}$(Get-UiUrl)${NC}" Write-Host "" } else { W-Err "启动失败" W-Dim "查看日志: Get-Content $LogFile" Remove-Item $PidFile -Force -ErrorAction SilentlyContinue } } # ─── 停止 ─── function Invoke-CmdStop { if (-not (Test-Running)) { W-Warn "mihomo 没有在运行" if (Test-NetworkLeftovers) { Write-Host "" W-Info "检测到残留网络状态,正在清理 ..." Invoke-NetworkRestore $true Write-Host "" Invoke-NetworkHealthCheck } return } $procId = [int][System.IO.File]::ReadAllText($PidFile).Trim() Write-Host "" Write-Host -NoNewline " 停止中 ... " Stop-Process -Id $procId -ErrorAction SilentlyContinue for ($i = 0; $i -lt 10; $i++) { Start-Sleep -Milliseconds 500 $proc = Get-Process -Id $procId -ErrorAction SilentlyContinue if (-not $proc) { Write-Host "${Green}✓${NC} 已停止 (PID: $procId)" Remove-Item $PidFile -Force -ErrorAction SilentlyContinue Write-Host "" Invoke-NetworkRestore $false Invoke-NetworkHealthCheck return } } Write-Host "" W-Warn "超时,强制终止..." Stop-Process -Id $procId -Force -ErrorAction SilentlyContinue Start-Sleep -Milliseconds 500 Remove-Item $PidFile -Force -ErrorAction SilentlyContinue if (Get-Process -Name mihomo -ErrorAction SilentlyContinue) { W-Err "停止失败,残留进程仍在运行" W-Dim "试试: Stop-Process -Name mihomo -Force" return } W-Warn "已强制停止" W-Info "正在恢复网络 ..." Invoke-NetworkRestore $true Write-Host "" Invoke-NetworkHealthCheck } # ─── 重启 / 热重载 ─── function Invoke-CmdRestart { param([string]$Src) Invoke-CmdStop Start-Sleep -Seconds 1 Invoke-CmdStart $Src } function Invoke-CmdReload { param([string]$Cfg = $ConfigFile) if (-not (Test-Running)) { W-Warn "mihomo 没在运行,直接启动" Invoke-CmdStart $Cfg return } if (-not (Test-Path $Cfg)) { W-Err "配置文件不存在: $Cfg" return } Write-Host -NoNewline " 热重载配置 ... " try { $body = @{ path = $Cfg } | ConvertTo-Json -Compress $uri = "http://127.0.0.1:$ApiPort/configs" $resp = Invoke-WebRequest -Method PUT -Uri $uri -ContentType "application/json" -Body $body -UseBasicParsing -ErrorAction Stop W-Ok "成功" } catch { W-Err "API 不可达,尝试重启..." Invoke-CmdRestart $Cfg } } # ─── 状态 ─── function Invoke-CmdStatus { if (Test-Running) { $procId = [System.IO.File]::ReadAllText($PidFile).Trim() Write-Host "" Write-Host " ${BoldGreen}● 运行中${NC} ${Dim}PID: $procId${NC}" Write-Host " $SEP" Write-Host " ${Dim}代理端口 ${NC}${Cyan}127.0.0.1:$MixedPort${NC} (HTTP/SOCKS5)" Write-Host " ${Dim}API 端口 ${NC}${Cyan}127.0.0.1:$ApiPort${NC}" Write-Host " ${Dim}管理面板 ${NC}${Cyan}$(Get-UiUrl)${NC}" Write-Host " ${Dim}配置文件 ${NC}$ConfigFile" Write-Host " ${Dim}日志文件 ${NC}$LogFile" $src = if (Test-Path $LinkFile) { [System.IO.File]::ReadAllText($LinkFile).Trim() } else { Get-AutoDetectSource } if ($src) { Write-Host " ${Dim}源文件 ${NC}$src" } Write-Host "" } else { Write-Host "" Write-Host " ${BoldRed}○ 已停止${NC}" Write-Host " $SEP" W-Dim "mihomo 当前未运行" Write-Host "" Remove-Item $PidFile -Force -ErrorAction SilentlyContinue } } # ─── 链接源配置 ─── function Invoke-CmdLink { param([string]$Src) if (-not $Src) { Write-Host "" W-Section "源文件链接" if (Test-Path $LinkFile) { W-Info "当前链接: $([System.IO.File]::ReadAllText($LinkFile).Trim())" } else { $detected = Get-AutoDetectSource if ($detected) { W-Info "自动检测: $detected" } else { W-Warn "未检测到源文件" } } Write-Host "" $input = Read-Host " 请输入 YAML 文件路径 (Enter 取消)" if (-not $input) { W-Dim "已取消"; return } $Src = $input.Trim().Trim('"') } if (-not (Test-Path $Src)) { W-Err "文件不存在: $Src" return } [System.IO.File]::WriteAllText($LinkFile, $Src, [System.Text.UTF8Encoding]::new($false)) W-Ok "已链接" W-Dim "源文件 $Src" Write-Host "" $ans = Read-Host " 立即适配并启动? (y/n)" if ($ans -eq 'y') { $result = Invoke-WinAdaptConfig $Src if (-not $result) { return } if (Test-Running) { Invoke-CmdRestart } else { Invoke-CmdStart } } } # ─── 日志 / 编辑 / 面板 / 修复 / 诊断 ─── function Invoke-CmdLog { if (Test-Path $LogFile) { Write-Host "" W-Section "实时日志 (Ctrl+C 退出)" Get-Content $LogFile -Wait } else { W-Warn "日志文件不存在" } } function Invoke-CmdEdit { W-Info "打开编辑器: $ConfigFile" $editor = $env:EDITOR if (-not $editor) { $editor = "notepad" } Start-Process $editor -ArgumentList "`"$ConfigFile`"" -Wait } function Invoke-CmdOpen { if (Test-Running) { Update-UiIndex $url = Get-UiUrl Start-Process $url W-Ok "已打开管理面板" W-Dim $url } else { W-Warn "mihomo 未运行,请先启动" } } function Invoke-CmdCleanup { Invoke-NetworkRestore $true } function Invoke-CmdDoctor { Invoke-NetworkHealthCheck } # ─── 关闭 Clash Verge 后台服务 ─── function Invoke-CmdVergeOff { Write-Host "" W-Section "关闭 Clash Verge" $svc = Get-Service -ErrorAction SilentlyContinue | Where-Object { $_.Name -like "*clash*verge*" -or $_.Name -like "*clash_verge*" } if ($svc) { W-Info "发现 Clash Verge 服务: $($svc.Name) ($($svc.Status))" Write-Host -NoNewline " 正在停止 ... " if ($svc.Status -eq 'Running') { Stop-Service -Name $svc.Name -Force -ErrorAction SilentlyContinue } Start-Sleep -Seconds 1 # 杀残留进程 Get-Process -Name "clash-verge*", "clash_verge*" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue W-Ok "已停止" } else { $procs = Get-Process -Name "clash-verge*", "clash_verge*" -ErrorAction SilentlyContinue if ($procs) { W-Info "发现 Clash Verge 进程" $procs | Stop-Process -Force -ErrorAction SilentlyContinue W-Ok "已停止" } else { W-Ok "Clash Verge 服务未运行" } } Write-Host "" $ans = Read-Host " 是否同时卸载开机自启? (y/n)" if ($ans -eq 'y') { if ($svc) { sc.exe delete $svc.Name 2>$null | Out-Null W-Ok "已卸载开机自启" } else { W-Dim "未找到注册服务,跳过" } } Write-Host "" W-Ok "现在可以纯用 clash.ps1 管理了" Write-Host "" } # ─── 帮助 ─── function Show-Help { Write-Host "" Write-Host " ${BoldPurple}Mihomo Kernel Manager${NC} ${Dim}— Windows${NC}" Write-Host " $SEP" Write-Host "" Write-Host " ${Bold}用法:${NC}" Write-Host " ${Cyan}clash.ps1 ${NC} 执行子命令" Write-Host " ${Cyan}clash.ps1${NC} 进入交互菜单" Write-Host " ${Cyan}clash.ps1 help${NC} 显示本帮助" Write-Host "" Write-Host " ${Bold}核心:${NC}" Write-Host " ${Cyan}start [file]${NC} 启动(自动适配 Windows 配置,TUN 模式)" Write-Host " ${Cyan}stop${NC} 停止并恢复网络" Write-Host " ${Cyan}restart${NC} 重启" Write-Host " ${Cyan}status${NC} 查看运行状态" Write-Host " ${Cyan}reload [file]${NC} 热重载配置" Write-Host " ${Cyan}open${NC} 打开管理面板" Write-Host " ${Cyan}log${NC} 实时日志" Write-Host "" Write-Host " ${Bold}配置:${NC}" Write-Host " ${Cyan}link [file]${NC} 查看/设置源 YAML" Write-Host " ${Cyan}edit${NC} 编辑当前配置" Write-Host "" Write-Host " ${Bold}网络:${NC}" Write-Host " ${Cyan}cleanup${NC} 网络修复(清理残留 TUN/路由/DNS)" Write-Host " ${Cyan}doctor${NC} 网络诊断" Write-Host "" Write-Host " ${Bold}系统:${NC}" Write-Host " ${Cyan}verge-off${NC} 关闭 Clash Verge 后台服务" Write-Host "" Write-Host " ${Bold}示例:${NC}" W-Dim "clash.ps1 start" W-Dim 'clash.ps1 link C:\Users\you\Mihomo\config.yaml' W-Dim "clash.ps1 cleanup" Write-Host "" } # ─── 交互菜单 ─── function Show-Menu { Clear-Host Write-Host "" Write-Host " ${BoldPurple}Mihomo Kernel Manager${NC} ${Dim}— Windows${NC}" Write-Host " $SEP" if (Test-Running) { $procId = [System.IO.File]::ReadAllText($PidFile).Trim() Write-Host " ${BoldGreen}● 运行中${NC} ${Dim}PID: $procId 代理: $MixedPort API: $ApiPort${NC}" } else { Write-Host " ${BoldRed}○ 已停止${NC}" } Write-Host "" Write-Host " ${Bold}1${NC} 启动 ${Bold}5${NC} 状态" Write-Host " ${Bold}2${NC} 停止 ${Bold}6${NC} 实时日志" Write-Host " ${Bold}3${NC} 打开面板 ${Bold}7${NC} 热重载" Write-Host " ${Bold}4${NC} 重启 ${Bold}8${NC} 网络修复" Write-Host " ${Bold}0${NC} 退出" Write-Host "" Write-Host -NoNewline " ${Bold}❯${NC} " } function Invoke-InteractiveMenu { while ($true) { Show-Menu $choice = Read-Host Write-Host "" switch ($choice) { '1' { Invoke-CmdStart } '2' { Invoke-CmdStop } '3' { Invoke-CmdOpen } '4' { Invoke-CmdRestart } '5' { Invoke-CmdStatus } '6' { Invoke-CmdLog } '7' { Invoke-CmdReload } '8' { Invoke-CmdCleanup } { $_ -in '0', 'q', 'Q' } { Write-Host " ${Dim}再见 👋${NC}" Write-Host "" return } default { W-Warn "无效选项,请输入 0-8" } } Write-Host "" Write-Host -NoNewline " 按 Enter 返回菜单..." Read-Host } } # ═══════════════════════════════════════════════════════════ # 入口 # ═══════════════════════════════════════════════════════════ # 检测是否为提权重启 $Script:Elevated = $false $scriptArgs = $args if ($args.Count -gt 0 -and $args[0] -eq '--elevated') { $Script:Elevated = $true $scriptArgs = if ($args.Count -gt 1) { $args[1..($args.Count-1)] } else { @() } } $cmd = if ($scriptArgs.Count -gt 0) { $scriptArgs[0] } else { "" } $cmdArgs = if ($scriptArgs.Count -gt 1) { $scriptArgs[1..($scriptArgs.Count-1)] } else { @() } # 需要管理员的命令 $adminCmds = @('start', 's', 'stop', 'restart', 'r', 'cleanup', 'c', 'verge-off', '') # 自动提权(UAC 关闭时无感) if (-not $Script:Elevated -and ($cmd -in $adminCmds) -and -not (Test-Admin)) { $elevateArgs = @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $PSCommandPath, "--elevated") if ($cmd) { $elevateArgs += $cmd } foreach ($a in $cmdArgs) { $elevateArgs += $a } Start-Process "pwsh.exe" -Verb RunAs -ArgumentList $elevateArgs exit } # 命令分发 if ($cmd -eq 'start' -or $cmd -eq 's') { Invoke-CmdStart ($cmdArgs | Select-Object -First 1) } elseif ($cmd -eq 'stop') { Invoke-CmdStop } elseif ($cmd -eq 'restart' -or $cmd -eq 'r') { Invoke-CmdRestart ($cmdArgs | Select-Object -First 1) } elseif ($cmd -eq 'reload' -or $cmd -eq 'rl') { Invoke-CmdReload ($cmdArgs | Select-Object -First 1) } elseif ($cmd -eq 'status' -or $cmd -eq 'st') { Invoke-CmdStatus } elseif ($cmd -eq 'log' -or $cmd -eq 'l') { Invoke-CmdLog } elseif ($cmd -eq 'link' -or $cmd -eq 'lk') { Invoke-CmdLink ($cmdArgs | Select-Object -First 1) } elseif ($cmd -eq 'open' -or $cmd -eq 'o') { Invoke-CmdOpen } elseif ($cmd -eq 'edit' -or $cmd -eq 'e') { Invoke-CmdEdit } elseif ($cmd -eq 'cleanup' -or $cmd -eq 'c') { Invoke-CmdCleanup } elseif ($cmd -eq 'doctor' -or $cmd -eq 'd') { Invoke-CmdDoctor } elseif ($cmd -eq 'verge-off') { Invoke-CmdVergeOff } elseif ($cmd -eq 'help' -or $cmd -eq '-h' -or $cmd -eq '--help' -or $cmd -eq 'h') { Show-Help } else { Invoke-InteractiveMenu } # 提权模式下非交互命令结束时暂停(让用户看到输出) if ($Script:Elevated -and $cmd -ne '' -and $cmd -ne 'help' -and $cmd -ne '-h' -and $cmd -ne '--help' -and $cmd -ne 'h') { Write-Host "" Write-Host -NoNewline " 按 Enter 关闭..." Read-Host }