WSL2の仮想ディスクファイル(VHDX)の不要な領域を解放するスクリプト

「え?消したはずなのに…」WSLでのファイル削除が示すディスク容量の謎

開発作業でWSLを使用している方なら、こんな経験はありませんか?大容量のファイルを削除したにもかかわらず、Windowsのディスク容量がほとんど変わらない。一見すると不可解なこの現象には、実はWSLの仕組みに深く関係した理由が隠されています。今回は、多くの開発者が遭遇するこの「消えない容量」の謎に迫り、その解決方法までご紹介します。そして便利なスクリプトを準備しました。

なぜWSLでファイルを削除しても容量が増えないのか

仮想ディスクの特性

WSL2は仮想ディスクファイル(VHDX)を使用してLinux環境を管理しています。このVHDXファイルは、一度拡張されると自動的には縮小されない仕様となっています。そのため、内部でファイルを削除しても、VHDXファイル自体のサイズは変わりません。

二重の容量管理システム

WSL2では、二層の容量管理が行われています:

  1. WSL内部(Linux)での容量管理
    • ファイル削除時に内部の空き容量は正しく増加
    • Linuxからは容量が解放されたように見える
  2. ホストシステム(Windows)での容量管理
    • VHDXファイルのサイズが固定的
    • 内部でファイルを削除してもVHDXファイルは同じサイズを維持

動的縮小機能の制限

WSL2の仮想ディスクには、以下の制限があります:

  • 未使用領域の自動解放機能がない
  • ディスクの自動縮小機能が実装されていない
  • 手動での最適化作業が必要

これらの要因により、WSL内でファイルを削除しても、ホストシステムから見た使用容量には即座に反映されないという現象が発生します。

実は以前、この問題の解決手順を徹底的に解説した記事を公開したのですが、ある出来事があって興味深い気づきを得ました。いくら丁寧に手順を書いても、技術的な説明だけでは伝わりにくい場合があるんですね。具体的には、ある知人から「記事を読んでも意味がよく分からない」という声をいただき、結局その場で直接作業のサポートをすることになったのです。 技術記事を書く難しさを実感した良い経験でした。詳しい手順については、以下の記事でご確認いただけます:

WSL、VirtualBox内でファイルを削除しても容量が増えない件
WSLやVirtualBoxでファイルを削除してもディスク容量が増えない理由を解説。ストレージの仕組みや解決策を詳しく説明し、効率的なディスク管理をサポートします。

こうした経験から、「技術に詳しくない方でも簡単に実行できる方法が必要だ」と考え、全自動で処理を行うスクリプトを開発しました。このスクリプトを使えば、複雑な手順を意識することなく、誰でも簡単にWSLの容量最適化が行えます。 それでは、実際のスクリプトをご紹介します。

それでは、実際のスクリプトとその動作について解説していきます。

このスクリプトは、WSLの仮想ディスク(ext4.vhdx)を自動的に見つけ出し、不要な領域を解放する処理を自動化したものです。実行には以下の2つの前提条件があります:

  1. WSLを停止していること(wsl –shutdown で停止可能)
  2. PowerShellを管理者権限で実行すること

もしくは、WSL内でシャットダウンしておくことです。

「wsl –shutdown」は、WSL全体を正しく終了させるための推奨コマンドです。
一方、WSL内で「sudo shutdown now」を実行する方法は、Linuxの通常のシャットダウン手順ですが、WSLの仮想環境特有の挙動があるため、必ずしも望ましい方法とは言えません。しかし、「wsl –shutdown」はWSL上の全てのプロセスを即座に終了させるため、実行中のプログラムがある場合は予期せぬ中断やデータ損失のリスクがあります。
そのため、重要な処理が動いているときは、まず各プログラムを正常に終了させるか、必要なデータを保存した上でシャットダウンするのが望ましいです。

まとめると以下のようになります。

wsl --shutdownについて:

  • WSL全体を完全にシャットダウンする公式のコマンドです
  • WSLのインスタンス全体を終了させ、使用していたリソースを解放します
  • すべてのWSLプロセスを即座に終了させるため、確かにデータ損失のリスクがあります

sudo shutdown nowについて:

  • 通常のLinuxシステムで使用される標準的なシャットダウンコマンドです
  • WSL環境では、完全なシャットダウンができない場合があります
  • WSLの仮想環境の特性により、期待通りに動作しないことがあります

推奨される安全なシャットダウン手順:

  1. 実行中のプログラムを適切に終了する
  2. 未保存のデータがある場合は保存する
  3. データベースなどのサービスを正常に停止する
  4. その後でwsl --shutdownを実行する

この手順を踏むことで、データの整合性を保ちながら安全にWSLを終了することができます。

スクリプトの主な処理の流れは以下の通りです:

