Finding Disk Hogs (i.e. Large Folders)

“Disk full?   It was only at 60% last week!  Where did all the space go?”

If you haven’t said this, then you probably won’t appreciate this post.  It seems such a relatively simple task – give me a list of the biggest folders on the drive.  Yes, C:\Windows (or wherever you’ve stashed $env:WinDir to) will be one of them, but we’ll then have to rely on eyeballing and common sense (uh-oh) to address the offenders once we’ve found our disk hogs.

In other words, if you do something like this: Get-FolderSize c:\ | Sort-Object -Descending -Property Size | Select-Object -First 10 | Remove Item, you deserve what you get.

Humour aside, I run into this situation at least once a week.  On my home network, I run WinDirStat, but at work we have certain ‘clean’ labs that don’t allow willy-nilly downloaded apps to run.  In those labs, when I get the SCOM alert, I usually go searching for some script to do it.  For some reason, I’ve yet to find one that works reliably.  (It turns out I wrote one a few days ago, too.  Oh, well.)

So I decided to write my own:

 function Get-FolderSize
{param ([parameter(valuefrompipeline=$true)][string[]]$Path = @((Get-Location).ProviderPath));

    begin
    {

        ########################################
        function invoke-worker
        {
            param ([string]$Path = $null);
            
            # handle bum data
            if (!$Path -or !(Test-Path -Path $Path))
            {
                return;

            } # if (!$Path -or !(Test-Path -Path $Path))

            $Path = (Resolve-Path -Path $Path).ProviderPath;

            Write-Progress $MyInvocation.MyCommand.Name $Path;

            [long]$size = 0;
            
            # this is were most of the work occurs
            Get-ChildItem -Path $Path |
            % {
            
                if ($_.PsIsContainer)
                { # if it's a folder, recurse and return size
                    
                    $size += invoke-worker -Path $_.FullName;
                
                } # if ($_.PsIsContainer)
                else
                {  # otherwise, add it's size
                    $size += $_.Length;

                } # if ($_.PsIsContainer) ... else
            
            } # Get-ChildItem -Path $Path |

            # we've iterated (and recursed) through the folder. cache for summary view in the end {} block
             $script:output += New-Object -TypeName PsObject |
            Select-Object -Property @{
                n = 'Path';
                e = { $Path; }
            }, @{
                n = 'Size';
                e = { $size; }
            } # $object = New-Object -TypeName PsObject | Select-Object...

            # output folder size
            $size;

        } # function invoke-worker
        ########################################


        $script:output = @();

    } # begin

    process
    {
        foreach ($_path in $Path)
        { # this will return the file size, but we don't care.  We want the summary view in the end {} block
            invoke-worker -Path $Path | Out-Null;
        
        } # foreach ($_path in $Path)

    } # process

    end
    { # filter out all the zero-length files
        $script:output |
        ? { $_.size; } 

    } # end

} # function Get-FolderSize