写 Windows 功能有一个很朴素的麻烦:代码可以在 Mac 上写,问题却只肯在 Windows 上出现。
以前每次验证,我都要走到那台 Windows 小主机旁边,登录、打开应用、重复一遍刚才的操作,再把日志和截图带回来。机器离我只有几米,流程却像隔着一条河。
后来我给它起了一个很普通的名字:win-test。
现在,我可以直接把任务交给 Agent。它先确认设备在线,再通过 SSH 进入一台真正的 Windows 11,运行 PowerShell、查服务、看进程、读注册表,最后把结果带回来。要改东西时先停下来确认,改完再读一次状态,而不是丢下一句“命令执行成功”。
这篇文章不讲什么神奇的“AI 接管电脑”,只记录我实际使用的这条链路,以及它为什么比我一开始想的麻烦,也比远程桌面更适合 Agent。
我想要的不是另一台开发机
虚拟机当然能测 Windows,但有些问题只会出现在一台接近普通用户环境的真机上:PowerShell 版本、文件权限、Windows 服务、注册表、路径分隔符、系统编码、防火墙,甚至应用商店留下的执行别名,都可能让结果拐个弯。
一台装满开发工具的 Windows,也未必是理想测试机。它越像我的开发环境,越容易把代码里的“想当然”藏起来。
win-test 的目标很明确:把一台 Windows 真机接进开发和验收流程。以前我说“我刚才在那台机器上看过了”;现在需要的是一条可以重跑、可以留下证据、失败后知道去哪里找原因的链路。
这里说的“控制”,不是远程桌面
这套方法的结构其实很简单:
我在 DesireCore 中交代任务
↓
Agent 判断风险、拆解步骤
↓
SSH 别名连接 Windows OpenSSH
↓
cmd.exe / PowerShell 执行
↓
JSON、日志、退出状态和复查结果
↓
Agent 汇报,必要时等待人工确认
它和 RDP 不是一回事。
RDP 是我坐在屏幕前点鼠标;SSH 更像给 Agent 留了一扇窄门。它可以查询文件、进程、服务、CIM/WMI、注册表、网络状态和计划任务,也可以运行脚本,但看不到一个完整的交互式桌面。
这个边界很重要。SSH 会话里能启动进程,不代表窗口一定出现在当前用户眼前;能加载截图相关组件,也不代表它一定能看到正在使用的桌面。需要操作原生 GUI 时,应该走专门的桌面自动化通道,而不是假装一条 PowerShell 命令拥有眼睛。
在 Windows 11 启用 OpenSSH Server
Windows 11 将 OpenSSH Server 作为可选功能提供。先在管理员 PowerShell 中安装并启动服务:
Get-WindowsCapability -Online | Where-Object Name -Like 'OpenSSH.Server*'
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
Start-Service sshd
Set-Service -Name sshd -StartupType Automatic
Get-NetFirewallRule -Name OpenSSH-Server-In-TCP
如果最后一条没有找到入站规则,再按照微软的 OpenSSH Server 配置文档 创建 TCP 22 规则。不要因为 ping 不通就判断 SSH 也不通,Windows 防火墙可能只拦了 ICMP,直接检查 TCP 22 更准。
接下来配置密钥认证。微软文档里有一个很容易踩的细节:普通用户和管理员用户存放公钥的位置不同。管理员账户通常使用:
C:\ProgramData\ssh\administrators_authorized_keys
而且这个文件的 ACL 要收紧,只保留 Administrators 和 SYSTEM 所需权限。很多 Permission denied (publickey),不是密钥生成错了,而是公钥放错了地方,或者权限看起来“更宽松”,Windows OpenSSH 反而拒绝使用。
我会在 Agent 所在设备上生成一对独立密钥:
ssh-keygen -t ed25519 -f ~/.ssh/win-test
私钥留在本机,公钥写入 Windows 对应的 authorized_keys 文件。管理员账户的 ACL 可以参考微软文档使用 icacls 收紧。非英文版 Windows 的本地组名可能不同,照抄英文组名也会失败,微软建议必要时改用 SID。具体步骤见 Windows OpenSSH 密钥认证文档。
以下配置全部使用占位符,不公开真实 IP、用户名、机器名、密钥内容和指纹。
我在本机的 SSH 配置里只保留一个别名,形式大致如下:
Host win-test
HostName <windows-ip>
User <windows-user>
IdentityFile <private-key-path>
IdentitiesOnly yes
真正开始做事前,Agent 先跑的不是业务命令,而是连通性预检:
ssh -o BatchMode=yes \
-o PasswordAuthentication=no \
-o ConnectTimeout=8 \
win-test \
'cmd.exe /d /s /c "whoami & hostname & echo REMOTE_WINDOWS_OK"'
BatchMode=yes 很关键。没有它,认证失败时 SSH 可能停在密码框或确认提示上。人坐在终端前还会发现它在等输入,Agent 遇到这种情况只会一直等到超时。
我会让它证明三件事:别名能解析;认证过程不会等待人工输入;Windows 能返回预先约定的标记。三关都过了,后面才轮到 PowerShell。
让 Agent 稳定执行远端 PowerShell
简单命令很好办:
ssh win-test "powershell.exe -NoLogo -NoProfile -NonInteractive -Command Get-Service"
稍微复杂一点,事情就开始难看了。
一段命令从 macOS 发出,要经过本地 Shell、SSH、Windows 默认 Shell,最后才落到 PowerShell。肉眼看着完全正确的 $、引号、反斜杠和花括号,走完这一圈,可能已经不是原来的东西。
我最初也试过继续加反斜杠。加到第三层以后,我已经很难确认每个引号究竟由哪一层解释。能跑一次,不代表下次还看得懂。
现在复杂脚本会先转成 PowerShell 需要的 UTF-16LE Base64,再通过 EncodedCommand 发送:
powershell.exe -NoLogo -NoProfile -NonInteractive -EncodedCommand <BASE64>
这里的 Base64 不是加密,只是为了解决转义。密码、Token、私钥仍然不能塞进去。微软在 about_PowerShell_exe 中也明确说明了 EncodedCommand 使用 UTF-16LE 编码。
中文输出则要单独处理。PowerShell 5.1 的默认编码很容易让中文变成乱码,我在需要中文回显时会先设置:
[Console]::OutputEncoding = [Text.Encoding]::UTF8
能返回 JSON 的任务,尽量不要让 Agent 猜一大段自然语言输出。比如检查环境,我更愿意让 Windows 回这样一个结果:
$result = [ordered]@{
computer = $env:COMPUTERNAME
powershell = $PSVersionTable.PSVersion.ToString()
sshd = (Get-Service sshd).Status.ToString()
}
$result | ConvertTo-Json -Compress
Agent 不需要从“命令未找到”或本地化后的表格里猜结论,字段是什么,结果就是什么。
一次真正的 win-test
这次写文章前,我又把链路从头跑了一遍。
别名解析正常,BatchMode 没有卡在密码和指纹提示上;远端 cmd.exe 返回了约定标记;PowerShell 的 EncodedCommand 能执行,并返回压缩后的 JSON。实际结果做了脱敏,只保留验证结论:
| 验证项 | 实测结果 |
|---|---|
| 远端身份 | 返回了预期的 Windows 用户和主机名 |
| PowerShell | 5.1,UTF-8 中文输出正常 |
| 结构化脚本 | EncodedCommand 返回有效 JSON |
| 系统查询 | 文件、进程、服务、CIM/WMI、注册表、网络和计划任务均可读取 |
| 失败状态 | 返回非零,但外层包装改写了具体数值 |
我没有顺手跑安装、写文件或传输测试。验证能力不等于每次都要把权限用满。能用只读方式证明的事情,就没有必要为了截图去改一遍远端系统。
倒是遇到了一个不起眼、但很典型的坑:我让远端进程主动 exit 7,本地 SSH 最后拿到的却是 1。
如果代码写成“返回 7 才算我预期的失败”,这个测试就会误判。按 SSH 的正常语义,它会返回远端命令的退出状态;这次的结果说明 cmd.exe、PowerShell 或外层包装中至少有一层改写了数值,后面还需要单独定位。实际任务不能只押在一个数字上,我会把几项放在一起看:
- 退出状态是不是非零;
- 标准错误里写了什么;
- JSON 中有没有明确的
success或error字段; - 操作以后,目标状态有没有真的改变。
服务启动后再查一次服务,进程拉起后再查端口,配置修改后重新读取。返回成功只能说明命令没有报错,不能证明目标状态已经改变。
为什么要把这件事教给 Agent
如果只是偶尔连一台机器,Shell 历史记录已经够用。真正让我觉得值得做成 Agent 能力的,是这些细节会反复出现:
- 开始前必须做无交互连接测试;
- 默认只读,涉及安装、删除、重启和系统配置时先确认;
- 中文输出要切到 UTF-8;
- 复杂脚本使用
EncodedCommand; - 修改前读取,修改后复查;
- 汇报中隐藏地址、账户、路径和密钥信息;
- DHCP 地址变化时,先查别名和网络,不要急着重做密钥。
如果这些规矩只留在当前对话和 Shell 历史里,下一次任务仍然要重新发现。于是我把连接预检、默认只读、风险确认和结果复查写进 Agent 的规则与 Skill。下一次我说“去 Windows 真机复核一下”,它知道先做什么、哪些动作可以直接做、什么地方必须停下来问我。
更重要的是,保存下来的不是私钥本身。密钥由本地 SSH 管理,Agent 只需要知道一个经过授权的设备别名和操作规矩。能力与秘密分开,远比把一段账号密码粘进提示词可靠。
这些规则、技能、记忆和任务回执会进入 AgentFS。它们是可查看、可版本管理的文件。某天我发现“精确退出码不能作为唯一依据”,这次纠正可以进入 Skill;如果教错了,也能审阅和回滚。
可培养 Agent 在设备控制场景里的价值,是记住一整套做事边界,包括我愿意把设备交给它到什么程度。
命令行走不到的地方,交给 GUI MCP
SSH 跑通以后,Windows 上大部分系统问题已经可以交给 Agent:查服务、读注册表、跑脚本、抓日志,都不难。
剩下那一小截,往往才最磨人。
安装向导停在哪一步,旧软件弹了什么框,某个按钮到底是没加载出来,还是已经变灰——这些事情,PowerShell 再熟也看不见。
这里说的 MCP,不是给 SSH 再套一层工具。DesireCore 在 Windows 上接入的是一套真正面向 GUI 的 MCP:CUA Driver。
我更愿意把它理解成给 Agent 补了一双眼睛和一双手。它可以读取当前窗口和控件状态,也可以截图、点击、输入文字、发送快捷键,再把操作后的界面读回来。
实际链路是这样的:
DesireCore Agent
↓
系统内置 MCP:cua-driver
↓
cua-driver mcp
↓
Windows UIA / MSAA / PostMessage / SendInput
↓
记事本、安装程序和其他 Windows 应用
普通用户不需要再手动安装一遍
我写这篇文章时,DesireCore Windows 正式安装包已经内置 CUA Driver 0.7.1,并在 Windows 上默认启用。
也就是说,正常情况下,安装 DesireCore Windows 版已经完成了 MCP 的安装和接入。用户不需要再装 Python,不需要用 npx 下载社区包,也不用手工修改 agent.json。
DesireCore 内部注册的系统 MCP 大致如下:
{
"transport": "stdio",
"command": "cua-driver",
"args": ["mcp"],
"enabled": true,
"probe_command": true
}
这段配置不是让普通用户照着粘贴的,它是 DesireCore 在 Windows 上的系统默认项。
每次 Agent 开始任务时,DesireCore 会先检查 Windows 的 PATH。如果用户自己安装了较新的 CUA Driver,就优先使用用户版本;如果没有,则自动回退到安装包内置的二进制文件。两边都找不到时才会静默跳过,不会让每次对话都卡在一个不存在的工具上。
在“资源管理器 → 运行环境”中,还能看到当前使用的 CUA Driver 版本和来源。对普通用户来说,这比记住某个安装目录更实际。
Agent 实际怎么操作 Windows 界面
第一次使用时,我不会直接让 Agent 到处点,而是先给它一个很窄的任务:
只列出当前打开的 Windows 窗口。
不要点击,不要输入,也不要关闭任何程序。
这时 Agent 会调用类似这样的工具:
cua-driver__list_windows
确认目标窗口以后,再逐步放开截图、读取控件、点击和输入:
cua-driver__get_window_state
cua-driver__screenshot
cua-driver__click
cua-driver__type_text
我们在 Windows 真机上做过一次完整测试。DesireCore 成功发现了 39 个 GUI 工具,列出了当时真实存在的 11 个窗口和 9 个应用。
随后我让 Agent 打开记事本并输入一句测试文字。
它先找到记事本的编辑区域,尝试用 set_value 直接写入。Windows 11 新版记事本没有给这个控件实现对应的 ValuePattern,所以第一次调用失败了。
Agent 没有在这里停住,也没有假装操作成功。它读懂错误以后,换成了另一条路线:
启动记事本
↓
读取窗口和控件
↓
set_value 失败
↓
点击编辑区域
↓
模拟键盘输入
↓
重新读取窗口内容
最后,Agent 从窗口状态里读回了刚刚输入的完整文字。工具返回、独立查询和肉眼检查三边一致。
这件事看起来不大,却很能说明可培养 Agent 的意义。
MCP 提供的是操作能力,Agent 学到的则是做事的方法:新版记事本不能直接写值时,要切换成点击加输入;操作以后不能只相信“调用成功”,还要重新读取界面;下次遇到同一类软件,不必再从第一个坑重新踩起。
如果任务是安装验收,我还会把执行和复核拆给两个 Agent。一个负责点击和输入,另一个只看最终窗口、版本和截图,避免执行者顺手给自己的结果判个“通过”。
这不是一条远程桌面隧道
这里还有一个边界必须说清楚。
DesireCore 当前内置的 CUA Driver MCP,运行在安装 DesireCore 的那台 Windows 设备上。它通过本机 stdio 启动 cua-driver mcp,操作的也是这台机器当前的 Windows 桌面。
它不是让 Mac 上的 DesireCore 穿过 SSH,直接取得远端 Windows 的鼠标和键盘。
所以在本文这套方案里,两条链路各做各的事:
- SSH 和 PowerShell 负责远程查询服务、文件、注册表和系统状态;
- 目标 Windows 上的 DesireCore 通过 CUA Driver MCP 处理窗口、按钮、输入框和截图。
把这两件事写成同一个“SSH MCP”,反而会让人误以为命令行已经拥有了桌面会话。
GUI 操作也有自己的硬边界。UAC 安全桌面、Ctrl+Alt+Del、更高权限级别的窗口,以及部分使用特殊渲染方式的应用,都不能当作普通按钮随便点。
DesireCore 不会因为 MCP 声称自己是只读工具就自动相信它。MCP 操作真实宿主机,在默认确认策略下会经过人工确认;只有我明确把 Agent 放到更宽松的权限模式,它才会跳过这道闸门。
我现在的习惯仍然是:先列窗口,再读取状态,确认窗口标题和进程,最后才允许点击和输入。操作结束以后,再读一次窗口或者补一张截图。
GUI 自动化最怕的不是失败,而是点错了以后还告诉你一切正常。

