为什么要把文件推到 Windows 服务器
做 7×24 自动化的人迟早会遇到一个问题:本地生成的 HTML、脚本、配置,怎么落到那台云端 Windows 服务器上?SSH 没开、FTP 没开、RDP 又没法脚本化,最常见的官方通道就是 winrs(WinRM 远程 shell)。这条路能走通,但坑比想象中多。本文把我们在真实项目里踩过的坑和最终解法讲清楚,不吹嘘、不编数据。
坑一:本机 WinRM 客户端服务被停用
最隐蔽的一个故障。脚本逻辑完全正确,winrs echo hello 在本机一跑却报 Winrs error: 目录无效 / 句柄无效。排查半天才发现:是本机 Windows 的 WinRM 客户端服务(不是服务器)被设成了 Manual 且没启动。服务器端的 WinRM 一直好好的,挂的是自己这台发令机。
坑二:写文件与回读输出都被“句柄无效”吃掉
即便会话能建立、echo 偶尔能回,只要一写文件、一读输出,就报 句柄无效。结果是:你以为推成功了,远程却 404,因为文件根本没落地。这正是“命令返回 0 但没成功”的典型——千万别把命令退出码当成功判据。
坑三:一次性推太多文件压垮 WinRM 会话池
想“顺手”把几十个历史归档页一起推?实测一次推 20+ 文件、上百次 winrs 会话,服务器立刻 内存资源不足 / 句柄无效,后续所有 winrs 全部失败。这是资源耗尽,不是脚本 bug。
可靠模式:base64 分块 + certutil 解码 + SHA256 校验
把大文件切成块,每块 base64 编码后通过单行命令写远程,再用 certutil -decode 落地。关键三步:
- ASCII 编码:base64 输出本身是 ASCII,避免中文/编码在远端被截断。
- 分块:单条命令长度有限,按 ~50KB 切块,逐块 append。
- SHA256 校验:落地后取远端哈希与本地比对,不一致就重传——这是防截断的最后一道闸。
最终解法:纯 Python pywinrm,彻底不依赖本机服务
我们把传输层从 winrs.exe 换成纯 Python 的 pywinrm:
- 复用单个 shell 会话,避免反复建连的会话池压力;
- 远端
Get-FileHash返回的哈希是大写,比较时必须忽略大小写; - 单文件调用加超时,失败隔离,绝不拖垮整批。
适合谁、注意什么
这套做法适合:需要把静态站点、脚本、配置从本地可信机器定时推到云端 Windows 服务器的自动化场景。注意两点:① 传输走的是管理通道,凭证要单独保管、别硬编码进仓库;② 校验不能省,宁可多花 10 秒比对哈希,也别交付一个“看起来 200 其实错页”的产物。远程部署没有银弹,只有“可验证”才算真上线。