Getting Directory Sizes in PowerShell


Summary: Guest blogger, Bill Stewart, discusses a Windows PowerShell function to determine folder size.

Microsoft Scripting Guy, Ed Wilson, is here. Guest blogger today is Bill Stewart. Bill Stewart is a scripting guru and a moderator for the Official Scripting Guys forum.

Here’s Bill…

You have probably asked this question hundreds of times, “How big is that folder?” Of course, the typical GUI way to find out is to right-click the folder in Windows Explorer and open the folder’s properties. As with all things GUI, this does not scale well. For example, what if you need the size for 100 different folders?

If you have worked with Windows PowerShell for any length of time, you know that it provides a pretty comprehensive set of tools. The trick is learning how to combine the tools to get the results you need. In this case, I know that Windows PowerShell can find files (Get-ChildItem), and I know that it can count objects and sum a property on objects. A simple example would be a command like this:

Get-ChildItem | Measure-Object -Sum Length

Get-ChildItem outputs a list of items in the current location (in files and folders, if your current location is in a file system), and Measure-Object uses this list as input and adds together every input object’s Length property (file size). In other words, this command tells you the count and sum of the sizes of all the files in the current directory.

The Measure-Object cmdlet outputs an object with five properties (including Count, Average, and Sum). However, we only care about the Count and Sum properties, so let us refine our command a bit:

Get-ChildItem | Measure-Object -Sum Length | Select-Object Count, Sum

Now we are using Select-Object to select (hence the name) only the two properties we care about. The end result is a new output object that contains only those two properties.

This is good as far as it goes, but I wanted my output object to include the directory’s name. In addition, while we are at it, let us use the names “Files” and “Size” instead of “Count” and “Sum.” To do this, I am going to output a custom object like this:

$directory = Get-Item .

