TreeSize.psm1
# PowerShell 5 feature (oops, has to be the "first thing" in a script) # In PowerShell 4 we have to use Import-Namespace from the reflection module using namespace System.IO ## This is not working ... I guess we need a format file to show it right ## The objects appear to be typed correctly ( Get-Member shows them as "System.IO.TreeView" ) # Update-TypeData -TypeName "System.IO.TreeView" -DefaultDisplayProperty TreeName -DefaultDisplayPropertySet TreeName, Length -Force function Get-TreeSize { #.Synopsis # Recursively lists provider items and sums their lengths #.Description # The actual objects are the same file system objects you'd get from Get-ChildItem -Recurse # But with properties added to them like Depth and with Length calculated for the folders # Then a custom formmatter makes them come out like this: # # .Example # Get-Treesize # Localization\ 12021 # |-- En-US\ 2025 # |-- En\ 1339 # .Example # Get-Treesize -ShowFiles # Localization\ 12021 # |-- Localization.psd1 6698 # |-- En-US\ 2025 # |-- UserSettings.psd1 1000 # |-- Localization.psd1 958 # |-- numbers.psd1 67 # |-- UserSettings.psd1 1959 # |-- En\ 1339 # |-- UserSettings.psd1 1253 # |-- numbers.psd1 86 # .Example # Get-Treesize | Format-Custom # # Localization\ (11.74 KB) # |-- En-US\ (1.98 KB) # |-- En\ (1.31 KB) [CmdletBinding()] param( # The root of the tree view [Parameter(ValueFromPipelineByPropertyName)] [Alias("PSPath")] $Path = $pwd, # Whether to show files or just folder in the resulting tree [Parameter()] [Switch]$ShowFiles, # How deep to start the indent (hypothetically for splitting up the work) # Defaults to the number of elements in $Path.Split("\") [int]$Depth = $((Convert-Path $Path).Split([Path]::DirectorySeparatorChar).Length), # Customize the TreeView $ChildIndicator = $(([char[]]@(0x251c, 0x2500, " ")) -join "") ) process { # I'm going to choose to show FileSystem paths here # Which means any PowerShell "PSDrive" will be lost # We could change that later ... $Local:Path = Convert-Path $Path $Local:Length = 0 # Cache the recursive output so it comes out in the right order $Local:Children = @( switch(Get-ChildItem -Path $Local:Path -Force) { {$_ -is [DirectoryInfo]} { # Recurse! And don't forget to pass all the parameters down ... $Info = Get-TreeSize -Path $_.FullName -ShowFiles:$ShowFiles -Depth ($Depth + 1) # Running total ... $Local:Length += $Info[0].Length $Info } {$_ -is [FileInfo]} { # Running total ... $Local:Length += $_.Length if($ShowFiles) { $_ } } } ) # To allow us to sort the children by size, we need to collect them all so we can sort at each level of output # Thus, if we're not the root invocation, roll-up the children if(1 -lt (Get-PSCallStack | Where Command -eq $MyInvocation.MyCommand).Count) { Get-Item -Path $Local:Path | Add-Member NoteProperty Length $Local:Length -Force -Passthru | Add-Member NoteProperty Children $Local:Children -Passthru } else { # This just outputs items and their .Children, recursively, sorted by length... function UnrollChildren { param($Items) foreach($Item in @($Items)) { $Item if($Item.Children) { UnrollChildren ($Item.Children | Sort Length -Descending) } } } UnrollChildren (Get-Item -Path $Local:Path | Add-Member NoteProperty Length $Local:Length -Force -Passthru | Add-Member NoteProperty Children $Local:Children -Passthru) | ForEach-Object { $_.PSTypeNames.Insert(0, "System.IO.TreeView"); $_ } | Add-Member ScriptProperty Depth { $this.FullName.Split([Path]::DirectorySeparatorChar).Length } -Passthru -Force | # NOTE: Even though this is a ScriptProperty # We have to add it here, dynamically, instead of in a types file because we use $Depth Add-Member ScriptProperty "TreeName" { (" " * [Math]::Max(($this.Depth - $Depth -1), 0)) + $(if($this.Depth -ne $Depth){$ChildIndicator}) + $this.Name + $(if($this.PSIsContainer){"\"})} -Passthru -Force } } } Set-Alias TreeSize Get-TreeSize |