The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

如何完美解決 PowerShell 無法正確解析命令列參數的問題

這十幾年來,在 Windows 使用 PowerShell 呼叫一些命令列工具,一直都存在一個惱人的問題,那就是傳入 *.exe 的參數會自動將雙引號(")過濾掉,導致程式無法正確執行。這個問題終於在 PowerShell v7.2.0 得到了解決,這篇文章我就來說說這個問題的來龍去脈。

重現問題

我如果在 Bash 底下執行 curl 命令,要發出一個 POST 命令的話,我們會這樣執行:

curl -X POST https://localhost:7255/signin -H 'Content-Type: application/json' --data '{"Username": "will", "Password": "123"}'

因為 Windows 也有內建 cURL 命令列工具,因此我們可以在 PowerShell 底下執行完全一樣的命令,不過我們的 --data 傳入的參數,將不會是 {"Username": "will", "Password": "123"},而是 {Username: will, Password: 123},因此你的 curl 命令永遠無法執行成功!光是這一點,不知道浪費了多少人的時間啊!

我們可以透過 UnxUtils 工具組中的 echo.exe 來測試這個問題:

  1. 先透過 Chocolatey 下載安裝 UnxUtils 套件

    choco install unxutils -y
    
  2. 然後執行以下命令

    echo.exe '{"Username": "will", "Password": "123"}'
    

    此時你就會看到以下輸出:

    {Username: will, Password: 123}
    

對一個不知道此地雷的人來說,你應該永遠想不到該怎樣執行才正確!正確的執行命令如下:

echo.exe '{""""Username"""": """"will"""", """"Password"""": """"123""""}'

輸出如下:

{"Username": "will", "Password": "123"}

是不是非常意外?你要把 1 個 " 改寫成 4 個 " 才能正確的傳入 echo.exe 執行!

另一種可行的方案是以下這種格式,不過一樣不漂亮、不直覺:

echo.exe '{\"Username\": \"will\", \"Password\": \"123\"}'

輸出如下:

{"Username": "will", "Password": "123"}

解決方案

從 PowerShell 7.2.0 開始,有一個實驗性的 PSNativeCommandArgumentPassing 特性 ,這個特性需要特別啟用才會生效!

以下是測試該特性的步驟:

  1. 啟用 PSNativeCommandArgumentPassing 特性

    Enable-ExperimentalFeature PSNativeCommandArgumentPassing
    
  2. 請務必重開 PowerShell 才能讓此特性生效

  3. 在參數列處理雙引號的部分就可以正常的解析了

    echo.exe '{"Username": "will", "Password": "123"}'
    

    輸出如下:

    {"Username": "will", "Password": "123"}
    

注意相容性問題

啟用 PSNativeCommandArgumentPassing 特性之後,非常有可能會讓一些早期寫好的 PowerShell 在執行命令時發生錯誤,因為他會改變你 PowerShell 處理命令列參數的行為! 🔥

這個時候你肯定要認識這個 $PSNativeCommandArgumentPassing 變數,他可以讓你執行 PowerShell 的時候彈性的切換不同的命令列參數處理方式。不過 $PSNativeCommandArgumentPassing 變數講起來有點小複雜,基本上 $PSNativeCommandArgumentPassing 變數可以有三種可能的值:

  1. 'Legacy'

    $PSNativeCommandArgumentPassing = 'Legacy'
    

    這個設定會讓 PowerShell 回復到沒有啟用該特性的時候,確保早期的 PowerShell 程式一定可以正確執行!

  2. 'Standard'

    這個是 Linux / macOS 作業系統下的預設值。

    $PSNativeCommandArgumentPassing = 'Standard'
    

    這個設定會讓 PowerShell 採用新的特性來解析命令列參數!

  3. 'Windows'

    這個是 Windows 作業系統下的預設值。

    $PSNativeCommandArgumentPassing = 'Windows'
    

    設定成這個值會讓你在執行以下命令時,採用 Legacy 模式解析命令列參數,任何其他執行檔則會使用 Standard 模式來解析,以確保最大的相容性!

    • cmd.exe
    • cscript.exe
    • wscript.exe
    • 任何副檔名為 .bat 的批次檔
    • 任何副檔名為 .cmd 的批次檔
    • 任何副檔名為 .vbs 的 VBScript 腳本

由於 PowerShell 新版是跨平台的命令列工具,該特性在不同的作業系統,預設值也是不同的!

  • Windows

    $PSNativeCommandArgumentPassing 的預設值為 'Windows'

  • non-Windows

    $PSNativeCommandArgumentPassing 的預設值為 'Standard'

相關連結