Windowsで物理ディスク全体の空き容量・使用容量をVB/.NETから取得する方法|WMIのMSFT_DISK→MSFT_VOLUME連携を徹底解説

システムドライブだけでなく、EFIや回復パーティションを含む「物理ディスク全体」の空き容量/使用容量を正しく把握したい――その要求に対し、ボリューム単位のAPIでは届かないことが多々あります。本記事では、WMI(CIM)クラス MSFT_Disk→MSFT_Partition→MSFT_Volume をたどり、VB/.NETから確実に集計する実装と運用の要点を解説します。

目次

課題の整理:なぜ GetDiskFreeSpace(Ex) では不十分なのか

GetDiskFreeSpace/GetDiskFreeSpaceEx は「ボリューム(ドライブレターやマウントポイント)」に対する空き容量を返します。したがって、ドライブレターを持たないEFIや回復パーティションは集計対象から漏れ、さらに複数ボリュームが同一物理ディスクに跨る場合、物理ディスク単位の全体像を正しく示せません。

観点ボリュームAPI物理ディスク集計
EFI/回復パーティション取得不可(多くはドライブレターなし)取得可(ディスク→パーティション→ボリューム)
総容量ボリューム単位の容量のみディスク総容量と照合しながら集計可
空き容量ボリュームの空き容量全ボリュームの空き容量合算で把握
未割り当て領域不明ディスク総容量 − 全パーティション容量の差で推定

解決方針の全体像

「デバイス(ディスク)」を起点にCIMの関連をたどり、対象ディスク上に存在するあらゆるボリュームの使用状況を合算します。具体的には次の順路です。

  1. 起点の特定: root\microsoft\windows\storage で MSFT_Disk を検索し、OSがブートしている物理ディスク(IsBoot = TRUE)を特定。
  2. ディスク → パーティション: ASSOCIATORS OF {MSFT_Disk.ObjectId="..."} で MSFT_Partition を列挙。
  3. パーティション → ボリューム: 各パーティションから MSFT_Volume を関連取得。ドライブレター無しのボリュームも列挙されます。
  4. 集計: すべてのボリュームの Size と SizeRemaining を合算し、TotalSize/TotalFree/TotalUsed を算出。

この流れにより、物理ディスク全体の使用状況(EFIや回復を含む)が再現性高く得られます。

使用クラスと主なプロパティ(一覧)

クラス主なプロパティ意味補足
MSFT_DiskObjectId, Number, Size, IsBoot, PartitionStyle物理ディスクの識別・総容量・ブート判定など起点となるオブジェクト。IsBoot=TRUE がシステムディスク。
MSFT_PartitionObjectId, PartitionNumber, Size, GptType, Typeパーティション情報EFI/MSR/回復などの判定に活用可能。
MSFT_VolumeObjectId, Size, SizeRemaining, DriveLetter, FileSystemLabelボリューム容量と空き容量ドライブレター無しも列挙される点が重要。

WQLクエリ(確認用)

-- システムが起動している物理ディスク
SELECT * FROM MSFT_Disk WHERE IsBoot = TRUE;

-- ディスク → パーティション
ASSOCIATORS OF {MSFT_Disk.ObjectId=""} WHERE ResultClass = MSFT_Partition;

-- パーティション → ボリューム
ASSOCIATORS OF {MSFT_Partition.ObjectId=""} WHERE ResultClass = MSFT_Volume;

フルコード(VB/.NET Framework 4.8 コンソール)

以下は、管理者権限で実行することを前提に、システムディスク配下のすべてのボリュームを合算して「物理ディスク全体の空き容量/使用容量」を計算する、動作に必要な最小限+αを含む実装です。重複ボリュームの計上を避けるため、ObjectId での重複排除(HashSet)も行っています。

Option Strict On
Imports System
Imports System.Collections.Generic
Imports System.Management

Module Module1


