AWS
PowerShell 5.1でAWS CLIの出力が化ける・cp932で落ちる—出す側と読む側の文字コードをそろえ、本物のaws.exeで試す
日本語WindowsのPowerShell 5.1からAWS CLI v2を呼ぶと、cp932に無い文字で終了コード255になったり、落ちずに化けたJSONが保存されたりします。片方だけ直して終わらないための判断基準と、偽のエンドポイントを使ったテスト、AIエージェントに渡す指示文。
日本語のWindowsでPowerShell 5.1からAWS CLIを呼び、結果を変数に受けてJSONで保存するツールを作ると、ある日 'cp932' codec can't encode character というエラーで終了コード255になります。慌てて環境変数やコンソールの設定を足すと、今度はエラーが消えたのに、化けた中身がそのまま保存されます。どちらも、手元のテストが全部通ったあと、本番の初回で出やすい不具合です。テストデータが日本語だけだと、cp932で表せるので通ってしまうからです。読み終わると、文字コードの設定を2か所そろえた呼び出し方と、本物の aws.exe で化けを再現するテスト、AIエージェントにツールを書かせるときに渡す指示がそろった状態になります。
この記事でわかること
AWS CLIの出力がPowerShellの変数に届くまでに文字コードが2回決まることと、片方だけ直すと何が起きるかを、手元で再現した結果つきで示します。あわせて、終了コードと文字化けの確かめ方、偽のエンドポイントを使ったテストの組み方を、失敗ごとに合格条件つきでまとめます。
前提
2026年10月時点で、日本語版Windows 11、Windows PowerShell 5.1、AWS CLI v2.36.44で確かめました。PowerShell 7では試していません。元になったのは、Windows PCでPowerShellの集計ツールを作り、AWS CLIでCloudWatchやS3を調べていた現場の失敗です。表の結果は、同じ失敗を筆者の手元で偽のエンドポイントに向けて再現したものです。AWS CLIの環境変数は変わることがあるので、AWSの公式ドキュメントを優先してください。
失敗1 文字コードの設定を1か所だけ変えて直したつもりになる
AWS CLIで取った結果をPowerShellの変数に受けていたところ、'cp932' codec can't encode character '\u2a2f' で落ちた場面です。U+2A2Fは「⨯」という記号で、cp932には入っていません。
AWS CLI v2はPythonで書かれていて、Pythonの実行環境ごと同梱した形で配布されています。標準出力に書くとき、何も指定しなければWindowsのロケールの文字コード、日本語版ならcp932で文字をバイトに直します。cp932に無い文字が来た時点で、そこで落ちます。PowerShellの側は、受け取ったバイトを [Console]::OutputEncoding の文字コードで文字に戻します。こちらの既定もcp932です。つまり、AWS CLIが書き出すときと、PowerShellが読み取るときの2か所で文字コードが決まり、両方がそろわないと正しく読めません。
悪い例は、検索で見つけた設定を1つずつ足して、エラーが消えたところで直ったと判断することです。失敗4で紹介する偽サーバーに aws dynamodb list-tables を向け、日本語のテーブル名「表示テスト」「能力」と「⨯」を含む名前を返させて、設定ごとに試した結果が次の表です。
| 設定 | 終了コード | 変数に入ったもの |
|---|---|---|
| 何もしない | 255 | 途中までのJSONとエラー文 |
PYTHONIOENCODING=utf-8 | 255 | 何もしないときと同じ |
[Console]::OutputEncoding だけUTF-8 | 255 | 同じエラーに加え、途中までの日本語も化ける |
AWS_CLI_OUTPUT_ENCODING=UTF-8 だけ | 0 | 落ちないが日本語が化ける |
PYTHONUTF8=1 だけ | 0 | 落ちないが日本語が化ける |
AWS_CLI_OUTPUT_ENCODING=UTF-8 と [Console]::OutputEncoding のUTF-8 | 0 | 正しく読める |
PYTHONIOENCODING が効かないのは、同梱の形で固めたPythonが、文字コードを指定するPythonの環境変数に従わないからです。PYTHONUTF8 はAWS CLI自身が読み取って従来どおり効くようにしていますが、公式の設定は AWS_CLI_OUTPUT_ENCODING(v2.24.14で追加)です。PYTHONUTF8 を設定すると、同じPCで動くほかのPythonツールまで動きが変わるので、AWS CLIには AWS_CLI_OUTPUT_ENCODING を使います。元の現場では chcp 65001 も試しましたが、これも直りませんでした。
良い例では、呼び出しの前に書き出し側と読み取り側の両方をUTF-8にし、終わったら読み取り側を元に戻します。AWS CLIを呼ぶ箇所が散らばると設定の漏れが出るので、呼び出しは1つの関数にまとめ、本番もテストもこの関数を通します。
function Invoke-AwsJson {
param([string[]]$AwsArgs)
$env:AWS_CLI_OUTPUT_ENCODING = 'UTF-8'
$prev = [Console]::OutputEncoding
[Console]::OutputEncoding = [Text.UTF8Encoding]::new($false)
try {
$lines = & aws @AwsArgs --output json
if ($LASTEXITCODE -ne 0) { throw "aws が終了コード $LASTEXITCODE で失敗しました" }
$json = $lines -join "`n"
[pscustomobject]@{ Json = $json; Data = ($json | ConvertFrom-Json) }
} finally {
[Console]::OutputEncoding = $prev
}
}
$r = Invoke-AwsJson @('logs', 'describe-log-groups')
[IO.File]::WriteAllText($path, $r.Json, [Text.UTF8Encoding]::new($false))
file:// で渡すJSONやクエリのファイルは、さらに別の AWS_CLI_FILE_ENCODING で読み込みの文字コードが決まります。出力の設定を直しても、入力のファイルは直りません。
合格条件は、AWS CLIを呼ぶ箇所すべてで、AWS_CLI_OUTPUT_ENCODING と [Console]::OutputEncoding の両方がUTF-8に設定されていることです。
失敗2 終了コードを見ずに、受け取った出力をそのまま保存する
失敗1の表で「何もしない」ときに変数に入ったのは、{ "TableNames": [ から始まり1件目の名前まで書いたところで切れたJSONでした。AWS CLIは、落ちる前に書けたところまでを出力しています。終了コードを見ないツールは、この切れたJSONを結果として保存します。
呼び出しに 2>&1 を付けていると、さらに見分けにくくなります。エラー文がデータの行に混ざるうえ、PowerShell 5.1では標準エラーの行がエラーの記録として届くので、文字列にすると System.Management.Automation.RemoteException という行まで入りました。
良い例では、出力を使う前に $LASTEXITCODE が0であることを確かめ、ConvertFrom-Json で読めることを確かめてから保存します。データを受ける呼び出しには 2>&1 を付けません。保存するときは、PowerShell 5.1の Out-File や > はUTF-16で、-Encoding UTF8 はBOM付きで書くので、ほかのツールが読むJSONは失敗1の例のように [IO.File]::WriteAllText とBOMなしのUTF-8で書きます。
合格条件は、終了コードが0でない、またはJSONとして読めないときに、何も保存せずにツールが止まることです。
失敗3 化けていないかを置換文字の件数だけで確かめる
元の現場では、落ちなかった回でも保存したファイルが化けていたことがあり、保存後に置換文字(U+FFFD、読めないバイトの代わりに入る「�」)の件数を数え、0件なら合格としていました。
この確かめ方では見逃しがあります。失敗1の表の「AWS_CLI_OUTPUT_ENCODING だけ」のとき、「表示テスト」は別の漢字の並びに化けていましたが、置換文字は0件でした。UTF-8のバイトをcp932として読むと、多くはたまたま別の漢字や記号として読めてしまうからです。
逆に、読み取り側だけをUTF-8にして、日本語だけ(cp932で表せる文字だけ)を返させると、ConvertFrom-Json が「認識できないエスケープ シーケンスです」(英語環境では Unrecognized escape)で失敗しました。「表」や「能」はcp932で2バイト目が \ と同じ値になるので、UTF-8として読むとJSONの中に \ が現れるためです。このエラーが出たら、文字コードが食い違っている印として読みます。
良い例では、テストで既知の文字列を返させ、受け取った値がそれと一致するかで判定します。置換文字の件数は、本番の保存後に足す補助の確認にとどめます。
合格条件は、文字化けの確認が既知の文字列との一致で行われていて、置換文字の件数だけを根拠にしていないことです。
失敗4 aws を偽の関数に置き換えてテストする
出荷前のテストは全部通ったのに、本番の初回で失敗1のエラーが出た例です。テストでは aws をPowerShellの関数で置き換え、決まったJSONを返させていました。この関数はPowerShellの中で文字列を返すだけなので、AWS CLIがバイトに直す処理も、PowerShellがそれを読み取る処理も通りません。出力が化けたり落ちたりするのは、この2か所です。
さらに、テストデータが日本語だけだと、本物の aws.exe を使っても通ってしまいます。何も設定しない状態で「表示テスト」と「能力」だけを返させると、終了コード0で正しく読めました。cp932に無い文字が来て初めて落ちます。
良い例では、本物の aws.exe を使い、通信先だけを --endpoint-url で手元の偽サーバーに向けます。偽サーバーの応答には、日本語と、cp932に無い記号を入れます。次はPowerShell 5.1だけで動く偽サーバーです。応答の形はDynamoDBのテーブル一覧(aws dynamodb list-tables)に合わせてあり、別のコマンドで試すときは、そのコマンドが受け取るJSONの形に変えます。日本語を含む .ps1 は、PowerShell 5.1が正しく読めるようにBOM付きのUTF-8で保存します。
# fake-aws.ps1 偽のAWSエンドポイント。日本語とcp932に無い記号(U+2A2F)を返す
$body = '{"TableNames":["表示テスト","x' + [char]0x2A2F + 'y","能力"]}'
$bytes = [Text.Encoding]::UTF8.GetBytes($body)
$listener = New-Object System.Net.HttpListener
$listener.Prefixes.Add('http://127.0.0.1:18765/')
$listener.Start()
while ($listener.IsListening) {
$ctx = $listener.GetContext()
$ctx.Response.ContentType = 'application/x-amz-json-1.0'
$ctx.Response.OutputStream.Write($bytes, 0, $bytes.Length)
$ctx.Response.Close()
}
このスクリプトを別のウィンドウで動かしたまま、テスト側ではダミーの認証情報を入れ、失敗1の Invoke-AwsJson に --endpoint-url だけを足して呼びます。
$env:AWS_ACCESS_KEY_ID = 'dummy'
$env:AWS_SECRET_ACCESS_KEY = 'dummy'
$env:AWS_DEFAULT_REGION = 'ap-northeast-1'
$r = Invoke-AwsJson @('dynamodb', 'list-tables', '--endpoint-url', 'http://127.0.0.1:18765')
$expected = @('表示テスト', ('x' + [char]0x2A2F + 'y'), '能力')
$diff = Compare-Object $expected @($r.Data.TableNames)
if ($diff) { throw '受け取った値が期待値と一致しません' }
合格条件は、テストが本物の aws.exe を通り、cp932に無い文字を含む応答で、受け取った値が期待値と一致することを確かめていることです。
作業前にAIへ渡す入力
人間向けの長い手順は書きません。次はすべて、作業前にAIに読ませる文と貼る文です。
AI向けの永続ルール(rulesやAGENTS.mdに書く)
Windows PowerShell 5.1からAWS CLI v2を呼ぶスクリプトでは、次を守る。
- 出力の文字コードは、AWS CLIが書き出す側とPowerShellが読み取る側の2か所で決まる。呼び出しの前に $env:AWS_CLI_OUTPUT_ENCODING = 'UTF-8' と [Console]::OutputEncoding = [Text.UTF8Encoding]::new($false) の両方を設定し、終わったら [Console]::OutputEncoding を元に戻す。片方だけ変えてエラーが消えても、直ったと判断しない。
- PYTHONIOENCODING と chcp 65001 では直らない。PYTHONUTF8 ではなく AWS_CLI_OUTPUT_ENCODING を使う。
- file:// で渡すファイルの読み込みは AWS_CLI_FILE_ENCODING で決まる。出力の設定とは別に扱う。
- 出力を使う前に、$LASTEXITCODE が0であることと ConvertFrom-Json で読めることを確かめる。どちらかが崩れたら保存せずに止める。データを受ける呼び出しに 2>&1 を付けない。
- JSONの保存は [IO.File]::WriteAllText と UTF8Encoding($false) でBOMなしにする。Out-File と > は使わない。
- 文字化けの確認を置換文字(U+FFFD)の件数だけで済ませない。テストでは既知の文字列との一致で判定する。
- テストで aws をPowerShellの関数やモックに置き換えない。本物の aws.exe を使い、--endpoint-url で手元の偽サーバーに向ける。偽サーバーの応答には日本語と、cp932に無い記号(例: U+2A2F)を入れる。
- 日本語を含む .ps1 はBOM付きのUTF-8で保存する。
AI向けの開始時に貼る文
これからPowerShell 5.1でAWS CLIを呼ぶツールを(作る/直す)。
書き始める前に、AWS CLIを呼ぶ箇所をすべて挙げ、それぞれについて出力の文字コード(AWS_CLI_OUTPUT_ENCODING と [Console]::OutputEncoding)、終了コードの確認、保存時の文字コードがどうなっているかを表で示せ。
テストは本物の aws.exe と --endpoint-url の偽サーバーで行い、日本語とcp932に無い記号を返させて、受け取った値が期待値と一致したかを結果として見せよ。
文字化けやエラーの原因を書くときは、再現した手順と結果を添えよ。再現していないなら推測と書け。
人間向け
なし。上をAIに渡すだけでよい。