VHDファイルの検出

まず、WSLの仮想ディスクファイル(ext4.vhdx)を自動的に探し出します。Ubuntuパッケージの場合、このファイルは通常、ローカルアプリケーションデータフォルダ内に存在します。

安全性の確保 既に仮想ディスクが他のプロセスにアタッチされている可能性を考慮し、まず切り離し(detach)処理を行います。これにより、後続の処理が確実に実行できるようになります。

コンパクト化の実行 仮想ディスクを読み取り専用でアタッチし、diskpartコマンドによるコンパクト化を実行します。これにより、不要な領域が解放され、実際のディスク使用量が削減されます。

エラー処理 処理中に問題が発生した場合は、分かりやすいエラーメッセージを表示して安全に終了します。また、一時ファイルなども確実に削除されます。拡張子を.ps1として保存します。

それでは、具体的なスクリプトをご紹介します:

WSLの仮想ディスク(ext4.vhdx)を自動的に見つけ出し、不要な領域を解放する処理を自動化するスクリプト。

#----------------------------------------------
# WSL VHD ファイルを自動検出し、既にアタッチされている場合は
# detach した上で diskpart を実行して compact 化するスクリプト
#----------------------------------------------
# ※ 事前に WSL を停止してください (例: wsl --shutdown)
# ※ 管理者権限で PowerShell を実行してください

# diskpart 用の一時スクリプトを作成し実行する関数
function Invoke-DiskPartScript {
    param (
        [Parameter(Mandatory)]
        [string]$ScriptContent
    )
    # 一時ファイル名をランダムに作成
    $tempFile = Join-Path $env:TEMP ("dp_" + [System.IO.Path]::GetRandomFileName() + ".txt")
    try {
        # ASCII エンコードで一時ファイルに書き出す
        $ScriptContent | Out-File -FilePath $tempFile -Encoding ascii
        Write-Host "diskpart を実行中: $tempFile" -ForegroundColor Yellow
        # diskpart の出力をキャプチャして表示
        $result = diskpart /s $tempFile 2>&1
        Write-Host $result
    }
    finally {
        if (Test-Path $tempFile) {
            Remove-Item $tempFile -Force
        }
    }
}

try {
    # ext4.vhdx のパスを再帰的に検索 (Ubuntu パッケージの場合)
    $wslVhd = Get-ChildItem "$env:LOCALAPPDATA\Packages\*Ubuntu*\LocalState\ext4.vhdx" -Recurse -ErrorAction Stop | Select-Object -First 1

    if (-not $wslVhd) {
        throw "WSL の VHD ファイルが見つかりませんでした。Ubuntu パッケージがインストールされているか確認してください。"
    }

    Write-Host "見つかった VHD ファイル: $($wslVhd.FullName)" -ForegroundColor Green

    # ★ 既にアタッチされている可能性があるので、まず detach を試みる ★
    $detachScript = @"
select vdisk file="$($wslVhd.FullName)"
detach vdisk
"@

    Write-Host "既にアタッチされている可能性のある VHD をデタッチします..." -ForegroundColor Yellow
    Invoke-DiskPartScript -ScriptContent $detachScript

    # 本来の処理:attach (読み取り専用) → compact → detach
    $compactScript = @"
select vdisk file="$($wslVhd.FullName)"
attach vdisk readonly
compact vdisk
detach vdisk
"@

    Write-Host "diskpart でコンパクト化を実行します..." -ForegroundColor Yellow
    Invoke-DiskPartScript -ScriptContent $compactScript

    Write-Host "処理が完了しました。" -ForegroundColor Green

} catch {
    Write-Error "エラーが発生しました: $_"
    exit 1
}

また、WSLの仮想ディスクファイルの場所が既にお分かりの方向けに、より直接的なアプローチが可能なバッチスクリプトも用意しました。このスクリプトは、特に以下のような場合に便利です:

・複数のWSL環境を使い分けている場合 ・カスタムロケーションにWSLをインストールしている場合 ・VHDXファイルの場所を明確に把握している場合

このスクリプトでは、仮想ディスクファイルのパスを直接指定するだけで、以下の一連の処理を安全に実行します:

・指定されたVHDXファイルの存在確認 ・diskpartコマンドの自動生成と実行 ・コンパクト化処理の実施 ・処理完了後の後片付け

特に注目すべき点は、エラーハンドリングが充実していることです。ファイルの存在チェックから始まり、各ステップでの処理結果を確認することで、安全な実行を確保しています。拡張子を.batとして保存します。

それでは、具体的なスクリプトをご紹介します:

WSLを複数作成するなどして、仮想イメージの場所がわかっているときのスクリプト

@echo off
setlocal enabledelayedexpansion

