I know I just posted an update to my Measure-Folder function but I couldn't help myself and now I have an update to the update. Part of the update came as the result of a comment asking about formatting results to a certain number of decimal places. I typically the Round() method from the Math .NET class.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
PS C:\> [math]::Round(1234.56789,2) 1234.57 PS C:\> [math]::Round(1234.5678900123,4) 1234.5679
So I added a parameter, Round, to automatically round to a certain number of decimal points. The default is 2 but you can enter any value between 0 and 10. If you use 0 the effect is to treat the value as an integer.
The other change I made was to simplify the code. My intention when creating a PowerShell tool is not have duplicate commands, or commands that are very, very similar. In last week's version I used a Switch statement to dynamically create properties and values. But each item was practically the same except for the unit of measurement. So instead I came up with a hash table of units.
$unitHash = @{ Bytes = 1 KB = 1KB MB = 1MB GB = 1GB TB = 1TB PB = 1PB }
With this I can use the hashtable key as the part of property name, and the value for formatting the result.
$value = [Math]::Round($stats.sum/$UnitHash.item($unit),$Round)
The property name is created on the fly for anything other than the default "bytes".
$Label = "Size" if ($unit -ne 'bytes') { $label+= $($unit.ToUpper()) } $propHash.Add($label,$value)
I use the same process if the user wants the average. Here's the complete revised function.
#requires -version 4.0 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 Get 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" .PARAMETER Round The number of decimal points to round the sum and average values. The default is 2. Use a value of 10 to not round. The maximum value is 10. .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.08 C:\scripts\Workflow Workflow 64 1253.02 C:\scripts\modhelp modhelp 1 386.49 C:\scripts\stuff stuff 4 309.09 C:\scripts\ADTFM-Scripts ADTFM-Scripts 76 297.78 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 -round 10 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 with no rounding .NOTES Last Updated: July 22, 2015 Version : 2.2 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", [ValidateRange(0,10)] [int]$Round = 2 ) 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) } Write-Verbose "Rounding to $Round decimal points." } #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) $unitHash = @{ Bytes = 1 KB = 1KB MB = 1MB GB = 1GB TB = 1TB PB = 1PB } $value = [Math]::Round($stats.sum/$UnitHash.item($unit),$Round) $Label = "Size" if ($unit -ne 'bytes') { $label+= $($unit.ToUpper()) } $propHash.Add($label,$value) #repeat process for Average if ($Average) { $value = [Math]::Round($stats.average/$UnitHash.item($unit),$Round) $Label = "Avg" if ($unit -ne 'bytes') { $label+= $($unit.ToUpper()) } $propHash.Add($label,$value) } #if Average #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
The results are the same, with the addition of the rounding option.
I swear this is the last change. Unless someone gives me a cool idea! Enjoy.
How about long path support?
Perhaps even some speed increase?
https://github.com/alphaleonis/AlphaFS/wiki/PowerShell
Long paths are always an issue in Windows. Performance could be tweaked by using .NET directly, but then that takes me out of creating a PowerShell script and into development. If someone has a situation where those things are critical, PowerShell may not be the best tool for the job.
Why
#requires -version 4.0
?
Primarily because that is what I tested with. I suspect it will work with v3. Change 4 to 3 and give it a go.