目的
PowerShellでUnixのduライクなコマンドを実現する。
背景
ネット上に色々サンプルがあるが、自分好みのものを作成。
環境
PowerShell: V3.0以上
コード
PowerShellスクリプトですが、Windows バッチファイルとしても実行できる特殊な構造になっています。
既定ではCドライブの1GB以上サイズのフォルダ上位15個とそれぞれのフォルダ中の1MB以上のサイズのファイル上位5個を降順に表示します。正確な値を得るために管理者権限での実行をお勧めします。
du.bat
<# : バッチコマンド(PowerShellコメント)開始
@echo off & setlocal
rem
rem DU.bat [最上位フォルダー] [-NumDirs/-nd <最大表示Dir数,既定値:20>]
rem [-NumFiles/-nf <Dir毎表示File数,既定値:5>]
rem [-MinDirSize/-sd <Dirサイズ下限,既定値:1GB>]
rem [-MinFileSize/-sf <Fileサイズ下限,既定値:1MB>]
rem [-ShowProgress/-p]
rem
rem Created by earthdiver1
rem クリエイティブ・コモンズ 表示 - 継承 4.0 国際 ライセンスの下に提供されています。
rem
rem -------------------------------------------------------------------------------
rem 以下はPowershellスクリプトをバッチファイルの中に埋め込むためのプリアンブルです。
set BATCH_ARGS=%*
if defined BATCH_ARGS set BATCH_ARGS=%BATCH_ARGS:"=\"%
if defined BATCH_ARGS set BATCH_ARGS=%BATCH_ARGS:^^=^%
endlocal & Powershell -NoP -C "&([ScriptBlock]::Create((${%~f0}|Out-String)))" %BATCH_ARGS%
pause & exit/b
rem -------------------------------------------------------------------------------
: バッチコマンド(PowerShellコメント)終了 #>
param (
[String]$Dir = "C:\",
[Alias("nd")][ValidateRange(1,[Int]::MaxValue)][Int]$NumDirs = 15,
[Alias("nf")][ValidateRange(0,[Int]::MaxValue)][Int]$NumFiles = 5,
[Alias("sd")][ValidateRange(0,[Int64]::MaxValue)][Int64]$MinDirSize = 1GB,
[Alias("sf")][ValidateRange(0,[Int64]::MaxValue)][Int64]$MinFileSize = 1MB,
[Alias("p")][Switch]$ShowProgress
)
Function du {
param (
[String]$Dir = ".",
[Alias("nd")][ValidateRange(1,[Int]::MaxValue)][Int]$NumDirs = 15,
[Alias("nf")][ValidateRange(0,[Int]::MaxValue)][Int]$NumFiles = 5,
[Alias("sd")][ValidateRange(0,[Int64]::MaxValue)][Int64]$MinDirSize = 1MB,
[Alias("sf")][ValidateRange(0,[Int64]::MaxValue)][Int64]$MinFileSize = 1MB,
[Alias("p")][Switch]$ShowProgress
)
Function TreeSize($Path) {
if ($ShowProgress) { $global:n_processed_dirs++ }
$dirSize = (Get-ChildItem -LiteralPath $Path -Recurse -Force -ErrorAction SilentlyContinue | `
Measure-Object Length -Sum -ErrorAction SilentlyContinue).Sum
if ($dirSize -ge $MinDirSize) {
[PSCustomObject]@{folder=$Path; file="(dir)"; size=$dirSize}
Get-ChildItem -LiteralPath $Path -Directory -Force -ErrorAction SilentlyContinue | Foreach-Object {
TreeSize $_.FullName
}
} elseif ($ShowProgress) {
$global:n_processed_dirs += (Get-ChildItem -LiteralPath $Path -Directory -Recurse -Force -ErrorAction SilentlyContinue).Length
}
}
$ftArgs = @{Expression={$_.size/1MB};Label="Size(MB)";Width=11;FormatString="{0:N0}";Alignment="Right"},`
@{Expression={$_.folder};Label="Folder"},`
@{Expression={$_.file};Label="File";Width=[Int](($host.UI.RawUI.BufferSize.Width - 12) * 0.3)}
try {
if ($ShowProgress) {
$timer = New-Object System.Timers.Timer
$timer.Interval = 1000 # 1 sec
$timer.Enabled = $True
Register-ObjectEvent -InputObject $timer -EventName Elapsed -SourceIdentifier duTimer -Action {
$p = $n_processed_dirs/$n_total_dirs * 100;
if($duComplete -or $p -gt 99) {
Unregister-Event duTimer
$timer.Stop()
}
Write-Progress -Activity "Processing..." `
-Status ("# of dirs {0,7:N0}/{1,0:N0} ({2,0:N1}%)" -F $n_processed_dirs, $n_total_dirs, $p) `
-PercentComplete $p
} | Out-Null
Write-Progress -Activity "Preparing..." -Status "# of dirs - / - (-.-%)" -PercentComplete 0
$global:n_total_dirs = (Get-ChildItem -LiteralPath $Dir -Directory -Recurse -Force -ErrorAction SilentlyContinue).Length
$global:n_processed_dirs = 0
$global:duComplete = $False
$timer.Start()
}
TreeSize (Convert-Path -LiteralPath $Dir) | Sort-Object Size -Descending | Select-Object -First $NumDirs | Foreach-Object {
$_
Get-ChildItem -LiteralPath $_.Folder -File -Force -ErrorAction SilentlyContinue | Foreach-Object {
if ($_.Length -lt $MinFileSize) { return }
[PSCustomObject]@{folder=""; file=(Split-Path $_.FullName -Leaf); size=$_.Length}
} | Sort-Object size -Descending | Select-Object -First $NumFiles
} | Format-Table $ftArgs -Wrap
} catch [System.Exception] {
Write-Output $Error[0].ToString() $Error[0].InvocationInfo.PositionMessage
} finally {
if ($ShowProgress) { $global:duComplete = $True }
}
}
# $PSBoundParameters にパラメータ初期値を反映
foreach ($p in $MyInvocation.MyCommand.ScriptBlock.Ast.ParamBlock.Parameters) {
$key = $p.Name.VariablePath.UserPath
if ($PSBoundParameters.ContainsKey($key)) { Continue }
$value = (Get-Variable $key).Value
if ($value) { $PSBoundParameters.Add($key, $value) }
}
du @PSBoundParameters
実行例
> du.bat c:\
Size(MB) Folder File
-------- ------ ----
109,902 C:\ (dir)
3,234 hiberfil.sys
1,920 pagefile.sys
16 swapfile.sys
32,591 C:\Windows (dir)
4 explorer.exe
3 RtlExUpd.dll
24,366 C:\Windows.old (dir)
18,927 C:\Windows.old\WINDOWS (dir)
5 explorer.exe
14,704 C:\Windows\System32 (dir)
121 MRT-KB890830.exe
121 MRT.exe
69 RCoRes64.dat
31 WindowsCodecsRaw.dll
27 MaxxAudioVnA64.dll
12,326 C:\Recovery (dir)
12,226 C:\Recovery\Customizations (dir)
12,226 apps.ppkg
10,950 C:\Windows\System32\DriverStore (dir)
10,948 C:\Windows\System32\DriverStore\FileRepository (dir)
9,285 C:\Windows.old\WINDOWS\WinSxS (dir)
9,207 C:\Program Files (dir)
8,759 C:\Fujitsu (dir)
8,693 C:\Fujitsu\Softwaredisc (dir)
5 DrvCdSrc.exe
2 OsInfo.exe
2 ExecRO.exe
7,929 C:\Windows\WinSxS (dir)
6,514 C:\Fujitsu\Softwaredisc\Software (dir)
追記
2017/11/22 上記スクリプトは、Junction のリンク先を含めてカウントします。Junction のリンク先がフォルダの場合にそのフォルダ以下を対象から除外しようとすると処理が遅くなってしまうので当面はこれを仕様とします。