Sub Main()
    Console.OutputEncoding = System.Text.Encoding.UTF8

    ' 管理スコープ(管理者権限での実行が必要)
    Dim scope As New ManagementScope("\\.\root\microsoft\windows\storage")
    scope.Connect()

    ' ◆ システムディスク(IsBoot=TRUE)を特定
    Dim bootDisk As ManagementObject = Nothing
    Using searcher As New ManagementObjectSearcher(scope, New ObjectQuery("SELECT * FROM MSFT_Disk WHERE IsBoot = TRUE"))
        Dim col = searcher.Get()
        For Each d As ManagementObject In col
            bootDisk = d
            Exit For ' 複数返ることは通常無いが最初の1件でOK
        Next
    End Using

    If bootDisk Is Nothing Then
        Console.WriteLine("システムディスクが見つかりません。管理者権限で実行しているか確認してください。")
        Return
    End If

    Dim diskNumber As UInteger = Convert.ToUInt32(bootDisk("Number"))
    Dim diskSize As ULong = Convert.ToUInt64(bootDisk("Size"))
    Dim diskObjId As String = CStr(bootDisk("ObjectId"))

    Console.WriteLine($"対象ディスク: #{diskNumber}")
    Console.WriteLine($"ディスク総容量: {FormatBytes(diskSize)} ({diskSize} bytes)")
    Console.WriteLine()

    ' ◆ ディスク → パーティション
    Dim totalVolumeSize As ULong = 0UL
    Dim totalVolumeFree As ULong = 0UL
    Dim seenVolumeIds As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase)

    Dim dAssoc As String = $"ASSOCIATORS OF {{MSFT_Disk.ObjectId=""{EscapeWql(diskObjId)}""}} WHERE ResultClass = MSFT_Partition"

    Using partSearch As New ManagementObjectSearcher(scope, New ObjectQuery(dAssoc))
        Dim parts = partSearch.Get()

        For Each part As ManagementObject In parts
            Dim partNum As UInteger = 0UI
            Dim partSize As ULong = 0UL
            Try
                partNum = Convert.ToUInt32(part("PartitionNumber"))
            Catch
            End Try
                Try
                partSize = Convert.ToUInt64(part("Size"))
            Catch
            End Try

            ' ◆ パーティション → ボリューム
            Dim partObjId As String = CStr(part("ObjectId"))
            Dim vAssoc As String = $"ASSOCIATORS OF {{MSFT_Partition.ObjectId=""{EscapeWql(partObjId)}""}} WHERE ResultClass = MSFT_Volume"

            Using volSearch As New ManagementObjectSearcher(scope, New ObjectQuery(vAssoc))
                Dim vols = volSearch.Get()
                For Each vol As ManagementObject In vols
                    Dim volId As String = CStr(vol("ObjectId"))
                    If Not seenVolumeIds.Add(volId) Then
                        ' 同じボリュームを複数パーティションから辿るなどの重複に対処
                        Continue For
                    End If

                    Dim vSize As ULong = 0UL
                    Dim vFree As ULong = 0UL
                    Try
                        vSize = Convert.ToUInt64(vol("Size"))
                    Catch
                    End Try
                    Try
                        vFree = Convert.ToUInt64(vol("SizeRemaining"))
                    Catch
                    End Try

                    totalVolumeSize += vSize
                    totalVolumeFree += vFree

                    Dim driveLetter As String = Convert.ToString(vol("DriveLetter"))
                    Dim label As String = Convert.ToString(vol("FileSystemLabel"))
                    If String.IsNullOrEmpty(driveLetter) Then driveLetter = "(no letter)"

                    Console.WriteLine($"[Volume] {driveLetter}  Label={label}  Size={FormatBytes(vSize)}  Free={FormatBytes(vFree)}")
                Next
            End Using
        Next
    End Using

    Dim totalUsed As ULong = totalVolumeSize - totalVolumeFree

    Console.WriteLine()
    Console.WriteLine("=== 集計結果(ボリューム合算) ===")
    Console.WriteLine($"合計容量: {FormatBytes(totalVolumeSize)} ({totalVolumeSize} bytes)")
    Console.WriteLine($"空き容量: {FormatBytes(totalVolumeFree)} ({totalVolumeFree} bytes)")
    Console.WriteLine($"使用容量: {FormatBytes(totalUsed)} ({totalUsed} bytes)")

    ' ◆ 参考:未割り当て領域を推定(ディスク総容量 − 全パーティション容量)
    Dim partitionsTotal As ULong = 0UL
    Using partSearch2 As New ManagementObjectSearcher(scope, New ObjectQuery(dAssoc))
        Dim parts2 = partSearch2.Get()
        For Each part As ManagementObject In parts2
            Try
                partitionsTotal += Convert.ToUInt64(part("Size"))
            Catch
            End Try
        Next
    End Using

    Dim unallocated As ULong = 0UL
    If diskSize >= partitionsTotal Then
        unallocated = diskSize - partitionsTotal
    End If
    Console.WriteLine($"未割り当て領域(推定): {FormatBytes(unallocated)} ({unallocated} bytes)")

    Console.WriteLine()
    Console.WriteLine("完了。任意のキーで終了します。")
    Console.ReadKey()
