Forum Discussion
LainRobertson
Aug 13, 2025Silver Contributor
How to: Finding large directories/recovering lost space.
Hi folks,
Every once in a blue moon I need to figure out where a disk's free space has disappeared to. There's boatloads of free tools that do this via a GUI but I felt like a basic PowerShell solution I can use in other contexts.
Here's the simple module I wrote as well as some basic examples on how it can be leveraged in a standalone context.
Props to anyone who spots the Easter egg.
Module: XTree.psm1
function Get-DirectorySize
{
[cmdletbinding()]
param(
[parameter(Mandatory=$true)][ValidateNotNull()][string] $Path
)
Write-Verbose -Message "Parsing $Path";
$Summary = [PSCustomObject] @{
Path = $Path.ToLowerInvariant();
Count = 0;
Size = 0;
}
[System.IO.Directory]::EnumerateFiles($Path) | ForEach-Object {
[System.IO.FileInfo]::new($_) | ForEach-Object {
$Summary.Count++;
$Summary.Size += $_.Length;
}
}
$Summary;
}
function Get-DirectoryTreeSize
{
[cmdletbinding()]
param(
[parameter(Mandatory=$true)][ValidateNotNull()][string] $Path
)
# Reference: https://learn.microsoft.com/en-us/dotnet/api/system.io.fileattributes?view=netframework-4.8.1
New-Variable -Name "ReparsePoint" -Value ([System.IO.FileAttributes]::ReparsePoint.value__) -Option Constant;
#region Create a new output object with default values.
$Summary = [PSCustomObject] @{
Path = $Path.ToLowerInvariant();
Count = 0;
Size = 0;
TotalCount = 0;
TotalSize = 0;
}
#endregion
#region Make any recursive calls first.
[System.IO.Directory]::EnumerateDirectories($Path) | ForEach-Object {
# We do not want to process reparse points.
if (0 -eq (([System.IO.DirectoryInfo]::new($_).Attributes.value__ -band $ReparsePoint)))
{
Get-DirectoryTreeSize -Path $_ | ForEach-Object {
$Summary.TotalCount += $_.Count;
$Summary.TotalSize += $_.Size;
$_;
}
}
}
#endregion
#region Now, process and output the current directory.
$Stats = Get-DirectorySize -Path $Path;
$Summary.Count = $Stats.Count;
$Summary.Size = $Stats.Size;
$Summary.TotalCount += $Stats.Count;
$Summary.TotalSize += $Stats.Size;
$Summary;
#endregion
}
Export-ModuleMember -Function @(
"Get-DirectorySize"
, "Get-DirectoryTreeSize"
);
Example 1: Selecting the top five consumers by TotalSize
This sort method is most useful for getting a high-level overview of a large directory structure.
Get-DirectoryTreeSize -Path "D:\Data\Temp\Edge\windows" | Sort-Object -Property TotalSize -Descending | Select-Object -First 5 | Format-Table -AutoSize -Property TotalSize, TotalCount, Path;
Example 2: Selecting the top five consumers by Size
This sort method is more useful where you're looking for large individual directories.
Get-DirectoryTreeSize -Path "D:\Data\Temp\Edge\windows" | Sort-Object -Property Size -Descending | Select-Object -First 5 | Format-Table -AutoSize -Property Size, Count, Path;
Example output from both examples
Additional information
- We do not want to process reparse points because:
- If the reference points to within the structure then we end up counting the same files twice, which is misleading;
- If the reference points outside the structure then it shouldn't be counted as contributing within the structure.
- I've used the native .NET class [System.IO.Directory] in lieu of the PowerShell-native Get-ChildItem as it's more efficient in a few scenarios - both in execution and coding effort. Get-ChildItem also errors out on certain reparse points in Windows PowerShell, which you can test for yourself using:
Get-ChildItem -Directory -Force -Path "$env:USERPROFILE\Application Data\";
Cheers,
Lain
No RepliesBe the first to reply