同じフォルダにログが2つあった。片方は自分のスクリプトが書いたもの。もう片方は、そのスクリプトが起動したnodeが書いたもの。
Get-Contentで読むと、自分のは正しく読めて、nodeのは化けた。-Encoding UTF8を付けたら、nodeのが直って、自分のが化けた。
同じGet-Contentなのに、どちらか片方しか正しく読めない。そこで、同じ日本語12文字を14通りの書き方で書いて、2通りの読み方で読み返しました。
この「4つ直って2つ壊れた」はWindows PowerShell 5.1での話です。そのあと、同じWindows 11 / ACP=932のマシンでPowerShell 7.6.5でも同じテストを走らせました。ファイル書き込み側の問題はほぼ消えました。ただし、nodeのUTF-8出力をPowerShellで受ける経路には、別の問題が残りました。
測定環境(まず5.1)
| 項目 | 値 |
|---|---|
| OS | Windows 11 ja-JP (NT 10.0.26100.0) |
| PowerShell | Windows PowerShell 5.1.26100.9168 |
| CurrentCulture | ja-JP |
| system ACP(レジストリ) | 932 |
| Console::OutputEncoding | 932 shift_jis |
| $OutputEncoding | 20127 us-ascii |
| 測定日 | 2026-08-25 |
2026-08-26 訂正。OSを10.0.26200.0と書いていましたが、実測は10.0.26100.0でした。8-25の測定ではOSのビルド番号を出力していませんでした。**測っていない数字が入っていました。**測定器に[System.Environment]::OSVersionの行を足して、8-26に測り直した値に差し替えています。表の他の値は、8-26に同じ2台で測り直して28行すべて一致しました。
この記事は.ps1スクリプト自体の文字コードの話ではありません。スクリプトが書いた「ログやデータのファイル」を読み返すときの話です。
測り方
テスト文字列は日本語12文字。ファイル名が見つからないときに多くのツールが出す、あの一行です。
読み方は2つ。
| 呼び方 | コマンド |
|---|---|
| bare | Get-Content -Raw |
| -Enc UTF8 | Get-Content -Raw -Encoding UTF8 |
書き方を14通り試して、それぞれ両方で読み返し、元の12文字に戻るかを見ました。
結果(Windows PowerShell 5.1)
| # | 書き方 | 先頭バイト | bare | -Enc UTF8 |
|---|---|---|---|---|
| 1 | Out-File既定 | FF FE D5 30 | OK | OK |
| 2 | Out-File -Encoding utf8 | EF BB BF E3 | OK | OK |
| 3 | Out-File -Encoding ascii | 3F 3F 3F 3F | 化ける | 化ける |
| 4 | Set-Content既定 | 83 74 83 40 | OK | 化ける |
| 5 | Set-Content -Encoding UTF8 | EF BB BF E3 | OK | OK |
| 6 | Add-Content既定 | 83 74 83 40 | OK | 化ける |
| 7 | Tee-Object -FilePath | FF FE D5 30 | OK | OK |
| 8 | リダイレクト | FF FE D5 30 | OK | OK |
| 9 | IO.File WriteAllText (UTF8) | E3 83 95 E3 | 化ける | OK |
| 10 | IO.File WriteAllBytes (UTF8) | E3 83 95 E3 | 化ける | OK |
| 11 | pythonをcmd.exeでリダイレクト | E3 83 95 E3 | 化ける | OK |
| 12 | nodeをcmd.exeでリダイレクト | E3 83 95 E3 | 化ける | OK |
| 13 | nodeの出力をPSで受けてOut-File | FF FE 5D 7E | 化ける | 化ける |
| 14 | nodeの出力をPSで受けて-Encoding utf8 | EF BB BF E7 | 化ける | 化ける |
1. どちらの読み方も、単独では全部を読めない
bareで読むと9から12が化けます。これは.NET / python / nodeが書いたファイルです。
一方、-Encoding UTF8を付けて読むと4と6が化けます。これはPowerShell自身が既定で書いたファイルです。
つまり5.1で-Encoding UTF8を付けると、**UTF-8 BOM無しで書かれた9・10・11・12の4ケースが直り、CP932で書かれた4・6の2ケースが壊れます。**これがタイトルの「4つ直って2つ壊れた」です。
「-Encoding UTF8を付ければ直る」は成り立ちません。付けると、別のものが壊れます。
2. 先頭バイトを見れば、どのエンコーディングで書かれたか分かる
| 先頭 | エンコーディング | この表で該当する行 |
|---|---|---|
| FF FE | UTF-16 LE | 1・7・8・13 |
| EF BB BF | UTF-8 BOM付き | 2・5・14 |
| 83 74 | CP932 | 4・6 |
| E3 83 | UTF-8 BOM無し | 9・10・11・12 |
| 3F 3F | ?に潰れている | 3 |
分かるのは「どのエンコーディングで書かれたか」までです。**誰が書いたかは、バイト列だけでは分かりません。**上の表で書き手と対応づけられているのは、14行とも自分で書き分けたからです。
それでも、今回の14ケースでは、読む前に先頭を見れば、どちらの読み方を使えばいいかは決まりました。
今回の14ケースでは、すべて日本語を含んでいるため、この判別ができました。中身がASCIIだけのファイルは、CP932とUTF-8でバイト列が同じなので、先頭を見ても区別できません。区別する必要も無いのですが。
3. 3番だけは復旧できない
3番の-Encoding asciiで書いたファイルは、日本語が全部 ?(0x3F)になっています。
他の行は読み方を変えれば戻りますが、**3番はファイルに元の情報が残っていません。**バックアップから戻すしかありません。
4. 13番と14番が一番たちが悪い
**エンコーディングとしては正しくても、中身がすでに壊れていることがあります。**13番と14番がそれです。
この2つは同じ失敗です。nodeの出力をPowerShellが受けた時点で、文字列がもう別物になっています。違うのは、そのあとの保存の仕方だけ。
| # | 保存の仕方 | 先頭バイト | ファイルとしては |
|---|---|---|---|
| 13 | Out-File(5.1の既定はUTF-16LE) | FF FE 5D 7E | 正しいUTF-16 |
| 14 | Out-File -Encoding utf8 | EF BB BF E7 | 正しいUTF-8(BOM付き) |
中身はどちらも 繝輔ぃ繧、繝ォ縺瑚ヲ九▽縺九j縺セ縺帙s。正しい先頭はE3 83 95 E3(ファ…)のはずでした。これは、元のUTF-8バイト列をCP932として読んだときの文字列と一致します。二重に変換されているので、どちらの読み方でも戻りません。
・3番は開いた瞬間に ?が並ぶので気づけます
・14番はBOMが正しく付いた、UTF-8として完全に妥当なファイルです
・先頭バイトを検査するCIを入れても、14番は合格します
BOMの有無は、中身が正しいことの証明にはなりません。
実務でどうしたか
書くとき
・自分のスクリプトからはSet-Content -Encoding UTF8に統一する
・既定のSet-Content / Add-Contentを使わない
・-Encoding asciiは使わない
外部プロセスの出力を受けるとき
・cmd.exe側でリダイレクトする
・PowerShellで受けてからOut-Fileに流さない(13番・14番になる)
対照:ACPを固定して、PowerShellのバージョンだけを変えた
最初はLinux / PowerShell 7.4.6 / ACP=65001と比べていました。それだとPowerShellのバージョンとACPが同時に違うので、どちらが効いたのか分けられません。
そこで同じWindows機にPowerShell 7.6.5を入れて、ACPを932に固定したまま測り直しました。
| 項目 | 1本目 | 2本目 |
|---|---|---|
| PowerShell | 5.1.26100.9168 | 7.6.5 |
| OS | NT 10.0.26100.0 | 同じ |
| CurrentCulture | ja-JP | 同じ |
| system ACP(レジストリ) | 932 | 932 |
| 実行時刻 | 14:23:58 | 14:24:23 |
ここで一度失敗しました。最初ACPを[System.Text.Encoding]::Default.CodePageで読んでいましたが、これは5.1ではACPを返す一方、PowerShell 7では常に65001を返します(.NET Coreの既定がUTF-8のため)。ACPと書いた行が、ACPではありませんでした。レジストリのHKLM:\SYSTEM\CurrentControlSet\Control\Nls\CodePageのACPを読み直して、両方932であることを確認しています。
PowerShell 7.6.5での結果です。
| # | 書き方 | 先頭バイト | bare | -Enc UTF8 |
|---|---|---|---|---|
| 1 | Out-File既定 | E3 83 95 E3 | OK | OK |
| 2 | Out-File -Encoding utf8 | E3 83 95 E3 | OK | OK |
| 3 | Out-File -Encoding ascii | 3F 3F 3F 3F | 化ける | 化ける |
| 4 | Set-Content既定 | E3 83 95 E3 | OK | OK |
| 5 | Set-Content -Encoding UTF8 | E3 83 95 E3 | OK | OK |
| 6 | Add-Content既定 | E3 83 95 E3 | OK | OK |
| 7 | Tee-Object -FilePath | E3 83 95 E3 | OK | OK |
| 8 | リダイレクト | E3 83 95 E3 | OK | OK |
| 9 | IO.File WriteAllText (UTF8) | E3 83 95 E3 | OK | OK |
| 10 | IO.File WriteAllBytes (UTF8) | E3 83 95 E3 | OK | OK |
| 11 | pythonをcmd.exeでリダイレクト | E3 83 95 E3 | OK | OK |
| 12 | nodeをcmd.exeでリダイレクト | E3 83 95 E3 | OK | OK |
| 13 | nodeの出力をPSで受けてOut-File | E7 B9 9D E8 | 化ける | 化ける |
| 14 | nodeの出力をPSで受けて-Encoding utf8 | E7 B9 9D E8 | 化ける | 化ける |
**14ケース中、11ケースがOK / OKになりました。**残ったのは3番と13・14番です。
書き出しのエンコーディングも全部E3 83 95 E3、つまりBOM無しUTF-8に揃っています。5.1ではUTF-16LE・CP932・BOM付きUTF-8に散らばっていたものが、1つになりました。
ACPは932のまま動かしていません。変わったのはPowerShellのバージョンだけです。
PowerShell 7にしても、13番と14番は直らなかった
7.6.5では書き込み側がほぼUTF-8に揃いました。それでも
・13番node → PowerShell → Out-File
・14番node → PowerShell → Out-File -Encoding utf8
の2つは、どちらも壊れたままでした。3番も同じく壊れたままです(こちらは日本語が ?に潰れていて、ファイルに元の情報が残っていないため)。
環境の欄を並べるとこうなっています。
| 項目 | 5.1 | 7.6.5 |
|---|---|---|
| [Console]::OutputEncoding | 932 shift_jis | 932 shift_jis |
| $OutputEncoding | 20127 us-ascii | 65001 utf-8 |
PowerShell 7にしたことで$OutputEncodingはUTF-8になりましたが、[Console]::OutputEncodingは932のままでした。
そして今回の測定では、nodeがUTF-8で出した日本語をPowerShellで受ける経路だけが、壊れて残りました。
この時点では、この2つが結びついているかどうかを切り分けていませんでした。言えたのは「7.6.5では[Console]::OutputEncodingが932のままだった」と「その環境で13番・14番が壊れた」の2つが同時に観測された、というところまでです。**2026-08-26に切り分けました。**下の「切り分けた結果」を見てください。
この1台で分かったこと
1. PowerShell 7では書き込み側が単純になった
5.1では、今回試した正常系でもUTF-16LE・CP932・UTF-8 BOM付き・UTF-8 BOM無しが混在していました。7.6.5では、今回試した正常系がすべてUTF-8 BOM無しになりました。
そのためGet-Content file.txtでもGet-Content file.txt -Encoding UTF8でも、今回の正常系は読めました。5.1で起きていた「どちらか片方しか読めない」が消えています。
2.しかし、外部プロセスとの境界は別問題
| 経路 | 7.6.5での結果 |
|---|---|
| node → cmd.exe → ファイル | OK |
| node → PowerShell → ファイル | NG |
同じnodeの出力です。PowerShellを経由するかどうかだけが違います。ファイルをUTF-8にするだけでは終わりません。
3. $OutputEncodingと[Console]::OutputEncodingは同じものではない
7.6.5では$OutputEncodingがUTF-8、[Console]::OutputEncodingがShift_JISでした。
ここを混同すると「PowerShell 7はUTF-8だからnodeのUTF-8出力も大丈夫」と考えてしまいます。今回の実測では、そうなりませんでした。
自分の環境で測れるようにした
この表を自分のマシンで再現するスクリプトを書きました。100% ASCIIで書いてあるので、保存の仕方でスクリプト自身が壊れることはありません。日本語のテスト文字列は、UTF-8のバイト配列から組み立てています。
python / nodeが入っていない環境では11から14が測れません。その場合は「測れなかった」と表の下に出るようにしました。黙って省略はしません。
測っていないこと
・上の表はWindows 11 ja-JPの1台で測った値です。他の機種では確認していません
・-Encoding Default / -Encoding OEMは測っていません
・PowerShell 7.6.5以外の7.xは測っていません
・CP949(韓国語)/ CP936(簡体字)での挙動は測っていません
切り分けた結果(2026-08-26 追記)
この記事に「次に測る」と書いていた実験を実行しました。読み方は先に決めてありました。
[Console]::OutputEncodingを変えて、13番・14番だけを測り直しています。同じプロセスの中で、A(そのまま)→ B(UTF-8に変更)→ C(Aに戻す)と3回。
13番・14番は5.1と7.6.5の両方で、BのときだけOKになりました。そしてCで戻したら、また壊れました。
先に決めておいた表の、上の行です。
| 結果 | そこから言えること |
|---|---|
| 13・14がOKになる | 今回の環境では、外部プロセスの標準出力を受け取るときのエンコーディング設定が効いていた |
| 13・14がNGのまま |
[Console]::OutputEncodingが932だったことだけでは説明できない。原因はまだ特定できていない |
同時に分かったことが3つあります。
**$OutputEncodingは関係ありませんでした。**5.1では20127 us-ascii、7.6.5では65001 utf-8と値が違いますが、13番・14番の結果は同じでした。
**PowerShellのバージョンも関係ありませんでした。**13番・14番に関しては5.1も7.6.5も同じ挙動です。上げても直りません。
**この設定を通るのは、PowerShellが受け取る経路だけでした。**11番・12番(cmd.exeのリダイレクト)は[Console]::OutputEncodingが932のままでもOKです。同じnodeの出力で、同じConsole設定で、11番・12番は通り13番・14番は壊れます。
**「PowerShellでは常にこれが原因」ではありません。**Windows 11 ja-JP / ACP=932の1台で切り分けた結果です。
書き方を変えられるなら、cmd.exe側でリダイレクトするのが一番簡単です。
node app.js > out.txt
同じプロセスの中で受け取る必要があるなら、受け取る前にこの1行を入れます。
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
まとめ
Windows 11 ja-JP / ACP=932の同じマシンで、PowerShellを5.1から7.6.5に変えて測りました。
・今回の14ケース中、3番と13・14番を除く11ケースがOK / OKになった
・3番(-Encoding ascii)は、ファイルに元の情報が残っていないので変わらなかった
・nodeのUTF-8出力をPowerShellで受ける13・14番は、7.6.5でも壊れた
「PowerShell 7にすればUTF-8問題は解決する」ではありません。
**ファイルI/O側はかなり改善するが、外部プロセスとの境界は別問題。**これが今回測った結果です。
そして、その境界がなぜ壊れるのかは、**2026-08-26に切り分けました。**今回の環境では[Console]::OutputEncodingでした。上の「切り分けた結果」に書いています。
これとは別の問題
この記事は「書いたファイルを読み返す」ときの話です。
.ps1スクリプトそのものが日本語環境だけで壊れる、という別の問題があります。そちらは測り終わったら別の記事にします。
測ってほしいことがあれば
手元で再現できるものなら、実際に動かして数字で返します。コメントに環境と再現手順を書いてもらえると早いです。結果がどちらに転んでも、そのまま出します。