【PowerShell】空のフォルダを一括削除する方法|入れ子・隠しファイルの罠と安全な削除

【PowerShell】空のフォルダを一括削除する方法|入れ子・隠しファイルの罠と安全な削除 PowerShell

ファイルを移動・整理したあとには、中身が空になったフォルダがあちこちに残りがちです。PowerShellを使えば、こうした空のフォルダをまとめて見つけて削除できます。拡張子ごとの仕分け古いファイルの整理のあとの仕上げにぴったりです。

ただし、単純に書くと2つの落とし穴にはまります。1つは、「空の中に空のフォルダがある」入れ子の状態は、一度処理しただけでは消えないこと。もう1つは、隠しファイルだけが入ったフォルダを、空と誤判定して消してしまうことです。この記事では、実機のPowerShell 5.1で確認しながら、空フォルダを安全に一括削除する方法を整理します。

先に結論

  • 空フォルダはWhere-Object { (Get-ChildItem $_.FullName -Force).Count -eq 0 }で判定します。
  • 判定には-Forceが必須。隠しファイルを数えないと、空でないフォルダを空と誤判定します。
  • 入れ子の空フォルダは、深い階層から順に処理します(パスの深さで降順ソート)。
  • 削除はRemove-Item必ず-WhatIfで確認してから本実行します。
  • これらをまとめた完成版スクリプトをそのまま使えます。

対象フォルダの取得はファイル・フォルダ操作(Get-ChildItem)、条件での絞り込みはWhere-Object・Select-Object・Sort-Objectもあわせて参考になります。

スポンサーリンク

空フォルダを見つける(基本)

あるフォルダが空かどうかは、その中身の数が0かで判定します。Get-ChildItem -Recurse -Directoryでフォルダの一覧を取り、それぞれの中身を数えます。

空フォルダを探す
$dir = "C:\data"

# 中身が0個のフォルダ(=空フォルダ)を探す
Get-ChildItem $dir -Recurse -Directory |
    Where-Object { (Get-ChildItem $_.FullName -Force).Count -eq 0 }

# -Directory … フォルダだけを対象にする
# -Force      … 隠しファイルも数える(後述・重要)

Get-ChildItem -Recurse -Directoryでサブフォルダを含むすべてのフォルダを取得し、Where-Objectで「中身(Get-ChildItem $_.FullName)の数が0」のものに絞り込んでいます。これが空フォルダの基本的な見つけ方です。ただし、このままだと2つの問題があります。次の章でそれぞれ解決します。

-Forceで隠しファイルも数える

1つ目の落とし穴は隠しファイルです。Get-ChildItemは、標準では隠しファイルやシステムファイルを表示しません。そのため、隠しファイルだけが入ったフォルダを「空」と誤判定してしまいます。中身を数えるときは-Forceを付けます。

-Force で隠しファイルも数える
# NG: -Force なし → 隠しファイルを見落とし、空でないフォルダを空と誤判定
(Get-ChildItem $folder).Count

# OK: -Force あり → 隠しファイル・システムファイルも数える
(Get-ChildItem $folder -Force).Count
-Forceなしは隠しファイルを見落とす

実機で確認したところ、隠し属性を付けた.secretファイルだけが入ったフォルダの中身を数えると、-Forceなしでは0(空に見える)-Forceありでは1(正しく中身ありと判定)になりました。-Forceを付けずに空フォルダ削除を実行すると、desktop.ini.gitkeepのような隠しファイル・設定ファイルだけが入ったフォルダを、空とみなして消してしまう恐れがあります。空かどうかの判定では必ず-Forceを付けるようにしてください。大切な設定ファイルを失わないための重要なポイントです。

【最重要】入れ子の空フォルダは深い順に処理する

2つ目の、そして最大の落とし穴が入れ子(ネスト)です。たとえばnestフォルダの中に空のinnerフォルダだけがある場合、nestは「innerという中身がある」ため、最初は空と判定されません。innerを消して初めてnestが空になります。

深い階層から順に処理する
$dir = "C:\data"

