后台保活.ps1
|
<#PSScriptInfo
.VERSION 1.0.0 .GUID d64662e5-a2c2-4f12-a253-7195a7d59bec .AUTHOR 埃博拉酱-机器人 .COMPANYNAME 一致行动党 .COPYRIGHT (c) 2026 埃博拉酱-机器人. MIT License. .TAGS Session0 Background Process Persist Logoff RDP SSH WMI Windows .LICENSEURI https://opensource.org/licenses/MIT .RELEASENOTES 1.0.0 - 初始版本。通过 SSH localhost + WMI 在 Session 0 创建保活进程。 #> <# .SYNOPSIS 在 Session 0 创建登出后保活的后台进程 .DESCRIPTION 通过 SSH localhost 让 sshd(Session 0 的 SYSTEM 服务)以当前用户身份创建进程, 再用 WMI Win32_Process.Create 绕过 sshd 的 Job Object 使进程独立存活。 进程运行在 Session 0,远程桌面登出后不会被终止。 原理: 1. SSH 连接 localhost → sshd 运行在 Session 0,故 SSH 会话继承 Session 0 2. SSH 会话内通过 WMI 创建进程 → 进程由 wmiprvse.exe 派生,脱离 sshd Job Object 3. SSH 断开后 WMI 创建的进程不受影响,持续运行在 Session 0 首次使用自动生成 SSH 密钥并配置免密登录。 若 sshd 未启动/被禁用则通过 UAC 请求一次性提权。 sshd 未安装时报错提示安装命令。 前置条件: - Windows 10 / Server 2019 或更高版本 - OpenSSH 客户端(系统内置) - OpenSSH Server(可选功能,首次使用会提示安装) .PARAMETER 命令行 要在后台运行的完整命令行 .EXAMPLE .\后台保活.ps1 'powershell -NoProfile -File C:\Scripts\MyTask.ps1' 在 Session 0 启动 PowerShell 脚本,RDP 登出后继续运行。 .EXAMPLE $PID = .\后台保活.ps1 'python C:\Scripts\server.py' Stop-Process -Id $PID 启动 Python 服务器并记录 PID 以便后续停止。 .INPUTS None .OUTPUTS System.Int32 成功时返回后台进程的 PID。 .NOTES 需要 OpenSSH Server 已安装。首次运行需 UAC 一次性提权以启动 sshd 服务。 .LINK https://learn.microsoft.com/windows-server/administration/openssh/openssh-install-firstuse #> param( [Parameter(Mandatory=$true, Position=0)] [string]$命令行 ) # ---------- 前置检查 ---------- if (-not (Get-Command ssh -ErrorAction SilentlyContinue)) { Write-Host "错误: 未找到 ssh 命令,请确认 OpenSSH 客户端已安装" -ForegroundColor Red return } $sshd服务 = Get-Service sshd -ErrorAction SilentlyContinue if (-not $sshd服务) { Write-Host "错误: OpenSSH Server 未安装。请以管理员身份运行:" -ForegroundColor Red Write-Host " Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0" -ForegroundColor Yellow return } # ---------- 确保 SSH 密钥 ---------- $密钥目录 = "$env:USERPROFILE\.ssh" $密钥路径 = "$密钥目录\id_ed25519" $公钥路径 = "$密钥目录\id_ed25519.pub" if (-not (Test-Path $密钥路径)) { if (-not (Test-Path $密钥目录)) { New-Item -ItemType Directory -Path $密钥目录 -Force | Out-Null } & ssh-keygen -t ed25519 -f "$密钥路径" -N '""' -q 2>&1 | Out-Null if (-not (Test-Path $密钥路径)) { Write-Host "错误: SSH 密钥生成失败" -ForegroundColor Red return } Write-Host "已生成 SSH 密钥" -ForegroundColor Green } # ---------- 测试 SSH 连接 ---------- $测试输出 = & ssh -n -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=5 localhost "echo __SSH_OK__" 2>&1 $已连通 = ($测试输出 | Out-String).Contains("__SSH_OK__") # ---------- SSH 未就绪时一次性 UAC 配置 ---------- if (-not $已连通) { Write-Host "首次使用,需要管理员权限配置 SSH..." -ForegroundColor Yellow $公钥内容 = (Get-Content $公钥路径 -Raw).Trim() $标记文件 = Join-Path $env:TEMP "ssh-setup-done-$PID" Remove-Item $标记文件 -ErrorAction SilentlyContinue $提权脚本 = Join-Path $env:TEMP "ssh-setup-$PID.ps1" @" `$ErrorActionPreference = 'Stop' try { # 启动 sshd 并设为开机自启 Start-Service sshd -ErrorAction SilentlyContinue Set-Service sshd -StartupType Automatic # 管理员用户的 authorized_keys `$管理员密钥 = 'C:\ProgramData\ssh\administrators_authorized_keys' `$公钥 = '$($公钥内容 -replace "'","''")' if (-not (Test-Path `$管理员密钥) -or -not (Get-Content `$管理员密钥 -Raw -ErrorAction SilentlyContinue).Contains(`$公钥)) { Add-Content `$管理员密钥 -Value `$公钥 -Encoding UTF8 } icacls `$管理员密钥 /inheritance:r /grant 'SYSTEM:(R)' /grant 'Administrators:(R)' | Out-Null # 普通用户的 authorized_keys `$用户密钥 = '$密钥目录\authorized_keys' if (-not (Test-Path `$用户密钥) -or -not (Get-Content `$用户密钥 -Raw -ErrorAction SilentlyContinue).Contains(`$公钥)) { Add-Content `$用户密钥 -Value `$公钥 -Encoding UTF8 } 'OK' | Out-File '$标记文件' } catch { `$_.Exception.Message | Out-File '$标记文件' } "@ | Set-Content $提权脚本 -Encoding UTF8 try { Start-Process powershell -Verb RunAs -Wait -ErrorAction Stop ` -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$提权脚本`"" } catch { Write-Host "错误: UAC 提权被取消" -ForegroundColor Red Remove-Item $提权脚本 -ErrorAction SilentlyContinue return } Remove-Item $提权脚本 -ErrorAction SilentlyContinue if (-not (Test-Path $标记文件)) { Write-Host "错误: 配置脚本未正常完成" -ForegroundColor Red return } $结果 = (Get-Content $标记文件 -Raw).Trim() Remove-Item $标记文件 -ErrorAction SilentlyContinue if ($结果 -ne 'OK') { Write-Host "错误: $结果" -ForegroundColor Red return } Start-Sleep 2 $测试输出 = & ssh -n -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=5 localhost "echo __SSH_OK__" 2>&1 $已连通 = ($测试输出 | Out-String).Contains("__SSH_OK__") if (-not $已连通) { Write-Host "错误: 配置完成但 SSH 连接仍失败" -ForegroundColor Red Write-Host ($测试输出 | Out-String) -ForegroundColor Gray return } Write-Host "SSH 配置完成" -ForegroundColor Green } # ---------- 通过 SSH + WMI 在 Session 0 创建保活进程 ---------- # 用 WMI Win32_Process.Create 绕过 sshd 的 Job Object, # 使进程在 SSH 会话结束后仍然存活 $命令文件 = Join-Path $env:TEMP "bg-cmd-$PID.txt" $启动器 = Join-Path $env:TEMP "bg-launch-$PID.ps1" $命令行 | Set-Content $命令文件 -Encoding UTF8 -NoNewline @" `$cmd = (Get-Content '$命令文件' -Raw).Trim() `$r = Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{CommandLine = `$cmd} if (`$r.ReturnValue -eq 0) { `$r.ProcessId } else { throw "WMI CreateProcess 失败: ReturnValue=`$(`$r.ReturnValue)" } "@ | Set-Content $启动器 -Encoding UTF8 $输出 = & ssh -n -o BatchMode=yes localhost "powershell -NoProfile -ExecutionPolicy Bypass -File `"$启动器`"" 2>&1 Remove-Item $命令文件, $启动器 -ErrorAction SilentlyContinue $PID行 = $输出 | Where-Object { "$_".Trim() -match '^\d+$' } | Select-Object -First 1 if ($PID行) { $进程ID = [int]"$PID行".Trim() $进程 = Get-Process -Id $进程ID -ErrorAction SilentlyContinue if ($进程) { Write-Host "已在 Session $($进程.SessionId) 启动后台进程 (PID=$进程ID)" -ForegroundColor Green } else { Write-Host "已启动后台进程 (PID=$进程ID)" -ForegroundColor Green } return $进程ID } else { Write-Host "错误: 启动后台进程失败" -ForegroundColor Red if ($输出) { $输出 | ForEach-Object { Write-Host " $_" -ForegroundColor Gray } } } |