🧊

Expand-Archive(もしくはExtractToDirectory)で文字化けする場合の対応方法〔PowerShell x 解凍〕

に公開
2

先日、ZIPファイルの解凍処理を行うPowerShellスクリプトを作成しました。そのスクリプトでテストした結果、なぜか解凍後のファイル名が文字化けしてしまう現象を確認。
調べた結果、解凍対象のZIPファイルがShift JISエンコードで圧縮されたファイルとなり、PowerShellの標準的なコマンドレットやメソッドを使うと文字コードの不一致により文字化けしていることがわかりました。

それらをふまえた対応方法を紹介します。

この記事のターゲット

  • PowerShellユーザーの方
  • 日本語などマルチバイトの名前を含むZIPファイルを正常に解凍したい方

なぜ、PowerShellの解凍で文字化けしてしまうのか

PowerShellで標準搭載されている機能は下記の2点です。

  • Expand-Archive: PowerShell標準搭載のコマンドレット

  • ExtractToDirectory: .NET(ドットネット)のフレームワークから呼び出すメソッド

$zipPath = "解凍するZIPファイルのパス"
$outputPath = "解凍後の出力先のパス"

# PowerShell標準搭載のコマンドレット
Expand-Archive -Path $zipPath -DestinationPath $outputPath

# .NETから呼び出すメソッド
Add-Type -AssemblyName System.IO.Compression.FileSystem
([System.IO.Compression.ZipFile]::ExtractToDirectory($zipPath, $outputPath))

これら機能は、各バージョンのPowerShellに標準搭載。これらのエンコードは「UTF-8」が採用されています。
一方、Windows 10(Win11では未検証)におけるGUI操作の古い規格の圧縮ツール(例: 右クリック → 送る → ZIP圧縮)では、
OSの文字コードを参照し「Shift-JIS」が採用。

このように圧縮した際のエンコードと解凍する時のエンコードがチグハグに。
結果、Shift-JISで圧縮したデータをUTF-8で解凍してたため、マルチバイト(日本語)を含むフォルダー名やファイル名が文字化けしていた事がわかりました。

なお、発生した不具合は名前のみで 解凍処理そのものは正常終了 しています。

つぎは、実際にどのような流れで文字化けするか確認してみましょう。

実際に文字化けを確認してみる

まず、フォルダー内に日本語を含む名前のファイルを準備。同じ場所に エンコード Shift-JISで圧縮 したファイルを配置しておきます。

日本語を含む名前のファイルを準備
PS D:\Desktop\Test_7Zip4Powershell> tree /F .\CompressedFile-Containing-MultibyteCharacters
フォルダー パスの一覧:  ボリューム ボリューム
ボリューム シリアル番号は 0000XXXX XXXX:XXXX です
D:\DESKTOP\TEST_7ZIP4POWERSHELL\COMPRESSEDFILE-CONTAINING-MULTIBYTECHARACTERS
    Only-Alphabet.txt
    マルチバイト(日本語)を含む.txt

サブフォルダーは存在しません

PS D:\Desktop\Test_7Zip4Powershell>

下記のとおり、PowerShellコマンドレット「Expand-Archive」で解凍すると解凍そのものはエラーなく終えますが、
解凍先のファイル名が文字化けしてしまいました。