# パスの深さ(区切り文字の数)で降順ソート → 深いフォルダから処理
Get-ChildItem $dir -Recurse -Directory |
    Sort-Object { $_.FullName.Split('\').Count } -Descending |
    ForEach-Object {
        if ((Get-ChildItem $_.FullName -Force).Count -eq 0) {
            Remove-Item $_.FullName
        }
    }
浅い順に消すと入れ子が残る

実機で確認したところ、empty1(単純な空)・nest\inner(入れ子の空)という構成で1回だけ空判定すると、検出されるのはempty1innerだけで、nestは検出されませんでした(中にinnerがあるため)。そこで、パスの深さで降順ソートして深い階層から処理すると、まずinnerが削除され、その結果nestが空になって続けて削除され、最終的にnestごと消えることを確認しました。中身のあるwithfileは残ったままです。Sort-Object { $_.FullName.Split('\').Count } -Descendingで「深い順」に並べてから処理するのが、入れ子の空フォルダをきれいに消すコツです。

空フォルダを安全に削除する(-WhatIf)

フォルダの削除も取り消せません。本実行の前に、必ず-WhatIfで「何が消えるか」を確認します。空フォルダとはいえ、判定ミスで必要なフォルダを消さないための安全策です。

まず -WhatIf で確認
$dir = "C:\data"

# まず -WhatIf で削除対象を確認(実際には消さない)
Get-ChildItem $dir -Recurse -Directory |
    Sort-Object { $_.FullName.Split('\').Count } -Descending |
    ForEach-Object {
        if ((Get-ChildItem $_.FullName -Force).Count -eq 0) {
            Remove-Item $_.FullName -WhatIf
        }
    }

# 表示内容を確認したら、-WhatIf を外して本実行

Remove-Item-WhatIfを付けると、削除予定のフォルダが表示されるだけで実際には消えません。表示された一覧に、消したくないフォルダが含まれていないことを確認してから、-WhatIfを外して本実行してください。

完成版スクリプト

ここまでの「-Forceで隠しファイルも数える」「深い順に処理する」「-WhatIfで確認する」をまとめた、そのまま使える完成版です。最初は-WhatIf付きで実行し、問題なければ外します。

空フォルダ一括削除(完成版)
# 対象フォルダを指定
$dir = "C:\data"

# 深い階層から順に、空(隠しファイル含め中身0)のフォルダを削除
Get-ChildItem $dir -Recurse -Directory |
    Sort-Object { $_.FullName.Split('\').Count } -Descending |
    ForEach-Object {
        if ((Get-ChildItem $_.FullName -Force).Count -eq 0) {
            # 最初は -WhatIf を付けて確認、本番では外す
            Remove-Item $_.FullName -WhatIf
        }
    }

このスクリプトは、拡張子ごとの仕分け古いファイルの移動を行ったあと、空になったフォルダを掃除する仕上げに使えます。タスクスケジューラに登録して定期実行する場合は、-WhatIfを外したうえで、対象フォルダを十分にテストしてから設定してください。

主な要素まとめ

空フォルダ削除で使う要素をまとめます。

要素 働き
Get-ChildItem -Recurse -Directory サブフォルダを含む全フォルダを取得
(Get-ChildItem $f -Force).Count -eq 0 空かどうかの判定(隠しファイルも数える)
Sort-Object { ... } -Descending 深い階層から順に処理する
$_.FullName.Split('\').Count パスの深さ(区切り数)
Remove-Item -WhatIf 削除内容を確認(実際には消さない)

よくある失敗

-Forceを付けず隠しファイルを見落とす

desktop.iniなどだけのフォルダを空と誤判定します。判定は-Force付きで数えます。

入れ子の空フォルダが残る

浅い順だと親が残ります。Sort-Objectで深い順に並べてから処理します。

確認せずに削除する

必ずRemove-Item -WhatIfで対象を確認してから本実行します。

-Directoryを付けずファイルまで対象にする

-Directoryでフォルダだけに絞ります。ファイルを誤って消さないためです。

Countの比較を間違える

空の条件は.Count -eq 0です。中身がある条件は-gt 0です。

よくある質問

Q空のフォルダだけをまとめて削除できますか?
Aできます。Get-ChildItem $dir -Recurse -Directory | Where-Object { (Get-ChildItem $_.FullName -Force).Count -eq 0 }で空フォルダを見つけ、Remove-Itemで削除します。入れ子の空フォルダもきれいに消すには、記事の完成版のように深い階層から順に処理してください。
Qなぜ-Forceが必要なのですか?
AGet-ChildItemは標準で隠しファイルやシステムファイルを表示しないため、-Forceを付けないと、desktop.ini.gitkeepのような隠しファイルだけのフォルダを「空」と誤判定してしまいます。空かどうかの判定では必ず-Forceを付けて、隠しファイルも数えてください。
Q入れ子になった空フォルダが消えません。
A中に空のサブフォルダがあるフォルダは、最初は「中身がある」と判定されるためです。Sort-Object { $_.FullName.Split('\\').Count } -Descendingで深い階層から順に処理すると、まず奥のフォルダが消え、その結果空になった親フォルダも続けて削除できます。
Q削除する前に対象を確認したいです。
ARemove-Item-WhatIfを付けて実行すると、削除予定のフォルダが表示されるだけで実際には消えません。一覧に消したくないフォルダが無いことを確認してから、-WhatIfを外して本実行してください。
Qファイルが入ったフォルダまで消えませんか?
A消えません。(Get-ChildItem $_.FullName -Force).Count -eq 0という条件で、中身が0個のフォルダだけを対象にしています。ファイルが1つでも入っていれば削除されません。実機でも、ファイルのあるフォルダは残ることを確認しています。

まとめ

  • 空判定は(Get-ChildItem $f -Force).Count -eq 0-Forceで隠しファイルも数えます
  • 入れ子の空フォルダは深い順に処理Sort-Object ... -Descending)します。
  • 削除はRemove-Item必ず-WhatIfで確認してから本実行します。
  • -Directoryでフォルダだけに絞り、ファイルを誤って消さないようにします。
  • 完成版スクリプトは、ファイル整理のあとの仕上げにそのまま使えます。

空フォルダの一括削除は単純に見えて、「隠しファイル」と「入れ子」という2つの罠があります。-Forceで正しく判定し、深い順に処理する——この2点を押さえれば、必要なフォルダを誤って消すことなく、安全に掃除できます。ファイル整理の仕上げに、ぜひ完成版スクリプトを活用してください。