Last year I posted a PowerShell function to measure the size of a folder. I recently had a need to use it again, and realized it needed a few tweaks. By default, the original version recursively searched through all subfolders. But there may be situations where you only want to measure the top level folder, so I added a –NoRecurse parameter. I also added a parameter to display an average value as well.
Manage and Report Active Directory, Exchange and Microsoft 365 with
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
Figure 1 Measuring the Temp folder in GB
Figure 2 Measuring my Documents folder including average size in KB
Here is the updated function:
Function Measure-Folder { <# .SYNOPSIS Measure the size of a folder. .DESCRIPTION This command will take a file path and create a custom measurement object that shows the number of files and the total size. The default size will be in bytes but you can specify a different unit of measurement. The command will format the result accordingly and dynamically change the property name as well. .PARAMETER Path The default is the current path. The command will fail if it is not a FileSystem path. .Parameter Average Calculate the average file size. This will be formatted with the same unit as the total size. .PARAMETER NoRecurse The default behavior is to recurse through all subfolders. But you can suppress that by using -NoRecurse. .PARAMETER Unit The default unit of measurement is bytes, but you can use any of the standard PowerShell numeric shortcuts: "KB","MB","GB","TB","PB" .EXAMPLE PS C:\> measure-folder c:\scripts Path Name Count Size ---- ---- ----- ---- C:\scripts scripts 2858 43800390 Measure the scripts folder using the default size of bytes. .EXAMPLE PS C:\> dir c:\scripts -Directory | measure-folder -Unit kb | Sort Size* -Descending | Select -first 5 | format-table -AutoSize Path Name Count SizeKB ---- ---- ----- ------ C:\scripts\GP GP 40 2287.080078125 C:\scripts\Workflow Workflow 64 1253.0185546875 C:\scripts\modhelp modhelp 1 386.4970703125 C:\scripts\stuff stuff 4 309.09375 C:\scripts\ADTFM-Scripts ADTFM-Scripts 76 297.7880859375 Get all the child folders under C:\scripts, measuring the size in KB. Sort the results on the size property in descending order. Then select the first 5 objects and format the results as a table. .EXAMPLE PS C:\> measure-folder $env:temp -Average -unit MB Path : C:\Users\Jeff\AppData\Local\Temp Name : Temp Count : 64626 SizeMB : 6769.94603252411 AvgMB : 0.104755764437287 Measure all the %TEMP% folder, including a file average all formatted in MB. .NOTES Last Updated: July 13, 2015 Version : 2.0 Originally published at https://jdhitsolutions.com/blog/scripting/3715/friday-fun-the-measure-of-a-folder/ Learn more about PowerShell:Essential PowerShell Learning Resources**************************************************************** * DO NOT USE IN A PRODUCTION ENVIRONMENT UNTIL YOU HAVE TESTED * * THOROUGHLY IN A LAB ENVIRONMENT. USE AT YOUR OWN RISK. IF * * YOU DO NOT UNDERSTAND WHAT THIS SCRIPT DOES OR HOW IT WORKS, * * DO NOT USE IT OUTSIDE OF A SECURE, TEST SETTING. * **************************************************************** .LINK Get-ChildItem Measure-Object .INPUTS string or directory .OUTPUTS Custom object #> [cmdletbinding()] Param( [Parameter(Position=0,ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True)] [ValidateScript({ if (Test-Path $_) { $True } else { Throw "Cannot validate path $_" } })] [Alias("fullname")] [string]$Path = ".", [switch]$NoRecurse, [Alias("avg")] [switch]$Average, [ValidateSet("bytes","KB","MB","GB","TB","PB")] [string]$Unit = "bytes" ) Begin { Write-Verbose -Message "Starting $($MyInvocation.Mycommand)" #hash table of parameters to Splat to Get-ChildItem $dirHash = @{ File = $True Recurse = $True } if ($NoRecurse) { Write-Verbose "No recurse" $dirHash.remove("recurse") } #hash table of parameters to splat to measure-Object $measureHash = @{ Property = "length" Sum = $True } if ($Average) { Write-Verbose "Including Average" $measureHash.Add("Average",$True) } } #begin Process { $Resolved = Resolve-Path -Path $path $Name = Split-Path -Path $Resolved -Leaf #verify we are in the file system if ($Resolved.Provider.Name -eq 'FileSystem') { #define a hash table to hold new object properties $propHash = [ordered]@{ Path=$Resolved.Path Name=$Name } Write-Verbose "Measuring $resolved in $unit" $dirHash.Path = $Resolved $stats = Get-ChildItem @dirHash | Measure-Object @measureHash Write-Verbose "Measured $($stats.count) files" $propHash.Add("Count",$stats.count) Switch ($Unit) { "bytes" { $propHash.Add("Size",$stats.sum) if ($average) { $propHash.Add("Avg",$stats.Average) } break } "kb" { $propHash.Add("SizeKB",$stats.sum/1KB) if ($average) { $propHash.Add("AvgKB",$stats.Average/1KB) } break } "mb" { $propHash.Add("SizeMB",$stats.sum/1MB) if ($average) { $propHash.Add("AvgMB",$stats.Average/1MB) } break } "gb" { $propHash.Add("SizeGB",$stats.sum/1GB) if ($average) { $propHash.Add("AvgGB",$stats.Average/1GB) } break } "tb" { $propHash.Add("SizeTB",$stats.sum/1TB) if ($average) { $propHash.Add("AvgTB",$stats.Average/1TB) } break } "pb" { $propHash.Add("SizePB",$stats.sum/1PB) if ($average) { $propHash.Add("AvgPB",$stats.Average/1PB) } break } } #switch #write the new object to the pipeline New-Object -TypeName PSobject -Property $propHash } else { Write-Warning "You must specify a file system path." } } #process End { Write-Verbose -Message "Ending $($MyInvocation.Mycommand)" } #end } #end function
I hope you'll let me know what you think or what additional features would be helpful.
Update: For an even newer version of this function go here.
What do you think about creating a $Units hashtable to eliminate the switch statement? Seems like a good opportunity to me because the switch branches are very similar.
That is a terrific alternative.
In place of the Switch statement, you could use code like this:
$unitHash = @{
Bytes = 1
KB = 1KB
MB = 1MB
GB = 1GB
TB = 1TB
PB = 1PB
}
$value = $stats.sum/$UnitHash.item($unit)
$Label = "Size"
if ($unit -ne 'bytes') {
$label+= $($unit.ToUpper())
}
$propHash.Add($label,$value)
#repeat process for Average
if ($Average) {
$value = $stats.average/$UnitHash.item($unit)
$Label = "Avg"
if ($unit -ne 'bytes') {
$label+= $($unit.ToUpper())
}
$propHash.Add($label,$value)
} #if Average
Thanks for sharing the code to Measure-Folder
One missing thing is help text for the -Average switch.
I went ahead and added a little something.
Jeffery, I like the simplicity of this. What would be an easy method to round off (to the thousandths place, for example) instead of having “6.611275422…GB” in Figure 1?
Thank you,
Manfred
I find it easiest to use the Round() method from the [MATH] class. Here’s a sample that would round to 2 decimal points.
$propHash.Add(“SizeTB”,[math]::round($stats.sum/1TB,2))
if ($average) {
$propHash.Add(“AvgTB”,[math]::Round($stats.Average/1TB,2))
Perhaps I should add a rounding parameter. Or you can!
}