PowerShellでduもどき

#目的
PowerShellでUnixのduライクなコマンドを実現する。

#背景
ネット上に色々サンプルがあるが、自分好みのものを作成。

#環境
PowerShell: V3.0以上

#コード
PowerShellスクリプトですが、Windows バッチファイルとしても実行できる特殊な構造になっています。
既定ではCドライブの1GB以上サイズのフォルダ上位15個とそれぞれのフォルダ中の1MB以上のサイズのファイル上位5個を降順に表示します。正確な値を得るために管理者権限での実行をお勧めします。

```PowerShell: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 のリンク先がフォルダの場合にそのフォルダ以下を対象から除外しようとすると処理が遅くなってしまうので当面はこれを仕様とします。