最近看到一张很有节目效果的截图:Codex 在 PowerShell 里修一个 Markdown 文件,先是 here-string 没执行,接着 CRLF/LF 匹配失败,再到反引号被 PowerShell 解析、路径反斜杠处理不稳、-split 参数写法踩坑。模型一路自我诊断,一路继续补脚本,最后评论区给它起了个很准确的标题:经典 GPT 5.5 大战 PowerShell。
截图里这串连续补救很有梗,但它背后的坑并不罕见。
这图好笑,是因为它太像真实开发现场。PowerShell 并不是简单的“Windows 版 Bash”。它有自己的字符串规则、参数绑定规则、cmdlet 错误模型和原生命令传参方式。Agent 一旦把 Bash 习惯带进 PowerShell,写文件、替换文本、调用外部程序这些看似普通的任务,就会变成连续小翻车。
Misaka-Mikoto-Tech 的 powershell-safe-invocation skill,正好把这些坑收束成了一套简洁规则。它不是教人“多背 PowerShell 语法”,而是告诉 Agent:在 Windows 上执行命令时,先选不容易出错的调用形态。
最大的问题不是 PowerShell 难,而是 Agent 容易把它当 Bash
截图里的几个问题很典型。
第一类是字符串和换行。Markdown 里有反引号,PowerShell 也把反引号当转义符;文档可能是 CRLF,脚本却按 LF 精确匹配;here-string 的起止格式只要缩进或引号不对,就可能根本没有按预期生成文本。
第二类是路径和参数。Windows 路径天然带反斜杠,目录里还常有空格、中括号和中文。把命令拼成一整串再执行,很容易让某个参数被拆开,或者让某段路径被当成转义、通配符、正则或另一个 shell 的语法。
第三类是错误判断。外部程序要看 $LASTEXITCODE,PowerShell cmdlet 则应该依赖终止错误和 $ErrorActionPreference = 'Stop'。如果 Agent 混用这两套模型,就会出现“命令其实没成功,但脚本以为成功了”的状态。
这些都不是大故障,却很消耗注意力。Agent 还会因为“看起来已经执行过命令”而继续在错误假设上修补,越修越绕。
这个 skill 的核心:不要拼大字符串
powershell-safe-invocation 里最重要的一条,是不要把可执行文件、路径、参数和引号拼成一个巨大的命令字符串。
调用原生程序时,应该把每个参数作为数组里的一个独立元素:
|
|
这段看起来比一行命令啰嗦,但它把风险拆开了:路径就是路径,参数就是参数,调用就是调用。Agent 不需要猜这一层引号是给 PowerShell 的、给 cmd 的,还是给目标程序的。
对 PowerShell cmdlet,则更适合用 hashtable splatting:
|
|
这里的关键是 -LiteralPath。真实路径默认按字面量处理,只有确实需要通配符时才使用会展开的路径参数。对 Agent 来说,这比临时判断路径里有没有 [、]、*、? 更稳。
复杂命令别塞进 -Command
很多 Windows 自动化事故,都从这一类命令开始:
|
|
里面一旦出现 JSON、XML、正则、管道、重定向、多行文本、中文路径或 Markdown 反引号,转义层数就会迅速失控。对人来说难读,对 Agent 来说更容易误判。
skill 给出的选择很朴素:复杂一点就写临时 .ps1 文件,再用 pwsh.exe -NoLogo -NoProfile -NonInteractive -File script.ps1 执行。
这条规则特别适合 Agent。因为 Agent 生成脚本文件后,可以分步骤检查脚本内容、运行结果和文件差异。把所有东西塞进一条 -Command,失败时只剩一团很难定位的引号和转义。
powershell.exe 不是 pwsh.exe
另一个容易被忽略的点是版本。
安装 PowerShell 7,并不会让 powershell.exe 自动变成 PowerShell 7。通常:
powershell.exe是 Windows PowerShell 5.1pwsh.exe是 PowerShell 7
两者在原生命令参数传递、编码默认值和一些行为细节上并不完全相同。Agent 如果不知道自己跑在哪个 shell 里,就应该先检查:
|
|
这不是仪式感。很多“我本地可以,Agent 跑不行”的问题,根源就是 shell 版本和参数传递规则不同。
写文件时,编码和换行要当成一等问题
截图里最有意思的地方,是 Agent 反复围绕 Markdown 文件替换失败打转。这里面至少有三个隐含前提:
- 文件实际编码是什么
- 文件换行是 CRLF 还是 LF
- 替换目标是否被 PowerShell 字符串语法改变过
中文文章、Markdown 代码块、front matter、短代码、反引号混在一起时,最好不要用随手拼出来的 PowerShell here-string 写入正文。要么使用明确编码的文件 API,要么让脚本本身只处理 ASCII 控制逻辑,把非 ASCII 正文交给更可靠的写入路径。
对 Agent 工作流来说,还有一条额外经验:写完文件后立刻验证。至少检查 UTF-8 可读、front matter 闭合、Markdown 链接没有断、正文里没有连续问号或明显 mojibake。很多乱码问题越早发现越便宜。
一套更稳的决策顺序
这个 skill 最实用的部分,是给 Agent 一个决策顺序:
- 能用 PowerShell cmdlet,就用 cmdlet。
- 调用外部程序时,用
& $exe @args。 - 命令复杂时,写
.ps1文件并用pwsh.exe -File。 - 必须精确控制进程参数时,用
ProcessStartInfo.ArgumentList。 - 只有需要新窗口、提权、脱离当前进程等特殊行为时,才用
Start-Process。 - 只有确实需要 cmd 语义时,才套
cmd.exe /c。 Invoke-Expression放到最后,且要非常克制。
这套顺序的好处是,它把“能跑”变成“可解释、可检查、可复现”。对人类开发者如此,对 Agent 更是如此。
给 Codex 的现实建议
如果让 Codex 在 Windows 仓库里改文件,我会给它几条硬约束:
- 不要用 Bash 风格的
\"去写 PowerShell。 - 不要把路径和参数拼成一整条字符串。
- 不要用
Start-Process -ArgumentList处理复杂参数。 - 不要用
cmd.exe /c包一层来解决普通调用问题。 - 对真实文件路径优先使用
-LiteralPath。 - 对复杂脚本优先写
.ps1,再用pwsh.exe -File跑。 - 写入中文、日文等非 ASCII 内容后,必须检查编码和乱码标记。
PowerShell 并不是 Agent 的敌人。真正的问题是 Agent 太容易沿用“拼字符串,然后交给 shell 猜”的旧习惯。只要把调用边界拆清楚,PowerShell 反而能提供很强的结构化能力。
所以这场“Codex 大战 PowerShell”的结论不是让 Agent 避开 Windows,而是给它一份战术手册:少赌引号,多用结构;少塞一行命令,多保留可检查的中间状态。
笑点在截图里,经验在规则里。