Skip to content

PowerShell Loop Through Directories

How to iterate through directories in PowerShell using Get-ChildItem -Directory to filter folders, access metadata like creation time and parent paths, and perform recursive traversals with -Recurse and -Depth for storage audits, cleanup, and size calculations.

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                Scripts

Where:

  • -Directory filters out regular files, returning only DirectoryInfo objects.
  • d---- in the Mode column 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 $RootPath specifies the starting search location.
  • -Directory filters out all file objects, returning only DirectoryInfo instances.
  • Select-Object -First 3 limits results to the first three directories for demonstration.
  • $_.Parent.Name accesses 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-24

Looping 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:

  • $_.FullName provides the full path of each subdirectory for the nested Get-ChildItem call.
  • .Count on 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                components

Where -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 1

Where -Depth 1 limits traversal to one level of subdirectories below the starting path, rather than walking the entire tree.

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:

  • -Force ensures hidden items are counted too, so a folder containing only hidden files is not mistaken for empty.
  • Measure-Object counts the items returned by the inner Get-ChildItem call for each candidate folder.

Example output:

Empty folder: C:\Projects\MobileApp\build
Empty folder: C:\Projects\Scripts\temp

Calculating 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 -Sum totals the Length, in bytes, of every file found recursively within each top-level folder.
  • 1MB is 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.15

Auditing 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 -AutoSize

Where:

  • Test-Path -PathType Container verifies that the target path is a valid folder.
  • Get-ChildItem -Directory -Recurse -Depth 1 discovers subdirectories up to one level deep.
  • Get-ChildItem -File -Recurse queries all files within each discovered folder.
  • Measure-Object -Property Length -Sum aggregates file sizes in bytes.
  • 1MB converts 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.0

Example 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
Navigation