入れ子構造になった.zipファイルをPowerShellで解消する

PCでよく作業をする方であれば、一度は入れ子になった.zipファイルを見かけたことがあるのではないでしょうか。解凍すると [フォルダA] / [フォルダA] / [ファイル] …みたいになっているアレです。

あれは単一のフォルダを指定して圧縮を行うと発生します。ツール側が選択したファイル数に関わらずフォルダにまとめてしまうからですね。(そのためフォルダではなく中身を全指定することで回避可能)

さて、単に解凍して使う分には気にならない入れ子構造ですが、複数の.zipファイルをまとめて解凍したり、あるいは保管しておく際には、この入れ子の有無が混在することに微妙な歯がゆさを感じることもあります。

そんな塵のような思いが積み重なって山となり、先日とうとう.zipファイルの入れ子を一括で取り除く処理が欲しくなってしまいました。

という訳で今回は、「入れ子構造の.zipをそうでない.zipに変換するPowershellスクリプト」の記事となります。なお、私はその日初めてPowerShellを触りましたので、スクリプトの品質について全く保障できません。完全自己責任でどうぞ。

動作環境

  • OS: Windows11
  • Windows PowerShell: 5.1.22621.3958
  • PowerShell: 7.4.4

上記の環境で動作確認を行いました。なお、初期状態ではPowerShellスクリプトの実行は制限されており、実行ポリシーを変更する必要があるようです。「PowerShell 実行ポリシー」などで検索すれば情報が出てきますが、セキュリティに関わる部分ですので慎重にご検討ください。

スクリプトと実行手順

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# backupフォルダが無ければ作成
$backupFolderPath = (Split-Path -LiteralPath $MyInvocation.MyCommand.Path) + "\backup\"
if (!(Test-Path -LiteralPath $backupFolderPath)) {
    New-Item -LiteralPath $backupFolderPath -ItemType Directory
}

# このファイルと同じディレクトリにある.zipを対象
$zips = Get-ChildItem ((Split-Path $MyInvocation.MyCommand.Path) + "\*.zip") -File
foreach ($zip in $zips) {
    $targetFolderPath = $zip.DirectoryName + "\" + $zip.BaseName    
    echo ("Target: " + $targetFolderPath)
    
    # 解凍
    Expand-Archive -LiteralPath $($zip.FullName) -DestinationPath $targetFolderPath
        
    # フォルダが1つのみ存在する場合に実行
    $folders = Get-ChildItem -LiteralPath $targetFolderPath -Directory
    $files = Get-ChildItem -LiteralPath $targetFolderPath -File
    if ($folders.Count -eq 1 -and $files.Count -eq 0) {
    
        # 元の.zipをbackupフォルダに移動
        Move-Item -LiteralPath $zip.FullName ($backupFolderPath + $zip.Name)

        # 圧縮(標準のCompress-Archiveは角括弧と2GB以上のファイルに対応できない)
        [IO.Compression.ZipFile]::CreateFromDirectory($folders[0].FullName, $zip.FullName)

        echo ("圧縮が完了しました。")
    }
    else { 
        echo ("処理条件に当てはまらないためスキップされました。")
    }

    # 解凍したフォルダを削除
    Remove-Item -LiteralPath $targetFolderPath -Recurse
}

上記のスクリプトを.ps1形式で保存します。このスクリプトは自身と同じフォルダ内の.zipに対して実行されますので、処理したいファイルと同じフォルダに配置してください。

スクリプトを右クリックし、メニューから「PowerShellで実行」で動作します。(Windows11なら「その他のオプションを表示」を経由する必要あり)

スクリプト実行時に「backup」フォルダが作成され、内容が変更される場合は元の.zipファイルがそこに移動します。処理を行う対象は「ルート直下のファイルが0個、フォルダが1個しかない」.zipファイルです。

注意点

注意点として、このスクリプトやスクリプトまでのパスに角括弧… [] これですね。これが含まれていると実行できません。.zipファイルやその中身に角括弧が含まれている分には問題ありませんので、必要に応じて角括弧を含まないフォルダに移して実行してください。

また、入れ子ならフォルダ名が異なっていても処理されるため、意図した構造まで削除される場合があります。例えばゲームにおける「Mods」フォルダや、エディタにおける「Plugins」フォルダがそれにあたりますね。

あとさらに、backupフォルダに同名ファイルが存在するとエラーが出ます。適宜お掃除してください。

まとめ

以上、「入れ子構造になった.zipファイルをPowerShellで解消する」でした。

やってることのショボさの割に注意点が多くて面倒かと思いますが、自分用に動けばOKの精神で書いたものなのでご容赦ください。

Powershell…ちゃんと勉強したら便利に使えそうな反面、普通にファイル名に使える角括弧くんに対してよわよわだったりとクセもありそうでした。ただ触っていて楽しかったので、次に一括処理の何かを作る時もPowershellで組んでみようと思います。

余談ですが、私は特に実行ポリシーの変更を行わずともスクリプトを実行できたため、不思議に思って確認したら実行ポリシーがUnrestricted(無制限)になっていました。いつの間に触っていたんだろう…怖…