Windows 远程太折腾,我做了个 OpenSSH 离线一键安装包

Zou, Ning 发布于 2 小时前 技术实践 3328 字 23 次阅读


AI Agent 工作台这波火了之后,我的日常变成了「让工具替我去操作机器」。Linux 那台随手一句 ssh 就进去了,轮到 Windows 就卡住——远程这一环一直是软的。要想用AI远程帮别人解决问题非常麻烦,RDP基本上不通,照着AI做一步截一张图十分浪费时间不说,效率还非常底下。走Windows传统的命令交互式,也很麻烦,需要在解决端口、证书,语法还比较复杂。

我前后试了三种传统办法,每一种都有它自己的不方便。先说结论:我要的不是「看到那台机器的桌面」,而是一条能在命令行里跑通、能被脚本调用的通道。 这件事 SSH 天生就对。

传统 Windows 远程手段,我踩完的结论

方式能不能用卡在哪
远程桌面(RDP)能用走图形界面,带宽占用高;Windows 客户端版(含专业版、企业版)只允许一个交互会话,远程一登,坐在机器前面的人就被踢下线;批处理、脚本化自动化基本没法做
WinRM / PowerShell Remoting能用,但要先配默认关闭,得先 Enable-PSRemoting;还要处理端口 5985/5986、证书、TrustedHosts 一堆前提;跨平台工具链对它的支持远不如 SSH
第三方远控(向日葵、ToDesk 之类)能用要装客户端、流量走它自己的服务器中转、免费版限速;给运维用的脚本化通道基本没有
OpenSSH 服务端能用,而且最像 Linux但 Windows 上装它本身就有坑,见下文

拿 RDP 举例。它解决的是「让我看到那台机器的屏幕」,所以我远程一连上去,工位上的人就黑屏了——这在需要并行用的机器上是硬伤。第三方远控绕开了单会话限制,代价是装客户端、走中转、限速,而且它给的是个画面,不是一个端口。

而 SSH 给的是一条端口、一个 shell、一套我在 Linux 侧已经用熟的肌肉记忆。脚本、密钥、scp、跳板、tmux,全部可以直接复用过来。这台 Windows 从此在我眼里就是一台 Linux。

于是决定:给它装上 sshd。

Windows 装 sshd,坑比想的多

Windows 10 1809 之后自带 OpenSSH 客户端,服务端是个「可选功能」。听起来一条命令的事:

Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0

实际跑下来不是这么回事。我把踩过的坑列一下——这也正是一键包存在的全部理由

坑 1:可选功能根本拉不下来

Add-WindowsCapability 背后依赖 Windows Update / FoD(Features on Demand)。只要机器满足下面任意一条,它就会失败:

  • 由 WSUS 或组策略接管了更新
  • 完全断网,或者处在内网隔离网段
  • 网络访问不到 Windows Update

报错通常是 0x800F09540x8024402C0x80240438 这几种。在离线或者被托管的机器上,这条路基本是死的。 这是所有坑里最先撞上的一个。

坑 2:防火墙规则绑死了程序路径

这条最隐蔽,也最气人。

Windows 内置的那条 OpenSSH-Server-In-TCP 规则,会把「只对某个程序生效」绑定到 C:\Windows\System32\OpenSSH\sshd.exe

而我用离线包(zip / MSI)装的时候,sshd 落在 C:\Program Files\OpenSSH\sshd.exe。结果就是:规则还在、端口也开着、服务显示 Running,但包在外面被丢掉了——死活连不上。 因为规则的「程序」这一项指向了一个并不存在的路径。

排查的时候最容易在服务状态上绕圈:服务是好的,日志是干净的,Get-Service sshd 一切正常,唯独连不上。

坑 3:依赖 VC++ 运行库

Win32-OpenSSH 的官方编译包依赖 Visual C++ 2015–2022 运行库(x64)。

干净系统上的典型症状是:sshd 服务装上了、也启动了、十秒后自己退了,日志里什么都没有。 手动跑一下才会看到 0xc000007b 或者提示缺 vcruntime140.dll。离线机器上这尤其难受——你没法顺手去下个运行库。