End Sub

' WQLの文字列リテラルで二重引用符をエスケープ
Private Function EscapeWql(value As String) As String
    If value Is Nothing Then Return ""
    Return value.Replace("""", """""")
End Function

' 直感的な単位で表示
Private Function FormatBytes(bytes As ULong) As String
    Dim units = New String() {"B", "KiB", "MiB", "GiB", "TiB", "PiB"}
    Dim size As Double = bytes
    Dim unitIdx As Integer = 0
    While size >= 1024 AndAlso unitIdx < units.Length - 1
        size /= 1024.0
        unitIdx += 1
    End While
    Return $"{size:0.##} {units(unitIdx)}"
End Function


End Module 

ポイント

  • 管理者権限: root\microsoft\windows\storage へのアクセスは管理者権限が必要です(Visual Studio/コンソールを「管理者として実行」)。
  • 重複排除: ある種の構成では同一ボリュームが複数のパーティションに関連付く可能性があるため、ObjectId による重複排除が安全です。
  • 未割り当て領域: WMIで未割り当て領域は直接は得られないため、ディスク総容量から全パーティション合計を差し引いて推定します。

実行例(想定出力)

[Volume] C  Label=OS  Size=238.27 GiB  Free=121.54 GiB
[Volume] (no letter)  Label=SYSTEM  Size=100 MiB  Free=70 MiB
[Volume] (no letter)  Label=WINRE  Size=1.03 GiB  Free=250 MiB

=== 集計結果(ボリューム合算) ===
合計容量: 239.4 GiB (257000000000 bytes)
空き容量: 121.78 GiB (130800000000 bytes)
使用容量: 117.62 GiB (126200000000 bytes)
未割り当て領域(推定): 0 B (0 bytes)

値は環境により異なります。EFIや回復パーティションを含めて合算される点が、ボリュームAPIとの差です。

任意のディスク番号を指定して取得する(応用)

サーバーではシステムディスク以外の集計ニーズもあります。MSFT_Disk.Number = n で起点を切り替え、同じ手順を適用できます。

SELECT * FROM MSFT_Disk WHERE Number = 1;

実装面では IsBoot = TRUE のクエリ文字列だけ差し替えれば同様に動作します。

全物理ディスクを走査して一覧化する(応用)

全ディスクの「合計/空き/使用」をダッシュボード化したい場合、MSFT_Disk を全件取得し、各ディスクで前述の集計ルーチンを呼び出します。ボリュームの重複排除はディスクごとに独立させてください(ボリュームが複数ディスクに跨る場合は分散構成に注意)。

PowerShellでの等価取得(検証・運用スクリプト向け)

$disk = Get-CimInstance -Namespace root/microsoft/windows/storage -ClassName MSFT_Disk -Filter "IsBoot = True"
$parts = Get-CimAssociatedInstance -InputObject $disk -ResultClassName MSFT_Partition
$vols  = foreach ($p in $parts) { Get-CimAssociatedInstance -InputObject $p -ResultClassName MSFT_Volume }

# 重複排除しつつ集計

$unique = $vols | Group-Object ObjectId | ForEach-Object { $_.Group[0] }
$total  = ($unique | Measure-Object -Property Size -Sum).Sum
$free   = ($unique | Measure-Object -Property SizeRemaining -Sum).Sum
$used   = $total - $free

"{0:N0},{1:N0},{2:N0}" -f $total, $free, $used

ジョブスケジューラに登録すれば、定期監視やしきい値超過時の通知の基礎になります。

落とし穴と対策(必読)

  • 権限不足: 管理者権限がないと MSFT_* クラスの取得に失敗します。必ず管理者で実行してください。
  • ドライブレター無しボリューム: MSFT_Volume の列挙に含まれるため問題ありませんが、UI表示では名称が空になりがちです。FileSystemLabel などと併記すると識別しやすくなります。
  • 重複ボリュームの計上: ストレージスペース/ダイナミックディスク等の構成で、同一ボリュームに複数の関連が貼られる場合があります。ObjectId をキーに重複排除しましょう。
  • 未割り当て領域の扱い: 直接のWMIプロパティがないため、ディスク総容量 − 全パーティション容量で推定します。計算誤差を避けるため、型は UInt64 (VBの ULong)で統一します。
  • 仮想化/RAID環境: 物理ディスクの見え方が抽象化される場合があります。監視設計ではハイパーバイザーやRAIDコントローラ側のメトリクスとも整合を取ってください。
  • BitLocker: 暗号化有無は容量計算に影響しませんが、回復パーティションの存在が前提の構成では、回復領域も集計されることを理解しておくと良いです。
  • OSバージョン: MSFT_* ストレージクラスは比較的新しいWindowsで提供されます。古いOSでは同等のクラスが不足する場合があるため、対象環境を事前に確認してください。

パフォーマンス最適化のヒント

  • スコープの再利用: ManagementScope は1回接続して再利用。
  • ASSOCIATORSの粒度: ResultClass を指定し、不要な関連取得を抑制。
  • 一括処理: 取得した ManagementObjectCollection を都度 Using ブロック内で処理し、COMリソースを確実に解放。
  • 例外の局所化: プロパティ取得の Try ... Catch はループ外へ波及させない(ホットパスのオーバーヘッド回避)。

検証・テスト観点(チェックリスト)

シナリオ確認ポイント期待結果
EFI+回復+OSの標準構成EFI/回復が計上されるか全ボリューム合算に含まれる
ドライブレター無しボリューム列挙・集計の可否DriveLetter空でも計上される
ストレージスペース/ダイナミック重複ボリュームの除外ObjectIdで重複排除できる
未割り当て領域が存在推定計算の妥当性総容量 − パーティション合計 ≒ 未割り当て
権限不足で実行例外・エラーメッセージアクセス権限に関する明確なメッセージ

よくある拡張要件への対応

  • しきい値監視: TotalFree が閾値未満でアラート(Windows タスクスケジューラ+イベントログ/メール連携)。
  • ログ出力: 時系列監視のため、csv/json に DiskNumber/TotalSize/TotalFree/Timestamp を追記。
  • GUIツール化: WPFやWinFormsで一覧表を表示。MSFT_Disk.Number ごとにグリッド行を作成。

トラブルシューティング

症状原因候補対処
「システムディスクが見つからない」管理者権限不足/OSサポート外管理者で再実行、対象OSを確認
容量が合わない重複計上/未割り当て領域の存在ObjectId重複排除と未割り当て推定の見直し
一部ボリュームが表示されないマウント解除/オフラインMSFT_Volumeの状態を確認、オンライン化

まとめ

物理ディスク全体の空き容量/使用容量を正確に取得するためには、ボリュームAPIだけでは不十分です。MSFT_Disk を起点に MSFT_Partition、MSFT_Volume へ関連をたどり、すべてのボリューム(ドライブレター無しを含む)を合算する――この手順こそが、EFIや回復パーティションを含めた「ディスク全体」を定量化する唯一の堅牢なアプローチです。本記事のVBコードをベースに、しきい値監視、ログ化、GUI化など、運用要件へ柔軟に発展させてください。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次