﻿# 如何強迫 Azure CLI 一定要以 UTF-8 輸出以避免亂碼問題

今天又解決了一個卡了我幾個月的問題，這次是 [Azure CLI](https://learn.microsoft.com/zh-tw/cli/azure/what-is-azure-cli?WT.mc_id=DT-MVP-4015686) 的輸出編碼問題，只要在一種特殊的條件組合下，就會遇到的問題，我一直都知道怎樣避開這個問題，但卻一直無法理解原因。今天這篇文章我就來說說這個特殊的狀況，幫助大家未來可以順利避開這個問題。

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/8b43b60f972824e105a9-488040b8-bc19-46fa-a2d0-a2ff1238d66c.webp)

### 認識非 Unicode 程式的語言 (Language for non-Unicode programs)

我手邊有許多 PowerShell 自動化腳本，尤其是 Microsoft 365、Azure 或 Azure DevOps 這類微軟服務，基本上對 PowerShell 的支援度都很高，所以要實現自動化相對容易許多。但是還是有不少自動化的程式是用「批次檔」寫的 (`*.bat` 或 `*.cmd`)，當我在 PowerShell 腳本中使用 Azure CLI 時，總是會遇到一個問題，就是 Azure CLI 遇到中文總是「有機會」會輸出亂碼，這讓我很困擾，因為我並不知道確切發生問題的原因，這種不知道問題根源狀況就有點困擾著我。

其實作業系統的**預設文字編碼**一直都是一個很棘手的問題，尤其是在 Windows 作業系統上，面對老舊的地區設定 (legacy system locale)，像是台灣數十年來常用的 `Big5` 編碼，一直到現在都還擺脫不掉，連我都不敢直接改變系統的預設編碼，因為這樣會影響到許多應用程式的運作，幾乎可以說一定會出事。

我所說的「老舊的地區設定」(legacy system locale)，講的其實就是「控制台」中「地區」設定的「系統管理」的「非 Unicode 程式的語言」設定！

