注意
この記事の解決方法はあまり実用性がなく、PowerShell 7以降(.=NET環境)では使えないため、実務で使うことをオススメしません。
以下の内容について解説がありますので、興味を持った方は是非読んでください。
- Expand-Archiveの仕組み
- ・処理の概要と下記のクラス・定数との関係性が分かります。
- System.IO.Compression.ZipArchiveの処理
- ・レジストリとの関係が分かります(.NET Famework限定)。
- ・ZIP内のファイル名を読み込むタイミングが分かります。
- ・.NET Frameworkと.NETの差異が分かります(主に文字コード)。
概要
PowerShellには、Expand-Archive
というzipを解凍するコマンドがあります。これには文字コードを指定できない欠点があり、ファイル名の文字化けを起こす問題があると言われています。そのため、解決策として以下の方法が利用されます。
- 7-Zipなどの外部ツールを使う
-
[System.IO.Compression]::ZipArchive
などのクラスを使う
ところが、実は他にも解決策があります。
PowerShell 5以前(=.NET Framework環境)では、システムのコードページに依存する性質があります。そのため、レジストリを変更することで間接的に圧縮時・解凍時の文字コードを指定することができます。
この記事では、スクリプト例、Expand-Archiveの仕組み、まとめを紹介します。
スクリプト例
Expand-Archive
は、レジストリのHKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\CodePage
のACP
から影響を受けます。これを弄ることで解凍時の文字コードを指定します。
# 解凍したいZIPのパス
$ZipPath = 'C:/a/a.zip'
# 解凍先
$ExtractPath = 'C:/a'
# 解凍時に使う文字コード
# 例えば、これを'1252'に設定した状態で解凍すると日本語のファイルが文字化けする。
$CharCode = '932'
# レジストリ変更前に、今のコードページを保存する
$before = ([System.Text.Encoding]::Default).WindowsCodePage
# 管理者権限でPowerShellを起動し、レジストリを変更する
Start-Process PowerShell -Verb RunAs -ArgumentList "-command ""Set-ItemProperty -LiteralPath 'HKLM:\SYSTEM\CurrentControlSet\Control\Nls\CodePage' -Name 'ACP' -Value '$CharCode'"
# Zipを展開する
powershell -command "Expand-Archive $ZipPath $ExtractPath"
# 管理者権限でPowerShellを起動し、レジストリを元に戻す
Start-Process powershell -Verb RunAs -ArgumentList "-command ""Set-ItemProperty -LiteralPath 'HKLM:\SYSTEM\CurrentControlSet\Control\Nls\CodePage' -Name 'ACP' -Value '$before'"
実行例
この例では、「7-ZIP」で圧縮したzipファイルに対して、CP1252に変更した状態でExpand-Archiveを実行します。
この結果から、Expand-Archive
がシステムのコードページに依存することが分かります。
Expand-Archive
の仕組み
Expand-Archive
の処理を大雑把に書くと以下のようになっています。内部でSystem.IO.Compression.ZipArchive
を利用しています。さらに、そのクラスがSystem.IO.Compression.ZipArchiveEntry
を利用しており、これの処理がレジストリから影響を受けます。
詳細解説
ユーザがExpand-Archive
を実行すると、ZIPファイルを展開するために文字コードを指定せずにZipArchiveクラスのインスタンスを生成します。そのコンストラクタは、ZIPファイルの読み込みを行います(この時点ではファイルストリームからZIPのバイトデータを取得するだけで、ZIP内にあるファイル名の解析等は行いません)。
Expand-Archive
はZipArchive
のインスタンスを作った後、ZIP内のファイル名一覧を取得します。ファイル名の取得のためにZipArchive.Entries
プロパティへアクセスしますが、そのアクセスでファイルの解析処理が行われます(EnsureCentralDirectoryReadメソッド、ReadCentralDirectoryメソッド)。
ファイル名といった細かい部分の解析処理はZipArchiveEntry
クラスのコンストラクタで行い、ZipArchive
に設定された文字コードを利用します。文字コードが設定されていない場合、ある条件に基づいて文字コードを設定します。なお、その条件は.NET Frameworkと.NETで異なります。
.NET Frameworkの場合
情報元:C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.IO.Compression.dll
をILSpyでデコンパイルしたコード
条件 | 利用する文字コード |
---|---|
ファイル名にUnicode文字が含まれる場合 | System.Text.Encoding.UTF-8 |
そうでない場合 | System.Text.Encoding.GetEncoding(0) |
※System.Text.Encoding.GetEncoding(0)
は、System.Text.Encoding.Default
と同じ値になります。Encoding.Default
はWindows APIのGetACP()
の戻り値が設定されており、その関数がレジストリを読み取ります(https://referencesource.microsoft.com/#mscorlib/system/text/encoding.cs,1402)。
.NETの場合
条件 | 利用する文字コード |
---|---|
ファイル名にUnicode文字が含まれる場合 | System.Text.Encoding.UTF-8 |
そうでない場合 | System.Text.Encoding.UTF-8 |
ZipArchive
の処理内容が文章だけだと分かりづらいので、アクティビティ図で表記します。
Entriesプロパティにアクセスしたときのアクティビティ図
文字コード設定のアクティビティ図
まとめ
情報量が多くごちゃごちゃになってしまったので、まとめを2種類書いて締めます。
1. 直し方
PowerShell 5のExpand-Archive
は、HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\CodePage
のACP
から影響を受けるので、それを変更すれば間接的に文字コードを指定できる。
2. 文字コードがレジストリから影響を受ける原因
項目 | 説明 |
---|---|
コマンド側の原因 |
Expand-Archive がZipArchive を使うときに文字コードを指定しないから |
ライブラリ側の原因 | .NET FrameworkのZipArchiveEntry は、文字コードが指定されていないとEncoding.GetEncoding(0) を利用するから。(.NETでは、文字コード指定がない場合は常にUTF-8を常に使うので影響を受けない) |
参考文献