One of the sessions I presented recently at TechDays San Francisco was on file share management with PowerShell. One of the scripts I demonstrated was for a function to get information for top level folders. This is the type of thing that could be handy to run say against the root of your shared users folder. Or the root of a group share where each subfolder is a share that belongs to a different group. My function takes advantage of a new feature for Get-ChildItem that makes it much easier to retrieve only file or directories. Here's my Get-FolderSize function.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
#requires -version 3.0 Function Get-FolderSize { <# .Synopsis Get a top level folder size report .Description This command will analyze the top level folders in a given root. It will write a custom object to the pipeline that shows the number of files in each folder, the total size in bytes and folder attributes. The output will also include files in the root of the specified path. Sample output: Name : DesktopTileResources Fullname : C:\windows\DesktopTileResources Size : 21094 Count : 17 Attributes : ReadOnly, Directory Use the -Force parameter to include hidden directories. .Example PS C:\> get-foldersize c:\work | format-table -auto Path Name Size Count Attributes ---- ---- ---- ----- ---------- C:\work work 252083656 223 Directory C:\work\atomic atomic 622445 6 Directory C:\work\fooby fooby 18 1 Directory C:\work\images images 1470091 118 Directory C:\work\resources resources 8542561 143 Directory C:\work\shell shell 225161 4 Directory C:\work\test test 17198758 4 Directory C:\work\Test Rig 2 Test Rig 2 4194304 1 Directory C:\work\test2 test2 40 2 Directory C:\work\Ubuntu12 Ubuntu12 7656701952 2 Directory C:\work\widgets widgets 162703 49 Directory .Example PS C:\> get-foldersize c:\users\jeff\ -force | out-gridview -title Jeff .Notes Last Updated: May 8, 2013 Version : 0.9 **************************************************************** * 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 https://jdhitsolutions.com/blog/2013/05/getting-top-level-folder-report-in-powershell .Inputs None .Outputs Custom object #> [cmdletbinding()] Param( [Parameter(Position=0)] [ValidateScript({Test-Path $_})] [string]$Path=".", [switch]$Force ) Write-Verbose "Starting $($myinvocation.MyCommand)" Write-Verbose "Analyzing $path" #define a hashtable of parameters to splat to Get-ChildItem $dirParams = @{ Path = $Path ErrorAction = "Stop" ErrorVariable = "myErr" Directory = $True } if ($hidden) { $dirParams.Add("Force",$True) } $activity = $myinvocation.MyCommand Write-Progress -Activity $activity -Status "Getting top level folders" -CurrentOperation $Path $folders = Get-ChildItem @dirParams #process each folder $folders | foreach -begin { Write-Verbose $Path #initialize some total counters $totalFiles = 0 $totalSize = 0 #initialize a counter for progress bar $i=0 Try { #measure files in $Path root Write-Progress -Activity $activity -Status $Path -CurrentOperation "Measuring root folder" -PercentComplete 0 #modify dirParams hashtable $dirParams.Remove("Directory") $dirParams.Add("File",$True) $stats = Get-ChildItem @dirParams | Measure-Object -Property length -sum } Catch { $msg = "Error: $($myErr[0].ErrorRecord.CategoryInfo.Category) $($myErr[0].ErrorRecord.CategoryInfo.TargetName)" Write-Warning $msg } #increment the grand totals $totalFiles+= $stats.Count $totalSize+= $stats.sum if ($stats.count -eq 0) { #set size to 0 if the top level folder is empty $size = 0 } else { $size=$stats.sum } $root = Get-Item -Path $path #define properties for the custom object $hash = [ordered]@{ Path = $root.FullName Name = $root.Name Size = $size Count = $stats.count Attributes = (Get-Item $path).Attributes } #write the object for the folder root New-Object -TypeName PSobject -Property $hash } -process { Try { Write-Verbose $_.fullname $i++ [int]$percomplete = ($i/$folders.count)*100 Write-Progress -Activity $activity -Status $_.fullname -CurrentOperation "Measuring folder" -PercentComplete $percomplete #get directory information for top level folders $dirParams.Path = $_.Fullname $stats = Get-ChildItem @dirParams -Recurse | Measure-Object -Property length -sum } Catch { $msg = "Error: $($myErr[0].ErrorRecord.CategoryInfo.Category) $($myErr[0].ErrorRecord.CategoryInfo.TargetName)" Write-Warning $msg } #increment the grand totals $totalFiles+= $stats.Count $totalSize+= $stats.sum if ($stats.count -eq 0) { #set size to 0 if the top level folder is empty $size = 0 } else { $size=$stats.sum } #define properties for the custom object $hash = [ordered]@{ Path = $_.FullName Name = $_.Name Size = $size Count = $stats.count Attributes = $_.Attributes } #write the object for each top level folder New-Object -TypeName PSobject -Property $hash } -end { Write-Progress -Activity $activity -Status "Finished" -Completed Write-Verbose "Total number of files for $path = $totalfiles" Write-Verbose "Total file size in bytes for $path = $totalsize" } Write-Verbose "Ending $($myinvocation.MyCommand)" } #end Get-FolderSize
The function defaults to the local path and gets a collection of all of the top level folders, that is, those found directly in the root. The function then takes the collection of folders and pipes them to ForEach-Object. Most of the time we only use the Process scriptblock with ForEach-Object, but I want to take advantage of the Begin and End blocks as well. In the Begin scriptblock I measure all of the files in the root of the parent path and create a custom object that shows the number of files and total size in bytes. I'm going to get this same information for each child folder as well.
The process scriptblock does just that for each top level folder. This version of my function uses Write-Progress to display progress and in the End script block I have code to complete the progress bar, although It works just fine without it.
Other techniques I'd like to point out are the use of splatting and error handling. You'll notice that I'm using the common -ErrorVariable parameter. After exploring the different types of exceptions I decided I could easily display any errors and the paths In the Catch block. I'm using Write-Warning, but this could just as easily be written to a text file.
The function writes an object like this for every folder.
Path : C:\windows\Inf Name : Inf Size : 81926300 Count : 1265 Attributes : Directory
Here's an example of complete output:
Because I've written objects to the pipeline, I could pipe this to Out-Gridview, export to a CSV file or create an HTML report.
This is just a taste of what you can accomplish with some basic PowerShell commands.
Is there a reason you wouldn’t use (Get-ChildItem -Recure -Force | Where {$_.psiscontainer -eq $false | Measure-object -sum -Property Length).Sum with a custom object? Does it run faster than Get-ChildItem in version 2.0? I typically run into path length limitations based on ridiculous user folder creation with deep paths.
The main reason is that I wanted to take advantage of the new parameters for Get-Childitem in v3. I haven’t tried it on anything with a really deep path structure.
In v2, getting just folders using the where clause on my Scripts directory took 2.95 seconds. In v3 using the new -Directory took 148ms. Doing the same for files took 609MS in 2.0 and 534MS in 3.0. I then tested using C:Windows to get files. PowerShell 2.0 took 94.25 seconds and PowerShell 3.0 took 31.15 seconds.
Guess who learned something. -> This.person <-