DesireCore 在 Windows 上通过内置 CUA Driver MCP 读取窗口、点击和输入。它不是 SSH MCP,也不是远程桌面。
多智能体在这里不是为了热闹
查服务或读日志时,一个 Agent 足够。完整安装验收中,我会把执行和复核分开:一个负责操作,另一个只根据版本、日志和最终状态判断是否通过。这样做是为了避免执行者顺手给自己的结果盖章。DesireCore 的 跨智能体协作 在这里刚好用来隔开两份上下文。
先把安全边界说清楚
免密登录配置完成后,这台机器也多了一个需要认真管理的远程入口。
密钥登录不是“没有密码所以更安全”,只是把安全责任从记住密码,换成了保管私钥、限制网络边界和控制操作权限。我现在给 win-test 定的规矩很朴素:
- 只在可信局域网或 VPN 内使用,不把 SSH 端口直接暴露到公网;
- 最好使用专用测试账号和独立密钥,默认给够用的最低权限;
- 首次连接由人核验主机指纹,不用
StrictHostKeyChecking=no图省事; - 私钥、密码、Token 和恢复码不进入提示词、截图、脚本参数和日志;
- 删除文件、安装软件、重启机器、修改服务与系统设置,必须经过人工确认;
- 高风险操作前做预检,操作后读回状态;
- 任务结束后清理临时脚本和调试入口,保留必要回执。
DesireCore 的 人闸门 很适合放在“从读取变成修改”的那一刻。查询服务可以自动做;重启服务、安装驱动或删除文件,Agent 应该先把计划、影响和回退方式讲清楚,再等人点头。
我踩过的几个坑
| 现象 | 常见原因 | 我的处理方式 |
|---|---|---|
| 昨天能连,今天超时 | DHCP 地址变化、休眠或网络切换 | 先检查别名解析、TCP 22 和当前地址,不急着重做密钥 |
Permission denied (publickey) |
管理员公钥位置或 ACL 不对 | 使用 Windows 对应的密钥文件位置并收紧权限 |
| Agent 一直不返回 | SSH 正在等密码或首次连接确认 | 强制 BatchMode,设置连接超时,首次指纹由人确认 |
| 中文输出乱码 | PowerShell 5.1 默认输出编码 | 显式切换 UTF-8,复杂结果尽量返回 JSON |
| 命令肉眼正确,远端却报错 | 多层 Shell 把引号和变量拆坏 | 简单命令直接跑,复杂脚本使用 EncodedCommand |
| 进程存在,但桌面上没有窗口 | SSH 会话不是当前交互式桌面 | 不把进程状态当 GUI 证据,需要时走桌面自动化 |
| 返回码和远端预期不同 | 中间 Shell 或进程改变了退出状态 | 判断零/非零、stderr、结构化错误和最终状态 |
| 命令显示成功,实际没变化 | 只检查了调用,没有检查结果 | 每个变更动作都安排一次读回验证 |
这些问题单看都不大,凑在一起就很容易消耗一个下午。最值得培养的 Agent,往往不是会背最多命令的那个,而是踩过一次以后真的会绕开旧坑的那个。
如果你也想做一台 win-test
我建议先从只读巡检开始,不要一上来就追求“让 AI 自动安装软件”。
先让 Agent 完成一次最小闭环:无交互连接;返回预期用户和主机名;读取 PowerShell 版本;检查一个服务;输出 JSON;把命令、结果和仍然不确定的地方汇报出来。
接着故意制造几个安全的小问题:关闭一个测试进程、让一段脚本返回非零、输出一行中文、把地址换成失效值。看看 Agent 是真正理解失败,还是只会把终端最后一行重新念一遍。
等只读检查稳定以后,再加入需要人工确认的修改操作。GUI 自动化放到更后面,因为“看见并点击”比“读取并验证”更容易受环境变化影响。
这个顺序不够像一段炫目的演示,却更接近真实使用。
最后
这套链路把一次性的人工目测,变成了可以重跑和复查的 Windows 真机验证。Agent 执行命令以后,还要留下身份、输出、退出状态和最终状态,测试才算完成。
那台小主机多数时间仍然安静地放在桌边。不同的是,现在发现一个 Windows 问题以后,我可以把排查方法留下来,下次不必再从远程桌面的第一下点击开始。




hi~
哈喽 轶哥大佬
请问一下,我的13900k可以安装A100液冷显卡吗?如何辨别nvme的SSD是否占用了CPU的PCIE通道。
不过如果要计算卡 我还是建议用U1或U2服务器安 因为发然大
win11 24H2 26100.2605 ,按照如下修改成功:
Search: 8B 81 38 06 00 00 39 81 3C 06 00 00 75 Replace: B8 00 01 00 00 89 81 38 06 00 00 90 EB