実際に実行した結果
PS D:\Desktop\Test_7Zip4Powershell> $zipPath = ".\CompressedFile-Containing-MultibyteCharacters.zip"
PS D:\Desktop\Test_7Zip4Powershell> $outputPath = ".\"
PS D:\Desktop\Test_7Zip4Powershell>
PS D:\Desktop\Test_7Zip4Powershell> Expand-Archive -Path $zipPath -DestinationPath $outputPath
PS D:\Desktop\Test_7Zip4Powershell>
PS D:\Desktop\Test_7Zip4Powershell> tree /F .\CompressedFile-Containing-MultibyteCharacters
フォルダー パスの一覧:  ボリューム ボリューム
ボリューム シリアル番号は 0000XXXX XXXX:XXXX です
D:\DESKTOP\TEST_7ZIP4POWERSHELL\COMPRESSEDFILE-CONTAINING-MULTIBYTECHARACTERS
    Only-Alphabet.txt
    �}���`�o�C�g�i���{��j���܂�.txt

サブフォルダーは存在しません

PS D:\Desktop\Test_7Zip4Powershell>Powershell>

.NETにおける System.IO.Compression.ZipFile のメソッド「ExtractToDirectory」で解凍しても、
Expand-Archiveと同様に解凍はエラーなく終えるが、解凍先は文字化けという結果に。

PS D:\Desktop\Test_7Zip4Powershell> [System.IO.Compression.ZipFile]::ExtractToDirectory($zipPath, $outputPath)
PS D:\Desktop\Test_7Zip4Powershell>
PS D:\Desktop\Test_7Zip4Powershell> tree /F .\CompressedFile-Containing-MultibyteCharacters
フォルダー パスの一覧:  ボリューム ボリューム
ボリューム シリアル番号は 0000XXXX XXXX:XXXX です
D:\DESKTOP\TEST_7ZIP4POWERSHELL\COMPRESSEDFILE-CONTAINING-MULTIBYTECHARACTERS
    Only-Alphabet.txt
    �}���`�o�C�g�i���{��j���܂�.txt

サブフォルダーは存在しません

PS D:\Desktop\Test_7Zip4Powershell>
補足情報: エンコードがShift-JISの圧縮処理をコードで行い、文字化けを検証してみました。
参照元と出力先を設定
$CompressFrom = "D:\Desktop\Test_7Zip4Powershell\TargetFolder"
$CompressTo = "D:\Desktop\Test_7Zip4Powershell\TargetFolder.zip"
$ExtractFrom = "D:\Desktop\Test_7Zip4Powershell\TargetFolder.zip"
$ExtractTo = "D:\Desktop\Test_7Zip4Powershell\Extract"

ツリーコマンドで圧縮するデータをチェック。

CompressFromの圧縮対象フォルダーの中身を確認
PS D:\Desktop\Test_7Zip4Powershell> tree /f .\TargetFolder
フォルダー パスの一覧: ボリューム ボリューム
ボリューム シリアル番号は 0000XXXX XXXX:XXXX です
D:\DESKTOP\TEST_7ZIP4POWERSHELL\TARGETFOLDER
 Only-Alphabet.txt
 マルチバイト(日本語)のみ.txt
サブフォルダーは存在しません
PS D:\Desktop\Test_7Zip4Powershell>

下記のコマンドによりエンコードをShift-JISで圧縮する。

エンコードがShift-JISの圧縮をコマンドで実行
# ZIPファイルを生成
New-Item -ItemType File -Path $CompressTo
# ZIPファイルにデータを格納
$shell = New-Object -ComObject Shell.Application
$zip = $shell.NameSpace($CompressTo)
$source = $shell.NameSpace($CompressFrom)
$source.Items() | ForEach-Object {  $zip.CopyHere($_) }

つぎに下記2通りの解凍。PowerShell標準機能の解凍であるため、エンコードがUTF-8。

エンコードがUTF-8のコマンドで解凍
# エンコードUTF-8の解凍コマンド その1
Expand-Archive $ExtractFrom $ExtractTo

# エンコードUTF-8の解凍コマンド その2
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::ExtractToDirectory($ExtractFrom , $ExtractTo)

想定通り、どちらのコマンドでも文字化けが発生。

文字化けが発生
PS D:\Desktop\Test_7Zip4Powershell> tree /F .\Extract\
フォルダー パスの一覧:  ボリューム ボリューム
ボリューム シリアル番号は 0000XXXX XXXX:XXXX です
D:\DESKTOP\TEST_7ZIP4POWERSHELL\EXTRACT
    Only-Alphabet.txt
    �}���`�o�C�g�i���{��j�̂�.txt

サブフォルダーは存在しません

PS D:\Desktop\Test_7Zip4Powershell>

今度は圧縮のエンコードをShift-JISではなく、UTF-8でエンコードするPowerShellのコマンド・メソッドで対応してみる。

エンコードがUTF-8のコマンドで圧縮
# エンコードUTF-8の圧縮コマンド その1
Compress-Archive $CompressFrom $CompressTo

# エンコードUTF-8の圧縮コマンド その2
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::CreateFromDirectory($CompressFrom , $CompressTo)

前述した2通りの解凍コマンドを使うと、文字化けせずにすべての解凍処理が正常終了したことを確認。

圧縮・解凍の両方ともエンコードがUTF-8のコマンドを実行すると文字化けしない
# エンコードがUTF-8で圧縮・解凍その1
PS D:\Desktop\Test_7Zip4Powershell> Compress-Archive $CompressFrom $CompressTo
PS D:\Desktop\Test_7Zip4Powershell>
PS D:\Desktop\Test_7Zip4Powershell> Expand-Archive $ExtractFrom $ExtractTo
PS D:\Desktop\Test_7Zip4Powershell>
PS D:\Desktop\Test_7Zip4Powershell> tree /f .\Extract
フォルダー パスの一覧:  ボリューム ボリューム
ボリューム シリアル番号は 0000XXXX XXXX:XXXX です
D:\DESKTOP\TEST_7ZIP4POWERSHELL\EXTRACT
└─TargetFolder
        Only-Alphabet.txt
        マルチバイト(日本語)のみ.txt

PS D:\Desktop\Test_7Zip4Powershell>

# エンコードがUTF-8で圧縮・解凍その2
PS D:\Desktop\Test_7Zip4Powershell> Add-Type -AssemblyName System.IO.Compression.FileSystem
PS D:\Desktop\Test_7Zip4Powershell>
PS D:\Desktop\Test_7Zip4Powershell> [System.IO.Compression.ZipFile]::CreateFromDirectory($CompressFrom , $CompressTo)
PS D:\Desktop\Test_7Zip4Powershell>
PS D:\Desktop\Test_7Zip4Powershell> [System.IO.Compression.ZipFile]::ExtractToDirectory($ExtractFrom , $ExtractTo)
PS D:\Desktop\Test_7Zip4Powershell>
PS D:\Desktop\Test_7Zip4Powershell> tree /f .\Extract
フォルダー パスの一覧:  ボリューム ボリューム
ボリューム シリアル番号は 0000XXXX XXXX:XXXX です
D:\DESKTOP\TEST_7ZIP4POWERSHELL\EXTRACT
    Only-Alphabet.txt
    マルチバイト(日本語)のみ.txt

サブフォルダーは存在しません

PS D:\Desktop\Test_7Zip4Powershell>

以上の結果から圧縮時のエンコードがUTF-8以外(今回はShift-JIS)のデータに対し、エンコードUTF-8の解凍処理を行ったことで文字化けしたことが、はっきりとわかりました。

解決方法(回避方法)

方法1. 圧縮方法をエンコードUTF-8に変える

まずシンプルで手っ取り早い解決方法は、「圧縮する際のエンコードもUTF-8にする」です。

PowerShellのコマンドレットだと Compress-Archive "引数1" "引数2"
.NETのフレームワークを使用する方法だと [System.IO.Compression.ZipFile]::CreateFromDirectory("引数1", "引数2")

コマンドに固執する必要がなければ、7-ZipなどGUIで操作可能なアーカイバーを使うといいでしょう。

ただ、外部プログラムや外部システムで連携されたZIPファイルをそのまま使用しなければいけないケースの場合だと異なる対応方法が必要です。

方法2. 解凍のエンコードを「Shift-JIS」で指定する(ExtractToDirectoryを使用)

要件としてエンコードShift-JISで圧縮されたZIPファイルを解凍する必要がある場合、GUI操作で問題なければ 7-Zip などの自動でエンコードを判定する機能があるアーカイバーを使う方法ですが、
バッチやスクリプトで対応しなければならない場合は、ExtractToDirectoryで対応可能です。

ExtractToDirectoryには、解凍対象と解凍先以外に3つ目の引数で解凍時のエンコードを指定できます。

https://learn.microsoft.com/ja-jp/dotnet/api/system.io.compression.zipfile.extracttodirectory#system-io-compression-zipfile-extracttodirectory(system-string-system-string-system-text-encoding)

ExtractToDirectoryで解凍時のエンコードを指定する方法
$encodingShiftjis = [System.Text.Encoding]::GetEncoding("shift_jis")
[System.IO.Compression.ZipFile]::ExtractToDirectory($zipPath, $extractPath, $encodingShiftjis)
実際に実行した結果
# エンコードを指定しないとUTF-8で解凍するため、文字化けする
PS D:\Desktop\Test_7Zip4Powershell> [System.IO.Compression.ZipFile]::ExtractToDirectory($zipPath, $extractPath)
PS D:\Desktop\Test_7Zip4Powershell>
PS D:\Desktop\Test_7Zip4Powershell> tree /f .\Extract
フォルダー パスの一覧:  ボリューム ボリューム
ボリューム シリアル番号は 0000XXXX XXXX:XXXX です
D:\DESKTOP\TEST_7ZIP4POWERSHELL\EXTRACT
└─TargetFolder
        Only-Alphabet.txt
        �}���`�o�C�g�i���{��j�̂�.txt

PS D:\Desktop\Test_7Zip4P

# エンコードを「Shift-JIS」で指定すると文字化けしない
PS D:\Desktop\Test_7Zip4Powershell> $encoding = [System.Text.Encoding]::GetEncoding("shift_jis")
>> [System.IO.Compression.ZipFile]::ExtractToDirectory($zipPath, $extractPath, $encoding)
PS D:\Desktop\Test_7Zip4Powershell>
PS D:\Desktop\Test_7Zip4Powershell> tree /f .\Extract
フォルダー パスの一覧:  ボリューム ボリューム
ボリューム シリアル番号は 0000XXXX XXXX:XXXX です
D:\DESKTOP\TEST_7ZIP4POWERSHELL\EXTRACT
└─TargetFolder
        Only-Alphabet.txt
        マルチバイト(日本語)のみ.txt

PS D:\Desktop\Test_7Zip4Powershell>

ただ、複数の企業から外部連携されるなどの要因により圧縮時のエンコードを取り決められていないケースだと、
また違う対応方法の検討が必要です。
※ 脱線しますが、ここで例にあげている課題のあるべき姿は、外部要件で圧縮ファイルのエンコードを取り決めるべきです。

方法3. 自動で解凍時のエンコードを判定可能な「7Zip4Powershell」

GUI操作が問題なければ、7-Zipなどのアーカイバーで対応可能。

圧縮時のエンコードが不明な圧縮ファイルが解凍対象の場合は、PowerShellで標準搭載されているコマンドだと文字化けを回避できません。
そのようなケースを調べた結果、「7Zip4Powershell」というモジュールに行き着きました。

https://github.com/thoemmi/7Zip4Powershell

現在、精力的には更新されていないようですが、「7Zip4Powershell」はオープンソースのモジュールで一定の信頼性はありそう。
このモジュールを使用することで圧縮ファイルのエンコードを自動で判定し、文字化けしないように解凍してくれます。

「7Zip4Powershell」モジュールのインストール

下記のコマンドで「7Zip4Powershell」モジュールをインストールできます。初回はインストールして良いか確認メッセージが表示されるので、
問題なければ YA を選択してインストールを続行してください。

7Zip4Powershellモジュールをインストール
PS C:\Users\XXXX> Install-Module -Name 7Zip4Powershell

Untrusted repository
You are installing the modules from an untrusted repository. If you trust this repository, change its
InstallationPolicy value by running the Set-PSRepository cmdlet. Are you sure you want to install the modules from
'PSGallery'?
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "N"): a
PS C:\Users\Administrator>

