Forum Discussion

LainRobertson's avatar
LainRobertson
Silver Contributor
Aug 13, 2025

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

Resources