$directory | Get-ChildItem |

  Measure-Object -Sum Length | Select-Object `

    @{Name=”Path”; Expression={$directory.FullName}},

    @{Name=”Files”; Expression={$_.Count}},

    @{Name=”Size”; Expression={$_.Sum}}

I need $directory as a separate variable so I can include it in the output object. In addition, you can see here that I am using Select-Object with a set of hash tables as a shorthand technique for creating a custom output object.

In the following image (Figure 1), you can see the output from all three of the commands.

Image of command output

The output does not look that great, but remember that the presentation is less important than the content: We are outputting objects, not text. Because the output is objects, we can sort, filter, and measure.

To get the output we want (include the path name and change a couple of the property names), the commands can start to get a bit lengthy. So it makes sense to encapsulate the needed code in a script. Get-DirStats.ps1 is the script, and its syntax is as follows:

Get-DirStats [[-Path] <Object>] [-Only] [-Every] [-FormatNumbers] [-Total]

or

Get-DirStats -LiteralPath <String[]> [-Only] [-Every] [-FormatNumbers] [-Total]

As you can see, you can run the script by using two sets of mutually exclusive parameters. Windows PowerShell calls these parameter sets. The parameter sets’ names (Path and LiteralPath) are defined in the statement at the top of the script, and the script’s CmdletBinding attribute specifies that Path is the default parameter set.

The Path parameter supports pipeline input. Also, the Path parameter is defined as being first on the script’s command line, so the Path parameter name itself is optional. The LiteralPath parameter is useful when a directory name contains characters that Windows PowerShell would normally interpret as wildcard characters (the usual culprits are the [ and the ] characters.) The Path and LiteralPath parameters are in different parameter sets, so they’re mutually exclusive.

The Only parameter calculates the directory size only for the named path(s), but not subdirectories (like what is shown in Figure 1). Normally, when we ask about the size of a directory, we’re asking about all of its subdirectories also. If you only care about counting and summing the sizes of the files in a single directory (but not its subdirectories), you can use the Only parameter.

The Every parameter outputs an object for every subdirectory in the path. Without the Every parameter, the script outputs an object for the first level of subdirectories only. The following image shows what I mean.

Image of menu

If we use the following command, the script will output an object for every directory in the left (navigation) pane (if we expand them all, as shown in the previous image).

Get-DirStats -Path C:\Temp -Every

If we omit the Every parameter from this command, the script will only output the directories in the right pane. The script will still get the sizes of subdirectories if you omit the Every parameter; the difference is in the number of output objects.

The FormatNumbers parameter causes the script to output numbers as formatted strings with thousands separators, and the Total parameter outputs a final object after all other output that adds up the total number of files and directories for all output. These parameters are useful when running the script at a Windows PowerShell command prompt; but you shouldn’t use them if you’re going to do something else with the output (such as sorting or filtering) because the numbers will be text (with FormatNumbers) and there will be an extra object (with Total). The following image  shows an example command that uses the  FormatNumbers and Total parameters with US English thousands separators.

Image of command output

Get-DirStats.ps1 supports pipeline input, so it uses the Begin, Process, and End script blocks. The script uses the following lines of code within the Begin script block to detect the current parameter set and whether input is coming from the pipeline:

$ParamSetName = $PSCmdlet.ParameterSetName

if ( $ParamSetName -eq “Path” ) {

  $PipelineInput = ( -not $PSBoundParameters.ContainsKey(“Path”) ) -and ( -not $Path )

}

elseif ( $ParamSetName -eq “LiteralPath” ) {

  $PipelineInput = $false

}

The script uses the $ParamSetName and $PipelineInput variables later in the Process script block. The logic behind the definition of the $PipelineInput variable is thus: “If the Path parameter is not bound (that is, it was not specified on the script’s command line), and the $Path variable is $null, then the input is coming from the pipeline.” Both the $ParamSetName and $PipelineInput variables are used in the script’s Process script block.

The beginning of script’s Process script block has the following code:

if ( $PipelineInput ) {

  $item = $_

}

else {

  if ( $ParamSetName -eq “Path” ) {

    $item = $Path

  }

  elseif ( $ParamSetName -eq “LiteralPath” ) {

    $item = $LiteralPath

  }

}

The $item variable will contain the path that the script will process. Thus, if the script’s input is coming from the pipeline, $item will be the current pipeline object ($_); otherwise, $item will be $Path or $LiteralPath (depending on the current parameter set).

Next, Get-DirStats.ps1 uses the Get-Directory function as shown here:

function Get-Directory {

  param( $item )

 

  if ( $ParamSetName -eq “Path” ) {

    if ( Test-Path -Path $item -PathType Container ) {

      $item = Get-Item -Path $item -Force

    }

  }

  elseif ( $ParamSetName -eq “LiteralPath” ) {

    if ( Test-Path -LiteralPath $item -PathType Container ) {

      $item = Get-Item -LiteralPath $item -Force

    }

  }

  if ( $item -and ($item -is [System.IO.DirectoryInfo]) ) {

    return $item

  }

}

The Get-Directory function uses Test-Path to determine if its parameter ($item) is a container object and a file system directory (that is, a System.IO.DirectoryInfo object).

If the Get-Directory function returned $null, the script writes an error to the error stream by using the Write-Error cmdlet and exits the Process script block with the return keyword.

After validating that the directory exists in the file system, the script calls the Get-DirectoryStats function, which is really the workhorse function in the script. The Get-DirectoryStats function is basically a fancy version of the commands run in Figure 1. Here it is:

function Get-DirectoryStats {

  param( $directory, $recurse, $format )

 

  Write-Progress -Activity “Get-DirStats.ps1” -Status “Reading ‘$($directory.FullName)'”

  $files = $directory | Get-ChildItem -Force -Recurse:$recurse | Where-Object { -not $_.PSIsContainer }

  if ( $files ) {

    Write-Progress -Activity “Get-DirStats.ps1” -Status “Calculating ‘$($directory.FullName)'”

    $output = $files | Measure-Object -Sum -Property Length | Select-Object `

      @{Name=”Path”; Expression={$directory.FullName}},

      @{Name=”Files”; Expression={$_.Count; $script:totalcount += $_.Count}},

      @{Name=”Size”; Expression={$_.Sum; $script:totalbytes += $_.Sum}}

  }

  else {

    $output = “” | Select-Object `

      @{Name=”Path”; Expression={$directory.FullName}},

      @{Name=”Files”; Expression={0}},

      @{Name=”Size”; Expression={0}}

  }

  if ( -not $format ) { $output } else { $output | Format-Output }

}

This function uses the Write-Progress cmdlet to inform the user running the script that something’s happening, and it uses a combination of the Get-ChildItem, Where-Object, Measure-Object, and Select-Object cmdlets to output a custom object. Note the use of the scoped variables ($script:totalcount and $script:totalbytes). These are used with the script’s Total parameter, and they are output in the script’s End script block.

Drop this script into a directory in your path, and you can quickly find the sizes for directories in your file system. Remember that it outputs objects, so you can add tasks such as sort and filter, for example:

Get-DirStats -Path C:\Temp | Sort-Object -Property Size

This command outputs the size of directories in C:\Temp, sorted by size.

The entire script can be downloaded from the Script Repository.

~Bill

Thank you, Bill, for writing an interesting and useful blog. Join me tomorrow for more Windows PowerShell cool stuff.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

Comments (36)

  1. mredwilson says:

    @DarkGnome I do not think it would be an easy tweak. The reason is that I do not see anywhere to get directory depth. I would therefore be reduced to parsing the path — not very accurate — or trying to count how many directories deep you go — or doing recursion and setting the number of recursions.

  2. Anonymous says:

    when i ran the This Script Get-Dirstats and i get error. I have given the right path but still it throwing the error

    The term 'Get-DirStats' is not recognized as the name of a cmdlet, function, script file, or operable program. Check th

    e spelling of the name, or if a path was included, verify that the path is correct and try again.

    At line:1 char:13

    + Get-DirStats <<<<  -Path D:Temp | Sort-Object -Property Size

       + CategoryInfo          : ObjectNotFound: (Get-DirStats:String) [], CommandNotFoundException

       + FullyQualifiedErrorId : CommandNotFoundException

  3. Anonymous says:

    Bill, Good Stuff! I love this script for getting local folder sizes, but how would you retrieve the stats from a list of 50 servers

  4. Good stuff. How do you remove folders with zero size?

  5. jrv says:

    @Paolo – you can't.

    On Vista bnad later many folders are not real.  They are placeholders for the system to use th redirect to the new location.  You must access the driectories by accessing teh 'special folders' variables of teh shell.  It is the only way to know where the real drirectory is.

    blogs.technet.com/…/hey-scripting-guy-how-can-query-the-contents-of-a-special-folder-on-a-windows-7-computer.aspx

    # Get the dektop folder path

    [environment]::GetFolderPath('desktop')

    # List all special folder names

    [enum]::GetNames([System.Environment+SpecialFolder])

    You cannot just list and accesss all folders that you find.

  6. Anonymous says:

    @CRAZYITAUS: Not sure if this helps, but I had the same error message as you.  I was able to fix it by going to the directory where I downloaded Get-DirStats.ps1 and entered ".Get-DirStats.ps1" followed by the parameters that Bill provided in his example.  I didn't get that error message when doing it this way.

    @Bill: Thanks for the example/explanation on parameter sets!

  7. I wish there was a better way to get folder sizing, the persistent issues with access denied are really ticking me off.  It's not an issue with your script as much as it's an issue with using get-childitem to enumerate the contents of a folder and then calculating length.

  8. ResetSA says:

    Thanks.But path size limit 256 symbols.

  9. EVVJSK says:

    Could a variation on this process work with Quota limits (i.e. could it be made tell how much of a user/departments quota was in use )?

  10. Bill Stewart says:

    @ResetSA: That's a limitation of the .NET Framework.

    @EVVJSK: Possibly. This script is a quick example of how to combine existing PowerShell cmdlets and constructs into something useful.

    @CRAZYITAUS: Make sure you download the script from the repository and put a copy of the file Get-DirStats.ps1 in a directory in your Path. Also keep in mind that script execution must be enabled.

  11. Bill Stewart says:

    @C. Marshall: Thanks for your comment.

  12. xx says:

    My goodness, Linux is SO much easier than this!

  13. Paolo says:

    I ran this on our Userprofiles Directory and got an Access denied error. How can I modify this to read the directories using the SYSTEM account? I dont see a parameter for that.

  14. brad says:

    Is there a way the output from this script could be output into a csv file?

  15. Cab says:

    Hey,

    Thank you very much for taking the time to create this, it has been very helpful!

    I just have one issue, which might not be applicable for here but I am running your script from SQL Server Management studion 2008 and when I get the output of the file it truncates the file path if it is longer than 28 characters. I have set my fields in sql to be an nvarchar(2000) but I do not see how it is truncating it.

    Any help would be much appreciated.

    Kind Regards,

    Craig

  16. Bill Stewart says:

    @Brad – yes; just pipe the script's output to Export-CSV.

    @Cab – the script outputs objects and the default formatter is outputting each object. You can pipe to Format-List or Export-CSV to see all of the data.

  17. DarkGnome says:

    Love this script, but am wondering about a minor tweak:  To get it to list only one-to-two levels deep.

  18. Path truncated says:

    Thanks but i can only get 39 characters for the path printed ?

    I expanded the console width no change. I see no "40" size wise in the code. A mystery.

    How can i get a much bigger path ?

  19. Richie says:

    Is there a way to add additional objects from the Get-ChildItem cmdlet in the final output?  I would like to include  "select-object FullName,CreationTime,LastAccessTime,LastWriteTime" for each directory as well as the Size.

    Thanks

    Richie T.

  20. Tide says:

    Great script, however its usefulness is limited when it truncates paths over 39 characters. The script would be much more useful if you would modify the script to output to a csv file that in a format that does not truncate the paths.

  21. Jasbir says:

    Thanks.

  22. Rich Elmer says:

    If you want to export to csv so you can see the full folder names just Pipe it to a csv file "| Export-csv Directorylist.csv" on the end of the command line – job done

  23. Raja SK says:

    — SCRIPT TO GET ALL FOLDER SIZES IN A DRIVE OR PATH

    $driveletter = “D:”
    $colItems = Get-ChildItem $driveletter | where-object {$_.Length -lt 0 } | Select name

    $table = New-Object system.Data.DataTable “MyTable”
    $col1 = New-Object system.Data.DataColumn FolderName,([string])
    $col2 = New-Object system.Data.DataColumn Size_MB,([Double])
    $table.columns.add($col1)
    $table.columns.add($col2)

    foreach ($objItem in $colItems)
    {
    $str = $($driveletter+ $objItem.name)
    $sz = (“{0:n2}” -f ((gci -path $str -recurse | measure-object -property length -sum).sum /1mb) )
    #write-host $sz
    $row = $table.NewRow();
    $row.FolderName = $str ;
    $row.Size_MB = $sz ;
    $table.Rows.Add($row)
    }
    $table| Sort-Object Size_MB -descending | Format-Table -Auto

  24. Andy says:

    Thanks for this, is it possible to add the following?
    exclude a certain folders from search.
    e.g. one level folder search if the folder is over 1GB in path: c:users, and exclude c:users*musics, c:users*videos and c:users*pictures ?

  25. KirtCarson says:

    Here’s a 1 liner.

    foreach($fldr in (gci \myserverd$ | ?{$_.PSIsContainer} | select-object fullname)){“” + (gci $fldr.FullName -r |Measure-Object -Sum Length).sum + “`t” + $fldr.FullName }

  26. Tim says:

    Thank you. I like this tool but have you given any thought to better error handling? While it continues to parse through directories, I’ve hit access denied and path length errors (also reported by others) which would be nice to output elsewhere (like
    an error log file) instead of messing up the good output.

  27. techmattr says:

    Can I pass in a txt file with a list of paths?

  28. verocab says:

    Hi,

    What if I want to include only folders with specific name?
    Let’s say I want to have the size of every folder named "music" or "video" in a share named \fileserverstuff

    Thank you

  29. WhoMe says:

    @vistor

    In PowerShell, all folders have zero size. That makes them easier to remove.

  30. Joakim Svendsen says:

    I wrote a script that actually uses robocopy, funnily enough, to measure the folder sizes, inspired by a blog post by Boe Prox (link in article). It is not limited to 250-260 characters in the path.

    It’s also very fast compared to Get-ChildItem -Recurse.

    You can also tweak the code to have nesting levels to only one or two, as someone mentioned in a comment. Check out "robocopy /?" for help/parameter info. I put the script here:
    http://www.powershelladmin.com/wiki/Get_Folder_Size_with_PowerShell,_Blazingly_Fast

  31. jesse says:

    One line got me directory name and size:
    get-childitem -Directory |foreach {$_; Get-ChildItem $_ -Recurse | Measure-Object -sum -Property length |fl Sum}

  32. Galileo says:

    How do I format the output size to GB?, say i have an output 67460951, using Get-DirStats -Path C:Temp | Sort-Object -Property Size ???

  33. Fry says:

    @Galileo, far to late but here’s the answer as I did this for myself.

    Just modify the script.
    100: @{Name="Size(GB)"; Expression={[math]::Floor($_.Sum / 1073741824); $script:totalbytes += $_.Sum}}

    I also modified the script to display just the folder and not the whole path.
    98: @{Name="Path"; Expression={$directory.Name}},
    104: @{Name="Path"; Expression={$directory.Name}},

  34. Matt says:

    You wrote: “Drop this script into a directory in your path, and you can quickly find the sizes for directories in your file system.”, however, when I did so, it did not work. I tried adding the directory to my path using the environment variables, but to no avail.

    1. Matt says:

      I found out I was running on a restricted machine (scripts are restricted.) I found out how I should be able to enable scripting. The script seems to run, but does not give the desired output and yields several warnings:

      PS C:\Users\[username]> Get-DirStats -Path C:\users\[username]\dropbox | Sort-Object -Property Size
      Get-ChildItem : The specified path, file name, or both are too long. The fully qualified file name must be less than 26
      0 characters, and the directory name must be less than 248 characters.
      At C:\Program Files\Get-DirStats.ps1:94 char:40
      + $files = $directory | Get-ChildItem <<<< -Force -Recurse:$recurse | Where-Object { -not $_.PSIsContainer }
      + CategoryInfo : ReadError: (C:\users\[username]…AdvancedStudies:String) [Get-ChildItem], PathTooLongExcept
      ion
      + FullyQualifiedErrorId : DirIOError,Microsoft.PowerShell.Commands.GetChildItemCommand

      Get-ChildItem : The specified path, file name, or both are too long. The fully qualified file name must be less than 26
      0 characters, and the directory name must be less than 248 characters.
      At C:\Program Files\Get-DirStats.ps1:94 char:40
      + $files = $directory | Get-ChildItem <<<