# 対象のモジュールがインストールされたこと①
PS C:\Users\Administrator> Get-InstalledModule

Version              Name                                Repository           Description
-------              ----                                ----------           -----------
2.7.0                7Zip4Powershell                     PSGallery            Powershell module for creating and extra…

PS C:\Users\XXXX>

# 対象のモジュールがインストールされたこと②
PS C:\Users\XXXX> Get-Package -name 7Zip4Powershell

Name                           Version          Source                           ProviderName
----                           -------          ------                           ------------
7Zip4Powershell                2.7.0            https://www.powershellgallery.c… PowerShellGet

PS C:\Users\XXXX>

「Expand-7Zip」で解凍すると文字化けしない

下記のとおり、引数でエンコードを指定しなくても自動的に判定し正常に解凍してくれます。

「Expand-7Zip」で解凍
PS D:\Desktop\Test_7Zip4Powershell> Expand-7Zip -ArchiveFileName $zipPath -TargetPath $outputPath
PS D:\Desktop\Test_7Zip4Powershell> 
PS D:\Desktop\Test_7Zip4Powershell> tree /F .\CompressedFile-Containing-MultibyteCharacters
フォルダー パスの一覧:  ボリューム ボリューム
ボリューム シリアル番号は 0000XXXX XXXX:XXXX です
D:\DESKTOP\TEST_7ZIP4POWERSHELL\COMPRESSEDFILE-CONTAINING-MULTIBYTECHARACTERS
    Only-Alphabet.txt
    マルチバイト(日本語)を含む.txt

