xiaowei-system/skills/devops/hermes-debug/references/windows-ssh-probe.md

5.6 KiB
Raw Blame History

已 SSH 通道下解码 Windows 中文 UTF-16LE 输出

适用:已经通过 sshpass / ssh user@windows-host 进了 Windows cmd 或 PowerShell但终端里所有中文都成 乱码GBK 在 UTF-8 终端下被切碎成二进制)。
不要用 chcp 65001 —— Windows OpenSSH 默认把 channel 设置为 UTF-8但 cmd 输出是 GBK写到 pipe 后被 ssh 流程再次当 UTF-8 处理,结果是错的"二进制造纸"。


1. 症状(先把 ack 现象说清)

$ ssh user@host 'schtasks /query /tn "\Foo 任务" /v /fo LIST' 2>&1 | grep "LastRun"
grep: (standard input): binary file matches
# 中文 + 数字 + 标点的混合被 grep 视为 binary → -a 才能匹配

或者:

$ ssh user@host 'where hermes' 2>&1 | head
(空)
# cmd 输出看着是空,其实有内容,但都被当 binary 了

2. 三种解法(按推荐顺序)

解法 Apipe 到 strings (推荐)

ssh user@host '<CMD>' 2>/dev/null | strings | grep -v "^$" | head -40

原理strings(默认 GNU binutils扫任何输入把 ≥4 个字符的可打印字符串切出来,过滤掉控制字符。它对 UTF-8/GBK/UTF-16LE 全兼容,因为可打印字符在每种编码里都是 ASCII 范围。

例子

$ sshpass -p xue.2538 ssh administrator@192.168.123.11 \
    'schtasks /query /tn "\Hermes_Gateway" /v /fo LIST' \
    2>/dev/null | strings | grep -iE "Last|Result|Command" | head -10

输出真实样例(这是 Windows Server 计划任务查询的真实响应):

:                             ZHOME\Administrator
:                             \Hermes_Gateway
:                       N/A
:                             ZHOME\Administrator
:                       C:\Users\Administrator\.hermes\gateway-service\Hermes_Gateway.cmd

注意第一列 : 之前是被 UTF-8 切碎的中文,全部丢了;剩下可打印的英文/数字/路径完整。

解法 Bgrep -a 强制当文本(适合偶尔快速 grep

ssh user@host '<CMD>' 2>/dev/null | grep -a -E "LastRun|Hermes"

grep -a(或 --binary-files=text)让 grep 不把 stdin 当 binary。坏处中文输出会成 ,但你要找的英文 tokenLastRunResultC:\...)都能匹配到。

解法 C把输出重定向到文件再读回

如果目标 cmd 有大量中文,且 truly 中文也想看(比如 计划任务名、文件路径里有中文):

# 在 Windows 端重定向cmd 用 GBK 重定向)
ssh user@host 'cmd /c "schtasks /query /tn \"\Hermes_Gateway\" /v /fo LIST > C:\Users\Administrator\task.txt 2>&1"'

# 拉回 Linux
scp user@host:/C:/Users/Administrator/task.txt /tmp/

# 用 iconv 把 GBK 转 UTF-8
iconv -f GBK -t UTF-8 /tmp/task.txt

但这只有在中文真的对解决问题必要时才做。绝大多数诊断只关心 ASCII token能用解法 A 就别上 C。


3. 配套:把 Windows PowerShell 输出变干净

PowerShell 默认输出 UTF-16LE传统 pipe 也会乱。两种干净做法:

-NoProfile -Command "<短命令>"

ssh user@host 'powershell -NoProfile -Command "Get-Service | Select Name,Status | ConvertTo-Csv -NoTypeInformation"' \
    2>/dev/null | strings | head -10

或者输出 JSON 再 jq 解析

ssh user@host 'powershell -NoProfile -Command "Get-ScheduledTaskInfo -TaskName \"Hermes_Gateway\" -TaskPath \"\\\" | ConvertTo-Json"' \
    2>/dev/null | strings | python3 -m json.tool

ConvertTo-Json 输出 UTF-8ssh 不会切,但你要过 strings 兜底(仍然有些控制字符污染)。


4. 案例:解码 Windows 计划任务 LastRunTime 真正含义

PowerShell 提供 Get-ScheduledTaskInfo 才是真正的「上次运行时间 / 退出码」,不是 schtasks /query /v 拿到的那个 LastRun(那个常常显示系统时区不同的混乱格式)。

ssh user@host 'powershell -NoProfile -Command "Get-ScheduledTaskInfo -TaskName \"Hermes_Gateway\" -TaskPath \"\\\" | Format-List LastRunTime,LastTaskResult,NextRunTime,NumberOfMissedRuns"' \
    2>/dev/null | strings | head -10

返回:

LastRunTime        : 2026/7/1  18:26:26
LastTaskResult     : 267014
NextRunTime        : 
NumberOfMissedRuns : 0

关键码表

LastTaskResult 含义
0 操作成功完成
267011 任务尚未运行
267012 任务正在运行
267013 任务已禁用
267014 任务有要求,但任务尚未执行(开机启动项未触发场景)
1058 显示检索服务不可用
1062 任务计划程序服务未运行

备注267014 与 267011 经常被混用,都是 SCHED_S_TASK_HAS_NOT_RUN 家族;如果 LastRunTime 是 1999/11/30 0:00:00Windows 默认占位值),再核对 NumberOfMissedRuns 是否 0 综合判断。


5. Linux 端 strings 不可用时的兜底

某些 Alpine/musl 容器没有 strings,用 tr -cd '\11\12\15\40-\176'(保留可打印 ASCII

ssh user@host 'cmd' 2>/dev/null | tr -cd '\11\12\15\40-\176' | grep -E "Last|Run"

这个粗暴但能凑合。可打印 ASCII 范围 \40-\176 = ~,足以抓到路径和英文 token。


6. 与 winserver-recon.md 的分工

  • 侦察阶段(还没 SSH 通道)→ winserver-recon.md
  • 已 SSH 通道、处理 Windows 中文输出 → 本文件
  • 服务状态/重启hermes-debug SKILL.md 主体已有章节

7. 历史快照

  • 2026-07-01:首次成型。背景:连接 winserver 后查计划任务,所有 schtasks /query /v 输出都被 grep 当 binarystrings 兜底拿到 LastTaskResult=267014 / NumberOfMissedRuns=0 健康判定。