#Requires -Version 5.1 <# .SYNOPSIS XW远程(湘网远程)客户端一键配置 .DESCRIPTION 将本机 RustDesk 指向湘网自建服务器,同时写入"用户级"与"服务级"两份配置。 只写用户级会导致界面正常、但开机自启的无人值守服务仍连公共服务器。 .PARAMETER SkipRestart 写完配置后不重启 RustDesk 服务(供批量部署脚本串联使用) .EXAMPLE powershell -ExecutionPolicy Bypass -File .\Install-XWRemote.ps1 .NOTES 必须以管理员身份运行。会备份原始配置文件为 *.bak-时间戳。 #> [CmdletBinding()] param( [switch]$SkipRestart ) $ErrorActionPreference = 'Stop' # ============ 服务器参数(改动此处即可适配其它节点) ============ $ID_SERVER = 'rd.xwlemd.com' $RELAY_SERVER = 'rd.xwlemd.com' $API_SERVER = 'https://rd.xwlemd.com' $PUBLIC_KEY = 'okv1OjwoFuEeM4zD09UV2FJK85itofUwvNSEVYQujS0=' $SERVICE_NAME = 'RustDesk' $HOST_TITLE = 'XW远程' function Write-Step { param($m) Write-Host "[*] $m" -ForegroundColor Cyan } function Write-Ok { param($m) Write-Host "[+] $m" -ForegroundColor Green } function Write-Warn2 { param($m) Write-Host "[!] $m" -ForegroundColor Yellow } function Write-Err { param($m) Write-Host "[x] $m" -ForegroundColor Red } # ---------- 0. 权限检查 ---------- $isAdmin = ([Security.Principal.WindowsPrincipal] ` [Security.Principal.WindowsIdentity]::GetCurrent() ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) if (-not $isAdmin) { Write-Err '需要管理员权限。请右键"以管理员身份运行" PowerShell 后重试。' exit 1 } Write-Host '' Write-Host " $HOST_TITLE · 客户端配置" -ForegroundColor White Write-Host " 服务器 $ID_SERVER" -ForegroundColor DarkGray Write-Host '' # ---------- 1. 定位 RustDesk ---------- Write-Step '定位 RustDesk 安装路径' $candidates = @( "$env:ProgramFiles\RustDesk\rustdesk.exe", "${env:ProgramFiles(x86)}\RustDesk\rustdesk.exe", "$env:LOCALAPPDATA\Programs\RustDesk\rustdesk.exe" ) $exe = $null foreach ($p in $candidates) { if (Test-Path -LiteralPath $p) { $exe = $p; break } } if (-not $exe) { $reg = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' ` -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -match 'RustDesk' } | Select-Object -First 1 if ($reg -and $reg.InstallLocation) { $try = Join-Path $reg.InstallLocation 'rustdesk.exe' if (Test-Path -LiteralPath $try) { $exe = $try } } } if (-not $exe) { Write-Err '未找到 RustDesk。请先从 https://rustdesk.com 下载安装客户端,再运行本脚本。' exit 2 } Write-Ok "已找到: $exe" $rustdeskDir = Split-Path -Parent $exe $version = (Get-Item -LiteralPath $exe).VersionInfo.FileVersion if ($version) { Write-Ok "客户端版本: $version" } # ---------- 2. 停止服务(关键:RustDesk 退出时会回写配置) ---------- $svc = Get-Service -Name $SERVICE_NAME -ErrorAction SilentlyContinue $svcWasRunning = $false if ($svc) { if ($svc.Status -eq 'Running') { $svcWasRunning = $true Write-Step '停止 RustDesk 服务(避免退出时覆盖配置)' Stop-Service -Name $SERVICE_NAME -Force -ErrorAction SilentlyContinue Start-Sleep -Seconds 2 } } else { Write-Warn2 "未检测到 $SERVICE_NAME 服务,将只配置用户级(无人值守场景可能不生效)" } # 结束残留进程 Get-Process -Name 'rustdesk' -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue Start-Sleep -Milliseconds 800 # ---------- 3. 写入 TOML ---------- function Set-TomlOption { <# 在指定 TOML 文件中把 Key 设为 Value。 存在则替换,不存在则插入到 [options] 段内;段落缺失则追加。 保持其它配置项(如永久密码)不被破坏。 #> param( [Parameter(Mandatory)][string]$Path, [Parameter(Mandatory)][string]$Key, [Parameter(Mandatory)][string]$Value ) $dir = Split-Path -Parent $Path if (-not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } $lines = New-Object System.Collections.Generic.List[string] if (Test-Path -LiteralPath $Path) { Get-Content -LiteralPath $Path -Encoding UTF8 | ForEach-Object { $lines.Add($_) } } # 确保存在 [options] $optIdx = -1 for ($i = 0; $i -lt $lines.Count; $i++) { if ($lines[$i].Trim() -eq '[options]') { $optIdx = $i; break } } if ($optIdx -lt 0) { if ($lines.Count -gt 0 -and $lines[$lines.Count - 1].Trim() -ne '') { $lines.Add('') } $lines.Add('[options]') $optIdx = $lines.Count - 1 } # 在 [options] 段内查找该键 $found = $false $insertAt = $optIdx + 1 for ($i = $optIdx + 1; $i -lt $lines.Count; $i++) { $t = $lines[$i].Trim() if ($t.StartsWith('[')) { break } if ($t -match "^\s*$([regex]::Escape($Key))\s*=") { $lines[$i] = "$Key = '$Value'" $found = $true break } if ($t -ne '') { $insertAt = $i + 1 } } if (-not $found) { $lines.Insert($insertAt, "$Key = '$Value'") } # UTF-8 无 BOM,否则 RustDesk 解析失败 $enc = New-Object System.Text.UTF8Encoding($false) [System.IO.File]::WriteAllText($Path, ($lines -join "`r`n") + "`r`n", $enc) } function Set-TomlRoot { param([string]$Path, [string]$Key, [string]$Value) $dir = Split-Path -Parent $Path if (-not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } $lines = New-Object System.Collections.Generic.List[string] if (Test-Path -LiteralPath $Path) { Get-Content -LiteralPath $Path -Encoding UTF8 | ForEach-Object { $lines.Add($_) } } $found = $false for ($i = 0; $i -lt $lines.Count; $i++) { $t = $lines[$i].Trim() if ($t.StartsWith('[')) { break } if ($t -match "^\s*$([regex]::Escape($Key))\s*=") { $lines[$i] = "$Key = '$Value'" $found = $true break } } if (-not $found) { $lines.Insert(0, "$Key = '$Value'") } $enc = New-Object System.Text.UTF8Encoding($false) [System.IO.File]::WriteAllText($Path, ($lines -join "`r`n") + "`r`n", $enc) } $stamp = Get-Date -Format 'yyyyMMdd-HHmmss' $targets = @() # 服务级(无人值守,必须) $svcToml = "$env:SystemDrive\Windows\ServiceProfiles\LocalService\AppData\Roaming\RustDesk\config\RustDesk2.toml" $targets += [pscustomobject]@{ Name = '服务级'; Path = $svcToml } # 用户级(当前登录用户) $usrToml = Join-Path $env:APPDATA 'RustDesk\config\RustDesk2.toml' $targets += [pscustomobject]@{ Name = '用户级'; Path = $usrToml } foreach ($t in $targets) { Write-Step "写入$($t.Name)配置: $($t.Path)" if (Test-Path -LiteralPath $t.Path) { $bak = "$($t.Path).bak-$stamp" Copy-Item -LiteralPath $t.Path -Destination $bak -Force Write-Ok "已备份 -> $(Split-Path -Leaf $bak)" } Set-TomlRoot -Path $t.Path -Key 'rendezvous_server' -Value "${ID_SERVER}:21116" Set-TomlOption -Path $t.Path -Key 'custom-rendezvous-server' -Value $ID_SERVER Set-TomlOption -Path $t.Path -Key 'relay-server' -Value $RELAY_SERVER Set-TomlOption -Path $t.Path -Key 'api-server' -Value $API_SERVER Set-TomlOption -Path $t.Path -Key 'key' -Value $PUBLIC_KEY Write-Ok '配置已写入' } # ---------- 4. 重启服务 ---------- if (-not $SkipRestart) { if ($svc) { Write-Step '启动 RustDesk 服务' try { Start-Service -Name $SERVICE_NAME Start-Sleep -Seconds 2 $s = Get-Service -Name $SERVICE_NAME if ($s.Status -eq 'Running') { Write-Ok '服务已启动' } else { Write-Warn2 "服务状态: $($s.Status)" } } catch { Write-Warn2 "服务启动失败: $($_.Exception.Message)" } } Write-Step '启动客户端界面' Start-Process -FilePath $exe -ErrorAction SilentlyContinue Start-Sleep -Seconds 4 } # ---------- 5. 读取本机 ID ---------- Write-Host '' $myId = $null try { & $exe --get-id | Out-Null Start-Sleep -Seconds 2 Add-Type -AssemblyName System.Windows.Forms $myId = [System.Windows.Forms.Clipboard]::GetText() } catch { } Write-Host '──────────────────────────────────────────' -ForegroundColor DarkGray if ($myId -and $myId -match '^\d{6,12}$') { Write-Host " 本机 XW远程 ID: $myId" -ForegroundColor White } else { Write-Host ' 请在客户端主界面查看本机 ID' -ForegroundColor White } Write-Host " 服务器: $ID_SERVER" -ForegroundColor DarkGray Write-Host '──────────────────────────────────────────' -ForegroundColor DarkGray Write-Host '' Write-Ok '配置完成。若客户端仍显示旧服务器,请完全退出 RustDesk 后重新打开。' Write-Host '' Write-Warn2 '提示:远程控制本机前,请先在客户端「设置 → 安全」中设置永久密码。'