REM ================================================
REM WSL の VHD(X) ファイルのパスを指定してください
REM 例: set "VHD_FILE=%LOCALAPPDATA%\Packages\...\LocalState\ext4.vhdx"
REM ================================================
set "VHD_FILE=C:\wsl\ubuntu24.04lts\ext4.vhdx"

REM VHD ファイルの存在チェック
if not exist "%VHD_FILE%" (
    echo エラー: 指定された VHD ファイルが見つかりません: "%VHD_FILE%"
    pause
    exit /b 1
)

REM 一時的な diskpart 用スクリプトファイルのパスを設定
set "DP_SCRIPT=%TEMP%\dp_script.txt"

REM diskpart 用のスクリプトを作成
(
    echo select vdisk file="%VHD_FILE%"
    echo attach vdisk readonly
    echo compact vdisk
    echo detach vdisk
) > "%DP_SCRIPT%"

if errorlevel 1 (
    echo エラー: 一時スクリプトファイルの作成に失敗しました。
    pause
    exit /b 1
)

REM diskpart を実行
echo -------------------------------
echo diskpart を実行中...
diskpart /s "%DP_SCRIPT%"
if errorlevel 1 (
    echo エラー: diskpart の実行中に問題が発生しました。
    del "%DP_SCRIPT%" >nul 2>&1
    pause
    exit /b 1
)

REM 一時スクリプトファイルを削除
del "%DP_SCRIPT%" >nul 2>&1
if exist "%DP_SCRIPT%" (
    echo 警告: 一時スクリプトファイルの削除に失敗しました: "%DP_SCRIPT%"
) else (
    echo 一時スクリプトファイルを削除しました。
)

echo -------------------------------
echo 完了しました。お疲れ様です!
pause
endlocal

1. PowerShellスクリプト (.ps1) の保存と実行方法

  1. スクリプトの保存
    • ご紹介したPowerShell用スクリプトのコードをコピーします。
    • お好みのテキストエディタ(例: メモ帳、Visual Studio Code)に貼り付け、
      WSL_Compact.ps1 など分かりやすいファイル名で、拡張子 .ps1 として保存します。
  2. スクリプトの実行
    • 管理者権限でPowerShellを起動
      • スタートメニューから「Windows PowerShell」を右クリックし、「管理者として実行」を選択します。
    • 保存先ディレクトリへ移動
      • 例:
        cd "C:\Users\YourUserName\Desktop"
        ※ 保存先のパスはご自身の環境に合わせて変更してください。
    • 実行ポリシーの一時変更
      • 以下のコマンドを入力して、スクリプトの実行が許可されるようにします。
        Set-ExecutionPolicy RemoteSigned -Scope Process
    • スクリプトの実行
      • 次のコマンドでスクリプトを実行します。
        .\WSL_Compact.ps1

2. バッチスクリプト (.bat) の保存と実行方法

  1. スクリプトの保存
    • ご紹介したバッチスクリプトのコードをコピーします。
    • テキストエディタに貼り付け、ファイル名を WSL_Compact.bat として保存します。
      ※ 拡張子が .bat であることを確認してください。
  2. スクリプトの実行
    • 管理者権限で実行
      • エクスプローラー上でファイルを右クリックし、「管理者として実行」を選択します。
      • または、管理者権限でコマンドプロンプトを起動し、スクリプトが保存されているディレクトリに移動して、以下のように実行します。
        WSL_Compact.bat

【共通の注意事項】

  • WSLの停止:
    スクリプト実行前に必ずWSLを停止してください(例: wsl --shutdown)。
  • 管理者権限:
    どちらのスクリプトも、管理者権限で実行する必要があります。
  • エラー対応:
    実行中に表示されるエラーメッセージを確認し、必要に応じて対処してください。

GitHubページを用意しました。

GitHub - superdoccimo/VHDX
Contribute to superdoccimo/VHDX development by creating an account on GitHub.

【最終注意事項】

  • バックアップの実施:
    本スクリプトはWSLの仮想ディスクに直接影響を与えるため、実行前に必ず対象ファイルやシステム全体のバックアップを取ってください。
  • 環境ごとの動作確認:
    スクリプトは特定のWSL環境や構成に依存する場合があります。ご自身の環境で十分にテストを行い、予期せぬ動作がないか確認してください。
  • 管理者権限の重要性:
    いずれのスクリプトも管理者権限での実行が必要です。適切な権限で実行しないと、正しく動作しない可能性があります。
  • 自己責任での利用:
    本記事の内容はあくまで参考情報として提供しています。実行による万一の不具合やトラブルについては、自己責任で対処していただくようお願いいたします。
  • フィードバックのお願い:
    もし問題や改善点を発見された場合は、コメントやお問い合わせフォームを通じてご意見をいただけると幸いです。
タイトルとURLをコピーしました