Iterating through directories is essential for storage auditing, permission management, folder cleanup, and many other automation tasks. When querying directory containers, PowerShell returns strongly typed System.IO.DirectoryInfo objects. Using Get-ChildItem with the -Directory switch and iteration constructs lets you traverse folder structures, inspect folder metadata, and calculate cumulative directory sizes across recursive directory trees.
Filtering Directories with Get-ChildItem -Directory
The -Directory switch restricts Get-ChildItem to returning only folders, filtering out files.
Get-ChildItem -Path "C:\Projects" -Directory Directory: C:\Projects
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 3/10/2024 9:00 AM WebApp
d---- 3/12/2024 2:15 PM MobileApp
d---- 3/14/2024 8:30 AM ScriptsWhere:
-Directoryfilters out regular files, returning onlyDirectoryInfoobjects.d----in theModecolumn confirms each item is a directory.
Directory Object Properties
Unlike plain text strings, DirectoryInfo objects provide immediate access to hierarchy and filesystem metadata:
| Property | Type | Description |
|---|---|---|
$_.Name |
string |
The directory name, such as Logs. |
$_.FullName |
string |
The absolute path to the directory on disk. |
$_.Parent |
DirectoryInfo |
A reference object to the parent directory container. |
$_.Root |
DirectoryInfo |
The volume root containing the folder, such as C:\. |
$_.CreationTime |
DateTime |
Timestamp when the directory was created. |
$_.LastWriteTime |
DateTime |
Timestamp when the directory contents were last modified. |
param (
[string]$RootPath = "$env:ProgramFiles"
)
Get-ChildItem -Path $RootPath -Directory |
Select-Object -First 3 |
ForEach-Object {
[PSCustomObject]@{
DirectoryName = $_.Name
FullPath = $_.FullName
ParentFolder = $_.Parent.Name
LastModified = $_.LastWriteTime.ToString("yyyy-MM-dd")
}
}Where:
-Path $RootPathspecifies the starting search location.-Directoryfilters out all file objects, returning onlyDirectoryInfoinstances.Select-Object -First 3limits results to the first three directories for demonstration.$_.Parent.Nameaccesses the parent container name through property dot notation.
Example output:
DirectoryName FullPath ParentFolder LastModified
------------- -------- ------------ ------------
Common Files C:\Program Files\Common Files Program Files 2024-08-15
dotnet C:\Program Files\dotnet Program Files 2024-10-12
PowerShell C:\Program Files\PowerShell Program Files 2024-10-24Looping Through Top-Level Directories
Get-ChildItem -Path "C:\Projects" -Directory | ForEach-Object {
$fileCount = (Get-ChildItem -Path $_.FullName -File).Count
Write-Output "$($_.Name): $fileCount file(s)"
}Where:
$_.FullNameprovides the full path of each subdirectory for the nestedGet-ChildItemcall..Counton the nested result tallies the number of files directly inside that folder.
Example output:
WebApp: 12 file(s)
MobileApp: 8 file(s)
Scripts: 5 file(s)Recursive Directory Traversal
The -Recurse switch extends Get-ChildItem to traverse all levels of subdirectories, not just the immediate children of the specified path.
Get-ChildItem -Path "C:\Projects" -Directory -Recurse Directory: C:\Projects
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 3/10/2026 9:00 AM WebApp
Directory: C:\Projects\WebApp
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 3/10/2026 9:05 AM src
d---- 3/10/2026 9:05 AM dist
Directory: C:\Projects\WebApp\src
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 3/10/2024 9:10 AM componentsWhere -Recurse walks into every subfolder, no matter how deeply nested, and returns each one as it is encountered.
Limiting Recursion Depth
Traversing deeply nested directory trees on large storage volumes can take a long time and consume significant memory. The -Depth parameter limits how many levels of subdirectories the provider traverses.
Get-ChildItem -Path "C:\Projects" -Directory -Recurse -Depth 1Where -Depth 1 limits traversal to one level of subdirectories below the starting path, rather than walking the entire tree.
Symlinks and Junction Points
When recursing through filesystem trees containing symbolic links or NTFS junction points that link back to parent directories, recursive commands can enter an infinite loop.
In PowerShell 7+, Get-ChildItem does not follow symlinks by default unless you supply -FollowSymlink. In Windows PowerShell 5.1, reparse points can be skipped using:
Get-ChildItem -Path $Path -Directory -Recurse |
Where-Object { $_.Attributes -notmatch "ReparsePoint" }Practical Examples
Finding Empty Directories
A recursive traversal combined with Where-Object is an effective way to locate empty folders throughout a directory tree, which is useful for cleanup scripts.
Get-ChildItem -Path "C:\Projects" -Directory -Recurse | Where-Object {
(Get-ChildItem -Path $_.FullName -Force | Measure-Object).Count -eq 0
} | ForEach-Object {
Write-Output "Empty folder: $($_.FullName)"
}Where:
-Forceensures hidden items are counted too, so a folder containing only hidden files is not mistaken for empty.Measure-Objectcounts the items returned by the innerGet-ChildItemcall for each candidate folder.
Example output:
Empty folder: C:\Projects\MobileApp\build
Empty folder: C:\Projects\Scripts\tempCalculating Total Size Per Directory
Get-ChildItem -Path "C:\Projects" -Directory | ForEach-Object {
$size = (
Get-ChildItem -Path $_.FullName -Recurse -File |
Measure-Object -Property Length -Sum
).Sum
[PSCustomObject]@{
FolderName = $_.Name
SizeMB = [math]::Round($size / 1MB, 2)
}
}Where:
Measure-Object -Property Length -Sumtotals theLength, in bytes, of every file found recursively within each top-level folder.1MBis a built-in numeric literal suffix that converts the byte total into megabytes.
Example output:
FolderName SizeMB
---------- ------
WebApp 45.32
MobileApp 128.71
Scripts 2.15Auditing Directory Tree Storage and File Count
The following script walks a directory tree, calculates the total storage consumed by files inside each folder, counts contained files, and flags directories exceeding an administrative size threshold.
param (
[string]$TargetRoot = "$PSScriptRoot",
[double]$AlertThresholdMB = 50.0
)
if (-not (Test-Path -Path $TargetRoot -PathType Container)) {
Write-Error "The specified root path '$TargetRoot' does not exist or is not a directory."
exit 1
}
Write-Output "Auditing subdirectories under: $TargetRoot`n"
$DirectoryReport = Get-ChildItem -Path $TargetRoot -Directory -Recurse -Depth 1 |
ForEach-Object {
$dirPath = $_.FullName
$fileStats = Get-ChildItem -Path $dirPath -File -Recurse -ErrorAction SilentlyContinue |
Measure-Object -Property Length -Sum
$totalBytes = if ($null -ne $fileStats.Sum) { $fileStats.Sum } else { 0 }
$sizeMB = [math]::Round($totalBytes / 1MB, 2)
$fileCount = $fileStats.Count
[PSCustomObject]@{
DirectoryPath = $dirPath
TotalFiles = $fileCount
TotalSizeMB = $sizeMB
ExceedsLimit = ($sizeMB -ge $AlertThresholdMB)
}
}
$DirectoryReport |
Sort-Object -Property TotalSizeMB -Descending |
Format-Table -AutoSizeWhere:
Test-Path -PathType Containerverifies that the target path is a valid folder.Get-ChildItem -Directory -Recurse -Depth 1discovers subdirectories up to one level deep.Get-ChildItem -File -Recursequeries all files within each discovered folder.Measure-Object -Property Length -Sumaggregates file sizes in bytes.1MBconverts raw byte totals into megabytes.($sizeMB -ge $AlertThresholdMB)flags folders exceeding the configured threshold.
Example run:
pwsh ./audit-directory-sizes.ps1 -TargetRoot "C:\AppStorage" -AlertThresholdMB 100.0Example output:
Auditing subdirectories under: C:\AppStorage
DirectoryPath TotalFiles TotalSizeMB ExceedsLimit
------------- ---------- ----------- ------------
C:\AppStorage\ArchivedLogs 450 320.45 True
C:\AppStorage\Backups 4 185.12 True
C:\AppStorage\IncomingQueue 12 14.20 False
C:\AppStorage\TempCache 0 0.00 False