PowerShellでフォルダー内のファイルの論理サイズを合計する基本は、Get-ChildItemとMeasure-Objectです。ただし、アクセス拒否を無視した値や、隠しファイルを除外した値を「正確な全容量」と呼ばないようにします。また、Lengthの合計はエクスプローラーの「ディスク上のサイズ」や、削除して空く容量とは一致しないことがあります。
以下はファイルシステム向けの例です。Windows PowerShell 5.1とPowerShell 7系ではリンクの扱いなどに違いがあるため、使用版と対象の性質を確認してください。この記事の改訂ではPowerShell実行環境がなく、Windows実機での実行検証はしていません。
基本:再帰・ファイルのみ・隠し項目を含める
$path = 'C:\ExampleDirectory'
$measure = Get-ChildItem -LiteralPath $path -File -Recurse -Force -ErrorAction Stop |
Measure-Object -Property Length -Sum
[long]$bytes = 0
if ($null -ne $measure.Sum) { $bytes = [long]$measure.Sum }
[pscustomobject]@{
Path = $path
FileCount = $measure.Count
Bytes = $bytes
GiB = [math]::Round($bytes / 1GB, 2)
}
-LiteralPathは角括弧などをワイルドカードとして解釈させず、-Fileはファイルだけを集計します。-Recurseはサブフォルダーを含め、-Forceは隠し/システム属性の項目を列挙対象にしますが、アクセス権を回避する機能ではありません。-ErrorAction Stopは列挙時のエラーで処理を止め、欠けた集計を完成値として返すことを避けます。
空のフォルダーはファイル数0、合計0バイトとして扱います。存在しないパスやアクセスできない場所も0と扱うのは誤りです。上の短い例は通常のフォルダーの確認用で、リンク、ジャンクション、クラウドのプレースホルダーを含む場合は、次の対象範囲を明示する例を使います。
リンク類を除外し、対象範囲を明示する関数
次はWindowsのReparsePoint属性を持つ項目を除外し、通常のディレクトリだけを手動でたどる例です。ジャンクションやシンボリックリンクを経由して別の場所を合算しない方針ですが、OneDriveなどのクラウド項目も除外され得るため、「全ファイル」とは呼びません。除外件数を結果へ残します。
function Get-DirectoryLogicalSize {
[CmdletBinding()]
param(
[Parameter(Mandatory)][string]$LiteralPath,
[string]$NamePattern = '*'
)
$root = Get-Item -LiteralPath $LiteralPath -Force -ErrorAction Stop
if ($root.PSProvider.Name -ne 'FileSystem' -or -not $root.PSIsContainer) {
throw 'Specify a file-system directory.'
}
$rp = [System.IO.FileAttributes]::ReparsePoint
if (($root.Attributes -band $rp) -ne 0) {
throw 'The root directory is a reparse point; choose the intended target explicitly.'
}
$pending = New-Object 'System.Collections.Generic.Stack[string]'
$pending.Push($root.FullName)
[long]$bytes = 0
[long]$count = 0
[long]$skipped = 0
while ($pending.Count -gt 0) {
$current = $pending.Pop()
foreach ($item in (Get-ChildItem -LiteralPath $current -Force -ErrorAction Stop)) {
if (($item.Attributes -band $rp) -ne 0) {
$skipped++
continue
}
if ($item.PSIsContainer) {
$pending.Push($item.FullName)
} elseif ($item.Name -like $NamePattern) {
$bytes += [long]$item.Length
$count++
}
}
}
[pscustomobject]@{
Path = $root.FullName
NamePattern = $NamePattern
FileCount = $count
Bytes = $bytes
GiB = [math]::Round($bytes / 1GB, 2)
SkippedReparsePoints = $skipped
MeasuredAt = (Get-Date).ToString('o')
}
}
Get-DirectoryLogicalSize -LiteralPath 'C:\ExampleDirectory'
この関数は列挙エラーが発生すると結果オブジェクトを返さず停止します。SilentlyContinueでエラーを隠した部分合計とは区別できます。ただし、処理中にファイルが変化する環境の一時点スナップショットを保証するものではなく、競合やすべてのファイルシステム固有の条件を検出するものでもありません。
特定のファイルタイプだけを合計する
# 前のGet-DirectoryLogicalSizeを定義した後に実行
Get-DirectoryLogicalSize -LiteralPath 'C:\ExampleDirectory' -NamePattern '*.log'
フォルダーの探索は続け、ファイル名が*.logに一致する通常ファイルだけ合計します。名前による選択であり、ファイル内容から形式を判別しているわけではありません。通常のリンクを含まないツリーならGet-ChildItemの-Filterと-Fileを使う方法もありますが、集計間で除外方針をそろえて比較します。
KB/MB/GBとKiB/MiB/GiBの違い、境界値
PowerShellの数値接尾辞1KB、1MB、1GBは1024の累乗です。1GBで割った表示はGiB、1MBで割ればMiBと表記すると、10億バイトを1GBとする十進表示との混同を避けられます。集計値はBytesとして残し、丸めた文字列だけを後の計算に使わないでください。
function Format-BinarySize {
param([Parameter(Mandatory)][ValidateRange(0,[long]::MaxValue)][long]$Bytes)
if ($Bytes -ge 1GB) { return ('{0:N2} GiB' -f ($Bytes / 1GB)) }
if ($Bytes -ge 1MB) { return ('{0:N2} MiB' -f ($Bytes / 1MB)) }
if ($Bytes -ge 1KB) { return ('{0:N2} KiB' -f ($Bytes / 1KB)) }
return ('{0} Bytes' -f $Bytes)
}
Format-BinarySize -Bytes 1024
比較は-gtではなく-geなので、1024バイトちょうどもKiBへ切り替わります。表示例は1.00 KiBですが、小数点や桁区切りは実行環境のカルチャーで変わります。
テキストとCSVへ保存する
# 上のGet-DirectoryLogicalSizeを先に定義する
$result = Get-DirectoryLogicalSize -LiteralPath 'C:\ExampleDirectory'
$result | Format-List | Out-File -LiteralPath '.\directorySizeReport.txt' -Encoding utf8 -NoClobber
$result | Export-Csv -LiteralPath '.\directorySizeReport.csv' -NoTypeInformation -Encoding utf8 -NoClobber
保存先は実行中のカレントフォルダーです。書き込み可能な場所を選び、集計対象の外に保存するとレポート自身が次回の集計へ混ざりにくくなります。-NoClobberは既存ファイルの意図しない上書きを避けます。再実行時には別名にするか、保存方針を明確にしてから対応します。
$ErrorActionPreference = 'Stop'
$paths = @('C:\ExampleDirectory', 'D:\AnotherDirectory')
$results = foreach ($path in $paths) {
Get-DirectoryLogicalSize -LiteralPath $path
}
$results | Export-Csv -LiteralPath '.\directorySizes.csv' -NoTypeInformation -Encoding utf8 -NoClobber
Format-Table/Format-ListをExport-Csvの前へ入れず、数値を持つオブジェクトを直接渡します。複数フォルダーのどれかで失敗した場合、正常な一覧として保存しない運用にします。個別失敗を許容する設計なら、別途StatusやError列を設け、不明なサイズを0で埋めないでください。
定期監視では警告と実行失敗を区別する
次の本体を、上の関数定義と同じ.ps1ファイルへ保存する例です。10GiB超過を終了コード2、計測失敗や除外項目がある場合を1、閾値以下を0に分けています。これは監視システムが終了コードを読むための設計例で、Write-Warningだけでメールやデスクトップ通知が届くわけではありません。
$ErrorActionPreference = 'Stop'
try {
$r = Get-DirectoryLogicalSize -LiteralPath 'C:\ExampleDirectory'
if ($r.SkippedReparsePoints -gt 0) {
throw 'Skipped reparse points: coverage requires review.'
}
if ($r.Bytes -gt 10GB) {
Write-Warning ('Threshold exceeded: {0} bytes' -f $r.Bytes)
exit 2
}
exit 0
} catch {
Write-Error $_ -ErrorAction Continue
exit 1
}
タスクスケジューラの「プログラム/スクリプト」には使用するpowershell.exeまたはpwsh.exeの実際のパスを指定し、引数へ-NoProfile -File “C:\Scripts\MeasureDirectory.ps1″のようにスクリプトを渡します。ps1のパスだけをプログラム欄に入れる説明では不十分です。実行ポリシーを無条件にBypassへ変更せず、署名や組織の承認手順に従います。
実行アカウントの読み取り権限、作業フォルダー、ログの保存先、失敗時の通知経路を確認し、手動実行したタスクの結果とログを照合します。フォルダーが大きいほど走査に時間がかかるため、頻度を抑え、前回実行との重複を防ぎます。これは実装例であり、この記事で監視を設置・動作確認したという意味ではありません。
エクスプローラーや空き容量と一致しない理由
ファイルのLengthは論理長です。圧縮、スパースファイル、クラスタ割り当て、ハードリンク、代替データストリーム、重複排除、クラウドのオンライン専用ファイルでは、物理的な占有容量とずれることがあります。ハードリンクは複数の名前をたどると同じ実データを重ねて数える可能性があります。
この例ではReparsePointを除外する方針を採っているため、その配下やオンライン専用項目の容量を含まないことがあります。削除すれば同じバイト数だけ空くと判断せず、ファイルシステムやクラウドサービスの管理情報と照合します。アクセス拒否を回避するために所有権やACLを無断変更する必要もありません。
導入前に確認するテストケース
空フォルダー、1024バイトのファイル、サブフォルダー、隠しファイル、角括弧を含む名前、存在しないパス、アクセス拒否、リンク/ジャンクション、オンライン専用ファイルを分けて確認します。空フォルダーだけが0、エラーは失敗、除外項目は件数に現れるという意図と、実際の対象環境の挙動を照合してください。


コメント