最近看到一張很有節目效果的截圖: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、正規表示式、管線、重新導向、多行文字、非 ASCII 路徑或 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,而是給它一份戰術手冊:少賭引號,多用結構;少塞一行命令,多保留可檢查的中間狀態。
笑點在截圖裡,經驗在規則裡。