Skip to main content

PowerShell function to remove empty folders from a given path, recursively. You may optionally exclude a list of matching paths from removal.

function Remove-EmptyFolders
{
    <#
    .SYNOPSIS
        Removes empty folders recursively from a root directory.
        The root directory itself is not removed.

    .DESCRIPTION
        PowerShell function to remove empty folders from a given path, recursively.
        You may optionally exclude a list of matching paths from removal.

    .EXAMPLE
        PS > . .\Remove-EmptyFolders.ps1

        Dot sources the PowerShell script and imports the Remove-EmptyFolders
        function in the current session.

    .EXAMPLE
        PS > Remove-EmptyFolders -Path /some/posix/path

        Remove empty folders from a POSIX path.

    .EXAMPLE
        PS > Remove-EmptyFolders -Path \\some\unc\path

        Remove empty folders from a UNC path.

    .EXAMPLE
        PS > $pwd | Remove-EmptyFolders -WhatIf -Verbose -Exclude $('.git', 'node_modules')

        Perform a Dry-run/WhatIf, and exclude attempting to remove empty folders
        with '.git' and 'node_modules' in their path.

    .NOTES
        Authors: Joakim Borger Svendsen, Svendsen Tech, Jon LaBelle
        Version: v1.1.0
        Copyright (c) 2022 MIT License

    .LINK
        https://github.com/EliteLoser/misc/blob/master/PowerShell/Remove-EmptyFolders.ps1

    .LINK
        https://jonlabelle.com/snippets/view/powershell/remove-empty-folders-in-powershell
    #>
    [CmdletBinding(SupportsShouldProcess)]
    param(
        # A path to a location. Wildcards are accepted. The default location is the current directory (.).
        [Parameter(
            ValueFromPipeline,
            ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [String]
        $Path = '.',

        # Specifies an array of one or more string patterns to be matched as the cmdlet gets child items. Any matching item is excluded from the output. Enter a path element or pattern, such as *.txt or A*. Wildcard characters are accepted.
        [Parameter()]
        [String[]]
        $Exclude = @()
    )

    begin
    {
        [Int32] $Script:Counter = 0

        if (++$Counter -eq 1)
        {
            $RootPath = $Path
            Write-Verbose -Message "Saved root path as '$RootPath'."
        }

        # Avoid overflow. Overly cautious?
        if ($Counter -eq [Int32]::MaxValue)
        {
            $Counter = 1
        }
    }

    process
    {
        # List directories.
        foreach ($ChildDirectory in Get-ChildItem -LiteralPath $Path -Force -Exclude $Exclude |
            Where-Object {$_.PSIsContainer})
        {
            # Use .ProviderPath on Windows instead of .FullName in order to support UNC paths (untested).
            # Process each child directory recursively.
            Remove-EmptyFolders -Path $ChildDirectory.FullName
        }

        $CurrentChildren = Get-ChildItem -LiteralPath $Path -Force

        # If it's empty, the condition below evaluates to true.
        # Get-ChildItem returns $null for empty folders.

        if ($null -eq $CurrentChildren)
        {
            # Do not delete the root folder itself.
            if ($Path -ne $RootPath)
            {
                if ($PSCmdlet.ShouldProcess($Path, 'Remove empty folder'))
                {
                    Remove-Item -LiteralPath $Path -Force -WhatIf:$WhatIfPreference
                }
            }
        }
    }
}