0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【PowerShell 5以前】Expand-Archiveの文字コードをレジストリの変更で指定する方法

Posted at

注意

この記事の解決方法はあまり実用性がなく、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\CodePageACPから影響を受けます。これを弄ることで解凍時の文字コードを指定します。

# 解凍したい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を実行します。

7-ZIPで圧縮
image.png

圧縮したファイル(a.zip)
image.png

中身
image.png

解凍(スクリプトを実行)
image.png

レジストリの状態
image.png

解凍した結果
image.png

この結果から、Expand-Archiveがシステムのコードページに依存することが分かります。

Expand-Archiveの仕組み

Expand-Archiveの処理を大雑把に書くと以下のようになっています。内部でSystem.IO.Compression.ZipArchiveを利用しています。さらに、そのクラスがSystem.IO.Compression.ZipArchiveEntryを利用しており、これの処理がレジストリから影響を受けます。

詳細解説

ユーザがExpand-Archiveを実行すると、ZIPファイルを展開するために文字コードを指定せずにZipArchiveクラスのインスタンスを生成します。そのコンストラクタは、ZIPファイルの読み込みを行います(この時点ではファイルストリームからZIPのバイトデータを取得するだけで、ZIP内にあるファイル名の解析等は行いません)。

Expand-ArchiveZipArchiveのインスタンスを作った後、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の場合

情報元https://github.com/dotnet/runtime/blob/main/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs#L369-L379

条件 利用する文字コード
ファイル名に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\CodePageACPから影響を受けるので、それを変更すれば間接的に文字コードを指定できる。

2. 文字コードがレジストリから影響を受ける原因

項目 説明
コマンド側の原因 Expand-ArchiveZipArchiveを使うときに文字コードを指定しないから
ライブラリ側の原因 .NET FrameworkのZipArchiveEntryは、文字コードが指定されていないとEncoding.GetEncoding(0)を利用するから。
(.NETでは、文字コード指定がない場合は常にUTF-8を常に使うので影響を受けない)

参考文献

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?