![「控制台」中「地區」設定的「系統管理」的「非 Unicode 程式的語言」設定](https://stwillblogassets.blob.core.windows.net/files/images/external/github.com/a8a8498144ee83a0ba0d-9e972a11-3e71-4fd8-8634-09a7ea534f30.jpg)

基本上我都是強烈建議不要隨變更動這裡的設定，因為出意外的機率非常高：

![目前的系統地區設定](https://stwillblogassets.blob.core.windows.net/files/images/external/github.com/a8202a4dd76a3b2cd69f-e04916a6-3df1-4042-beae-4b4aac1c461d.jpg)

我只能跟你說，這裡的 **繁體中文 (台灣)** 選項，所代表的就是 `CP950` 這個**頁碼**(Code Page)，也就是 `Big5` 編碼，代表著目前作業系統遇到無法識別的文字時，或是遇到「**不支援 Unicode 的應用程式**」時，預設就會用這個編碼來處理文字，其中也包含了輸入、輸出與管道(Pipe)傳輸的文字編碼。

你可能會想說：「**還有應用程式不支援 Unicode 嗎？**」你如果有這個疑惑，那就建議你先勾選上圖的 `Beta: 使用 Unicode UTF-8 提供全球語言支援` 選項，我相信你看到「亂碼」的機率應該會高出許多！😅

### 哪些應用程式比較容易出問題？

如果系統地區設定不能改，那是不是意味著所有應用程式都要改用 `Big5` 或 `CP950` 編碼了呢？也不是，確實有許多應用程式已經都支援 Unicode 了，所以這些應用程式在處理 Unicode 的時候，是完全沒問題的，甚至能自動判斷編碼，不太容易出錯。但是，若有一些早期的文字檔案，包含原始碼、INI、... 等等各種純文字格式的檔案，如果當時使用了 `Big5` 編碼，這些檔案就不一定能夠順利的判斷其編碼，這時就會依賴作業系統所設定的「預設編碼」。如果你預設編碼設定錯誤，或是選擇 UTF-8 的話，很容易就會在文字編輯器中顯示「亂碼」，或是看到應用程式的選單出現無法閱讀的亂碼文字，有時候甚至會導致原始碼無法編譯等狀況。這些問題當時是出現在文字檔內容包含「中文」的情況，如果只有英文是沒問題的。

另一個常見的狀況，就是本文的重點所在：**命令列程式** (**Command Line Programs**)！

**命令列程式**是一種非常特殊的應用程式，他沒有傳統的 UI 介面，一個**命令列程式**的輸出可以傳給另一個**命令列程式**當作輸入，這種特性讓**命令列程式**成為一個非常強大的工具，尤其是在自動化工作上。但是，**命令列程式**的**輸出編碼**如果跟另一個**命令列程式**的**輸入編碼**不一致，基本上接收到的文字就會是亂碼。但如果大家都用 Unicode 就皆大歡喜了，可惜有許多**命令列程式**並沒有特別針對 Unicode 進行處理，當程式不知道該如何處理編碼時，預設就會遵照「作業系統」的地區設定，但也有許多**命令列程式**只會回應 Unicode 編碼。不過，大家的預設值如果不一樣，這就是一個超大問題了！🔥

本文要提到的這套 Azure CLI 命令列程式，預設會遵照「作業系統」的地區設定來選用預設編碼，而這樣的設定其實在大部分的情況下都是沒問題的。

另一個非常關鍵的程式，就是我們常用的**命令列執行環境**，首先，我們來看看 Windows 有哪些命令列執行環境：

1.  **Command Prompt** (`cmd.exe`) (**命令提示字元**)
    
    預設會遵照「作業系統」的地區設定來選用預設編碼，但你可以透過 `chcp` 命令來變更編碼。
    
    例如: `chcp 65001` 就可以將編碼切換到 UTF-8 編碼。
    
2.  **Windows PowerShell** (`powershell.exe`)
    
    預設會遵照「作業系統」的地區設定來選用預設編碼，但你可以透過設定 `[Console]::OutputEncoding` 來變更輸出編碼。
    
3.  **PowerShell** (`pwsh.exe`)
    
    預設會遵照「作業系統」的地區設定來選用預設編碼，但你可以透過設定 `[Console]::OutputEncoding` 來變更輸出編碼。
    
4.  **Windows Terminal** (`wt.exe`)
    
    預設僅支援 `UTF-8` 編碼，不支援變更編碼。
    
    你可以把 Windows Terminal 當成一種「終端機」，然後在裡面執行 `cmd.exe`、`powershell.exe` 或 `pwsh.exe`，所以只要裡面執行的程式最終輸出的編碼為 `UTF-8` 的話，在 Windows Terminal 是可以正常顯示文字的。
    
    不過你要知道，無論是 `cmd.exe`、`powershell.exe` 或 `pwsh.exe` 預設都是透過「作業系統」的地區設定來選用預設編碼，因此若你在 Windows Terminal 中執行 "非 PowerShell" 的程式時，就有機會遇到亂碼的狀況。不過，大部分的情況下，這樣的狀況都是可以避免的，只有在特殊的條件下容易發生亂碼的狀況。
    

### 問題重現

我想在 PowerShell 中執行 Azure CLI 時，你可以用以下命令找出執行所在位置：

```powershell
(Get-Command -Name az.cmd).Source
```

結果為：

```
C:\Program Files\Microsoft SDKs\Azure\CLI2\wbin\az.cmd
```

所以 `az.cmd` 其實他是一個**批次檔**(Batch file)，預設所有的批次檔都是透過 `cmd.exe` 來執行的，所以當你在 PowerShell 中執行 `az.cmd` 時，其實是透過 `cmd.exe` 來執行的。

我手邊剛好有個命令想要查出 Azure DevOps Service 上面某個使用者可以存取哪些專案：

```powershell
az devops user show --user $UserPrincipalName -o json | jq ".projectEntitlements[].projectRef.name" -r
```

你可以看到我這段程式讓整個狀況變的有點棘手：

1.  終端機採用 [Windows Terminal](https://apps.microsoft.com/detail/9n0dx20hk701?hl=zh-TW&gl=TW)
    
    僅支援 `UTF-8` 編碼，**不支援變更編碼**。
    
2.  啟動 [PowerShell](https://github.com/PowerShell/PowerShell) v7.4.1
    
    預設採用 `UTF-8` 編碼，可以變更編碼。
    
3.  執行 [Azure CLI](https://learn.microsoft.com/zh-tw/cli/azure/what-is-azure-cli?WT.mc_id=DT-MVP-4015686) 命令列工具
    
    預設採用 `Big5` 編碼 (系統地區設定)，可以變更編碼。
    
4.  執行 [jq](https://jqlang.github.io/jq/) 命令列工具
    
    預設採用 `Big5` 編碼 (系統地區設定)，可以變更編碼。
    

> 關於 Windows 預設編碼的問題，可以閱讀 [分享幾個在 Windows 與 Linux 常見的編碼問題與解決方案](/post/2021/08/05/Character-Encoding-Problems-for-Windows-and-Linux) 文章，補充一些重要知識。

基本上，跨程式的資料傳輸，就是一種不斷在編碼轉換的過程！

先說結果，上述命令在不同的環境下有不同結果：

1.  在 Windows PowerShell (`powershell.exe`) 執行
    
    預設設定下執行的結果正常
    
2.  在 Windows PowerShell (`pwsh.exe`) 執行
    
    預設設定下執行的結果正常
    
3.  在 Windows Terminal + Windows PowerShell (`powershell.exe`) 執行
    
    預設設定下執行的結果正常
    
4.  在 Windows Terminal + PowerShell (`pwsh.exe`) 執行
    
    預設設定下執行的結果正常
    

對，全部都正常！

但是，當我在 Windows Terminal + PowerShell (`pwsh.exe`) 執行時，同時載入 [oh-my-posh](https://ohmyposh.dev/) 之後，問題就出現了：

```powershell
Import-Module oh-my-posh

az devops user show --user $UserPrincipalName -o json | jq ".projectEntitlements[].projectRef.name" -r
```

錯誤訊息如下，基本上是 JSON 無法解析的錯誤：

```
jq: parse error: Invalid escape at line 63, column 32
```

重點是，我重新執行一次 `az devops user show --user $UserPrincipalName -o json` 命令，其實看不到任何錯誤，所有**中文字**都有正常顯示，是串接 `jq` 的時候才出錯的。所以我每次遇到「編碼」的問題，都十分刁鑽，非常難偵錯！😒

不過，這次我嘗試用這個命令執行：

```powershell
$(az devops user show --user $UserPrincipalName -o json)
```

這次終於看見錯誤了：

![Azure CLI 的亂碼問題](https://stwillblogassets.blob.core.windows.net/files/images/external/github.com/ce8249ceb42e1d15229e-2cf35877-a024-44a1-85c8-78ce16047cbf.jpg)

這裡的 `$()` 其實跟 `|` 有異曲同工之妙，因為都需要先讓 `az` 命令輸出，先做好編碼轉換後，才傳給下一個命令或程式處理，所以這種寫法會讓我看到亂碼！

出問題的當下，我的 `[Console]::OutputEncoding` 輸出如下：

```
Preamble          :
BodyName          : utf-8
EncodingName      : Unicode (UTF-8)
HeaderName        : utf-8
WebName           : utf-8
WindowsCodePage   : 1200
IsBrowserDisplay  : True
IsBrowserSave     : True
IsMailNewsDisplay : True
IsMailNewsSave    : True
IsSingleByte      : False
EncoderFallback   : System.Text.EncoderReplacementFallback
DecoderFallback   : System.Text.DecoderReplacementFallback
IsReadOnly        : False
CodePage          : 65001
```

我的環境被強制改成 `UTF-8` 編碼了，但這是有原因了，你看看我強制把 `[Console]::OutputEncoding` 改回 `big5` 的結果：

```powershell
[Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding('big5')
```

![oh-my-posh](https://stwillblogassets.blob.core.windows.net/files/images/external/github.com/1b6debd1e6a7da12a32c-0f3b12fd-509c-43ea-a4ec-ffb01df2881e.jpg)

沒錯，我的 PowerShell 環境中那個[華麗的提示字元](/post/2021/11/24/PowerShell-prompt-with-Oh-My-Posh-and-Windows-Terminal)，就是需要 `UTF-8` 編碼才能正常顯示特殊字型，但 `pwsh.exe` 強制使用 `UTF-8` 編碼之後，我的 Azure CLI 就會開始輸出亂碼了。(詳見: [如何打造一個華麗又實用的 PowerShell 命令輸入環境](/post/2021/11/24/PowerShell-prompt-with-Oh-My-Posh-and-Windows-Terminal))

雖然華麗的提示字元變成亂碼了，但是我的命令執行成功了，這就是目前可行的**應變措施** (Workaround)！

### 解決方案

太棒了，問題都可以穩定重現，也找到了問題的根源，接下來解決問題就不是太困難了！(*才怪*)

我們總結一下，我現在的狀況是這樣：

1.  我想用 [Windows Terminal](https://apps.microsoft.com/detail/9n0dx20hk701?hl=zh-TW&gl=TW) (`wt.exe`)
2.  我想用 [PowerShell](https://github.com/PowerShell/PowerShell) (而非傳統的 [Windows PowerShell 5.1](https://learn.microsoft.com/zh-tw/powershell/scripting/windows-powershell/overview?view=powershell-7.4&WT.mc_id=DT-MVP-4015686))
3.  我想用 [Oh My Posh](https://ohmyposh.dev/) 提供的華麗提示字元介面，所以一定要用 `UTF-8` 編碼

在我的刁鑽要求下，只有一種解決方案了：

1.  強迫 [Azure CLI](https://learn.microsoft.com/zh-tw/cli/azure/what-is-azure-cli?WT.mc_id=DT-MVP-4015686) 一定要以 `UTF-8` 編碼輸出即可！

可惜 Azure CLI 並沒有提供變更輸出編碼的選項，所以我把[問題](https://stackoverflow.com/q/78008939/910074)發到 StackOverflow 上面，希望有人能夠幫我解答，結果還真的給我遇到了個大神，他徹底解決了我的難題。看解答: [https://stackoverflow.com/a/78023334/910074](https://stackoverflow.com/a/78023334/910074)

簡單說，你只要在 `$PROFILE` 定義一個 `az` 的函式如下，未來執行 `az` 就可以強制以 `UTF-8` 編碼輸出了！

```powershell
# Custom az CLI entry point that requests UTF-8 output and decodes it as such.
# Based on the az.cmd batch file that comes with v2.57.0
# (but this batch file's content rarely changes).
function az {

  # Determine the full az.cmd path and derive the location of the
  # bundled Python executable.
  $azCliPath = (Get-Command -ErrorAction Ignore az.cmd).Path
  if (-not $azCliPath) { throw "'az.cmd' cannot be located via the system's path." }
  $bundledPythonExe = Convert-Path -ErrorAction Ignore -LiteralPath "$azCliPath\..\..\python.exe"
  if (-not $bundledPythonExe) { throw "Failed to load Python executable." }

  # Prepare the environment and temporarily instruct
  # PowerShell to decode external-program output as UTF-8.
  $prevValue = $env:AZ_INSTALLER; $env:AZ_INSTALLER = 'MSI'
  $prevEncoding = [Console]::OutputEncoding; [Console]::OutputEncoding = [Text.UTF8Encoding]::new()

  # Call the actual CLI via the bundled Python, requesting UTF-8 output (-X utf8),
  # and passing all arguments as well as the output through.
  & $bundledPythonExe -X utf8 -IBm azure.cli @args

  # Restore previous settings.
  [Console]::OutputEncoding = $prevEncoding
  $env:AZ_INSTALLER = $prevValue

}
```

這段 PowerShell 函式的亮點就在於，他把 `C:\Program Files\Microsoft SDKs\Azure\CLI2\wbin\az.cmd` 批次檔的內容，改用 PowerShell 來執行。而 Azure CLI 是基於 Python 開發的，所以只要在命令列啟動的地方加上 `-X utf8` 參數，就可以強制以 `UTF-8` 編碼輸出！

我最後也有實驗出，其實 Azure CLI 完全以 `UTF-8` 輸出，完全不會有問題，所有程式都可以自動的識別 `UTF-8` 編碼。所以我的**最終結果**就是，直接去修改 `C:\Program Files\Microsoft SDKs\Azure\CLI2\wbin\az.cmd` 批次檔，手動加上 `-X utf8` 參數，如此一來日後 Azure CLI 就可以強制以 `UTF-8` 編碼輸出了！

![az.cmd](https://stwillblogassets.blob.core.windows.net/files/images/external/github.com/8480f3e207309782e89b-c3c4a5e5-9442-458f-b33a-4530126d686e.jpg)

### 相關連結

-   相關文章
    -   [分享幾個在 Windows 與 Linux 常見的編碼問題與解決方案](/post/2021/08/05/Character-Encoding-Problems-for-Windows-and-Linux)
    -   [如何打造一個華麗又實用的 PowerShell 命令輸入環境](/post/2021/11/24/PowerShell-prompt-with-Oh-My-Posh-and-Windows-Terminal)
    -   [Oh My Posh](https://ohmyposh.dev/)
-   StackOverflow
    -   [Azure CLI and PowerShell 7.3 OutputEncoding issue - Stack Overflow](https://stackoverflow.com/questions/78008939/azure-cli-and-powershell-7-3-outputencoding-issue/78023334#78023334)
-   GitHub
    -   [Consider use UTF-8 by default for Azure CLI · Issue #28497 · Azure/azure-cli](https://github.com/Azure/azure-cli/issues/28497)