坑 4:一堆零碎,但每一个都能绊住你

  • 新版 v10 把用户认证阶段拆到了独立进程 sshd-auth.exe / sshd-session.exe。拷贝时漏掉其中一个,症状是能连上、但认证阶段直接失败——登不进去
  • 旧版 Windows 10 若残留 SshdBroker 服务,会让 sshd 注册失败,得先 sc.exe delete SshdBroker
  • 用脚本提权(UAC)时,Start-Process -Verb RunAs 对参数数组的转义处理并不可靠,常见后果是「UAC 点了『是』,窗口一闪就没了」,连报错都看不到
  • 想换非默认端口,得同时改 sshd_config 和防火墙,漏一个就不通

这几个坑我一个个手填过。不是不行,但每台机器都重来一遍太蠢,何况要装的不止一台。

于是做了个一键离线包

思路很直接:把上面这些坑全部写进一个脚本,做成一件事——在机器上双击一次,装完、注册服务、设开机自启、放通防火墙,最后把连接地址打印出来。全程不联网。

包里的东西:

文件作用体积
一键离线安装.bat双击入口,自动检测管理员权限,必要时弹 UAC 提权1.6 KB
Enable-OpenSSH.ps1主脚本,所有的活都在这儿34 KB
OpenSSH-Win64-v10.0.0.0.msi官方 v10 发布包,静默安装,优先用它6.3 MB
OpenSSH-Win64.zip同版本 zip,MSI 用不了时的备选5.4 MB
vc_redist.x64.exeVC++ 2015–2022 运行库(x64),离线补坑 324.5 MB
校验值.txt三个二进制的 SHA256,拷来拷去后验完整性
使用说明.txt三步用法 + 故障排查清单3.5 KB

整包 36 MB 左右,U 盘随手就装得下,拔了就断电也不影响——它不依赖任何在线资源。

脚本按执行顺序做六件事:

  1. 查权限。 非管理员自动弹 UAC 提权;传参用单个字符串拼装,专门避开坑 4 里那个转义问题
  2. 装组件。 按顺序自动降级:你指定的离线源 → 自动发现本地包 → Windows Update → 官方 GitHub。带上 -NoInternet 就只走前两条,绝不碰网络
  3. 配端口。 默认 22;-Port 2222 会同步改 sshd_config 和防火墙规则,原配置自动备份为 .bak
  4. 设默认 Shell。 把登录后的 shell 从 cmd 换成 PowerShell(写 HKLM:\SOFTWARE\OpenSSH 下的 DefaultShell)。不想换就传 $false
  5. 起服务。 设成自动启动并运行,然后等最多 10 秒确认它没崩——「启动即退出」的情况在这一步就会暴露
  6. 放通防火墙,并纠偏。 建好/更新入站规则(TCP + 所有网络配置文件),同时把绑死的程序路径改成当前实际在用的 sshd.exe。这就是坑 2 的针对性修复

另外有两个我特意加进去的东西,都是为了离线场景:

  • 落地自检。 拷贝完立刻核对 sshd.exelibcrypto.dll,以及新版的 sshd-auth.exe / sshd-session.exe 是否齐全。离线环境跑一趟不容易,不能等装完才发现缺文件。
  • 失败诊断。 服务一旦起不来,脚本自动跑 sshd -Vsshd -t,打印 C:\ProgramData\ssh\logs\sshd.log 的最后 15 行,缺 VC++ 运行库会直接点出来。省得在一台没网的机器上翻资料。

还有一条:它能回滚。 结尾会打印卸载命令,服务、防火墙规则、系统组件分别怎么撤,写得清清楚楚。这一步不做,我就不敢往别人机器上跑。

实测

包里留了完整安装日志(_OpenSSH-安装日志.txt),我在三台机器上跑过,这里挑关键行:

==== 第 1 步 / 安装 OpenSSH Server 组件 ====
[ INFO ] 发现本地离线包:...\OpenSSH-Win64-v10.0.0.0.msi
[ INFO ] 静默安装 MSI:OpenSSH-Win64-v10.0.0.0.msi
[  OK  ] MSI 安装完成。

==== 第 5 步 / 放通 Windows 防火墙 ====
[  OK  ] 已创建防火墙入站规则:TCP 22(所有网络配置文件)。
[ INFO ] 规则未绑定具体程序(按端口放通),当前 sshd:C:\Program Files\OpenSSH\sshd.exe

==== 第 6 步 / 结果验证 ====
[  OK  ] sshd 服务状态:Running(启动类型:Automatic)
[  OK  ] 端口 22 正在监听,SSH 已就绪。

三次的汇总:

次序系统安装源结果
第 1 台Windows 11 企业版 10.0.22631本地 MSI服务 Running,22 端口监听
第 2 台Windows 11 企业版 10.0.22631本地 MSI同上,脚本输出连接地址 ssh <user>@192.168.101.73
第 3 台Windows 11 企业版 10.0.22631本地 MSI同上

从双击到看到连接地址,一次十几秒。走完之后在另一台机器上直接 ssh 用户名@目标IP 就进去了。

怎么用

三步:

  1. 把整个文件夹拷到 U 盘,插到目标电脑上。建议先复制到本地磁盘(比如 C:\Temp\OpenSSH-Offline-Bundle)再运行
  2. 双击 一键离线安装.bat。它会自己检测权限,不是管理员就先弹 UAC,确认后在同一个窗口里继续跑——不会一闪而过
  3. 等它跑完,屏幕末尾会直接给你连接命令

不想用 bat 的话,管理员 PowerShell 里手动跑也一样:

.\Enable-OpenSSH.ps1 -OfflineSource .\OpenSSH-Win64.zip -NoInternet

常用参数:

参数说明
-NoInternet纯离线模式,绝不访问网络(本场景建议必加)
-OfflineSource <路径>指定离线源,支持 MSI / zip / 已解压目录 / FoD 的 cab 目录
-Port 2222换成非默认端口,会自动同步改配置和防火墙
-SetDefaultShellToPowerShell $false登录后进 cmd 而不是 PowerShell

两个前提要注意:

  • 用于登录的 Windows 账户必须设了密码,空密码账户登不进去。微软账户就用邮箱当用户名,本地账户可以用 计算机名\用户名 的形式
  • 如果是从外网连,云主机安全组或公司防火墙还要额外放通对应端口——脚本只管本机的 Windows 防火墙

卸载 / 回滚

# 停止并禁用服务
Stop-Service sshd; Set-Service sshd -StartupType Disabled

# 删除防火墙规则
Remove-NetFirewallRule -Name OpenSSH-Server-In-TCP

# 卸载系统组件(如果是通过可选功能装的)
Remove-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0

如果是 MSI 安装的,去控制面板卸载 OpenSSH-Win64 即可。

小结

这次折腾下来,几个结论算是记住了:

  1. Windows 不是不能当 Linux 管,缺的只是一条通道。 装上 sshd 之后,我在 Linux 侧的脚本、密钥、习惯全部可以直接搬过来,运维这件事终于统一成了同一套动作。
  2. RDP 和 SSH 解决的不是同一个问题。 前者给你屏幕,后者给你端口。需要自动化的时候,图形界面反而是负担——这也是单会话限制为什么那么致命。
  3. 离线环境里最难的不是「装」,是「拿到排障信息」。 有网的时候缺个运行库随手就补了,没网的时候连报错都看不到。所以脚本必须把诊断内建进去,而不是让你回来再查资料。
  4. 别忽略防火墙规则的「程序绑定」。 服务是 Running、端口在监听、日志干净,仍然可能被一条指向旧路径的规则掐死。这个坑我印象最深,因为它把所有常规排查方向都指向了错误的地方。
  5. 提权时的参数转义是个经典陷阱。 Start-Process -Verb RunAs 传数组参数不可靠,改成拼字符串才稳。这类问题一旦出现就是「点了确认,什么都没发生」,最难查。
  6. 能不能回滚,决定了你敢不敢用它。 一个只能装、不会卸的脚本,我是不敢往生产机器上放的。

现在新机器到手,插 U 盘、双击、十几秒,然后我就能在终端里 ssh 进去,跟管一台 Linux 一样。这件事就算告一段落了。

项目成果

最后附上打包好的离线包:

下载地址OpenSSH-Offline-Bundle

访问密码OOWW

解压后约 36 MB,含 MSI / zip / VC++ 运行库 / 一键脚本与使用说明。

包里三个二进制的 SHA256,拷来拷去之后可以自己验一下:

OpenSSH-Win64.zip
  SHA256  23F50F3458C4C5D0B12217C6A5DDFDE0137210A30FA870E98B29827F7B43ABA5

OpenSSH-Win64-v10.0.0.0.msi
  SHA256  DDEC9C53864280759CF9F74791CEFD387100E3946AA849A1C138A4ED1B96B7D9

vc_redist.x64.exe
  SHA256  CC0FF0EB1DC3F5188AE6300FAEF32BF5BEEBA4BDD6E8E445A9184072096B713B

延伸阅读