サブフォルダーは存在しません

PS D:\Desktop\Test_7Zip4Powershell>

まとめ

  • 文字化けしてしまう原因は、圧縮時と解凍時のエンコードが不一致となり、文字化けしていた!
  • シンプルな解決方法は、圧縮・解凍の両方ともエンコードがUTF-8のツール・コマンドを使用する方法
  • 上記にあるUTF8の対応方法が難しい場合は、ExtractToDirector の引数で解凍する際のエンコードを指定することで回避・解決!
  • 対象ファイルのエンコードが不明な場合に関しては、圧縮データのエンコードを自動的に判定し解凍してくれるモジュール 7Zip4Powershell の「Expand-7Zip」を使用することで回避・解決できた!

強力な機能をもっているPowerShellですが、とくに言語まわりの細かい不具合が多い印象です。
おそらく「Shift-JISが関係のない英語OSを使用」したり、「フォルダー名やファイル名にマルチバイト(日本語)を使用しない」とすれば問題ないのでしょうが、ロケーションが日本だとその対応方法は難しそう。

いっそのこと、互換を捨てたグローバル用のWindows OSというのも需要がありそうな気がしますが、どうなんでしょうね。

参考文献

https://github.com/thoemmi/7Zip4Powershell

https://qiita.com/LightSilver7/items/a2532cc6e33757bb9edb

関連記事

https://haretokidoki-blog.com/pasocon_powershell-startup/
https://zenn.dev/haretokidoki/articles/7e6924ff0cc960
https://zenn.dev/haretokidoki/articles/fb6830f9155de5

GitHubで編集を提案

Discussion