3

I have a PowerShell 2.0 script that I use to delete folders that have no files in them:

dir 'P:\path\to\wherever' -recurse | Where-Object { $_.PSIsContainer } | Where-Object { $_.GetFiles().Count -eq 0 } | foreach-object { remove-item $_.fullname -recurse}

However, I noticed that there were a ton of errors when running the script. Namely:

Remove-Item : Directory P:\path\to\wherever cannot be removed because it is not empty.

"WHAT?!" I panicked. They should all be empty! I filter for only empty folders! Apparently that's not quite how the script is working. In this scenario a folder that has only folders as children, but files as grandchildren is considered empty of files:

Folder1 (no files - 1 folder) \ Folder 2 (one file)

In that case, PowerShell sees Folder1 as being empty and tries to delete it. The reason this puzzles me is because if I right-click on Folder1 in Windows Explorer It says that Folder1 has 1 folder and 1 file within it. Whatever is used to calculate the child objects underneath Folder1 from within Explorer allows it to see grandchild objects ad infinitum.

Question:

How can I make my script not consider a folder empty if it has files as grandchildren or beyond?

Community
  • 1
  • 1
Wesley
  • 238
  • 1
  • 2
  • 17

4 Answers4

4

Here's a recursive function I used in a recent script...

function DeleteEmptyDirectories {
  param([string] $root)

  [System.IO.Directory]::GetDirectories("$root") |
    % {
      DeleteEmptyDirectories "$_";
      if ([System.IO.Directory]::GetFileSystemEntries("$_").Length -eq 0) {
        Write-Output "Removing $_";
        Remove-Item -Force "$_";
      }
    };
}

DeleteEmptyDirectories "P:\Path\to\wherever";
Dave Cluderay
  • 7,268
  • 1
  • 29
  • 28
3

Updating for recursive deletion:

You can use a nested pipeline like below:

dir -recurse | Where {$_.PSIsContainer -and `
@(dir -Lit $_.Fullname -r | Where {!$_.PSIsContainer}).Length -eq 0} |
Remove-Item -recurse -whatif

(from here - How to delete empty subfolders with PowerShell?)


Add a ($_.GetDirectories().Count -eq 0) condition too:

dir path -recurse | Where-Object { $_.PSIsContainer } | Where-Object { ($_.GetFiles().Count -eq 0) -and ($_.GetDirectories().Count -eq 0) } | Remove-Item

Here is a more succinct way of doing this though:

dir path -recurse | where {!@(dir -force $_.fullname)} | rm -whatif

Note that you do not need the Foreach-Object while doing remove item. Also add a -whatif to the Remove-Item to see if it is going to do what you expect it to.

Community
  • 1
  • 1
manojlds
  • 290,304
  • 63
  • 469
  • 417
  • Aha! That makes sense. However, I'd have to run the script over and over in order to truly flush out all empties sense the first run would get the furthest leaves on the tree. Then the second would get the next set that are now empty since the last run of the script. Etc. So do I either loop through the script until there's no output or is there a more elegant way of doing that? And would you prefer that as a totally separate question? – Wesley Aug 12 '11 at 00:51
  • @wesley - you can use a recursive function. Check my answer. – JNK Aug 12 '11 at 00:58
  • @WesleyDavid - You can nest pipelines. See my updated answer and the linked answer. – manojlds Aug 12 '11 at 01:08
  • @WesleyDavid - I agree that Dave Cluderay's recursive function is indeed more easily understood. – manojlds Aug 12 '11 at 19:42
  • 1
    Working through it now. I'm having a hard time understanding the `@(dir -Lit $_.Fullname -r | Where {!$_.PSIsContainer})` part, but I'm going to keep crunching on it for a while. – Wesley Aug 12 '11 at 19:46
  • PS, I took out my comment on the nested pipelines because I made a connection about how boneheaded I was. =P – Wesley Aug 12 '11 at 19:46
  • Okay, I poured over it and then used it. Got a lot of "cannot be removed because it is not empty." errors on various directories. =/ Perhaps I didn't understand it like I thought I did. – Wesley Aug 12 '11 at 21:03
  • @WesleyDavid - I had treid it out, it worked perfectly! I did not get any kind of error that you are talking about! – manojlds Aug 12 '11 at 21:15
  • Strange. Either way, it's the kind of thing I'm looking for. Thanks for the help! =) – Wesley Aug 12 '11 at 21:36
  • FYI, before running the above the statistics on the filesystem were: Folders: 17,832 Files: 232,343. Immediately after running the script: Folders: 12,622 Files 201,286. Don't worry, it was a controlled environment. =) – Wesley Aug 12 '11 at 23:06
2

There were some issues in making this script, one of them being using this to check if a folder is empty:

{!$_.PSIsContainer}).Length -eq 0

However, I discovered that empty folders are not sized with 0 but rather NULL. The following is the PowerShell script that I will be using. It is not my own. Rather, it is from PowerShell MVP Richard Siddaway. You can see the thread that this function comes from over at this thread on PowerShell.com.

function remove-emptyfolder {
 param ($folder)

 foreach ($subfolder in $folder.SubFolders){

 $notempty = $false
 if (($subfolder.Files | Measure-Object).Count -gt 0){$notempty = $true}
 if (($subFolders.SubFolders  | Measure-Object).Count -gt 0){$notempty = $true}
 if ($subfolder.Size -eq 0 -and !$notempty){
   Remove-Item -Path $($subfolder.Path) -Force -WhatIf
 }
 else {
  remove-emptyfolder $subfolder
 }

}

}

$path = "c:\test"
$fso = New-Object -ComObject "Scripting.FileSystemObject"

$folder = $fso.GetFolder($path)
remove-emptyfolder $folder
Wesley
  • 238
  • 1
  • 2
  • 17
1

You can use a recursive function for this. I actually have already written one:

cls

$dir = "C:\MyFolder"

Function RecurseDelete()
{
    param   (
            [string]$MyDir
            )

    IF (!(Get-ChildItem -Recurse $mydir | Where-Object {$_.length -ne $null}))
        {
            Write-Host "Deleting $mydir"
            Remove-Item -Recurse $mydir
        }
    ELSEIF (Get-ChildItem $mydir | Where-Object {$_.length -eq $null})
        {
            ForEach ($sub in (Get-ChildItem $mydir | Where-Object {$_.length -eq $null}))
            {
                Write-Host "Checking $($sub.fullname)"
                RecurseDelete $sub.fullname
            }   
        }
    ELSE
        {
            IF (!(Get-ChildItem $mydir))
                {
                    Write-Host "Deleting $mydir"
                    Remove-Item $mydir
                }

        }
}

IF (Test-Path $dir) {RecurseDelete $dir}
JNK
  • 63,321
  • 15
  • 122
  • 138
  • +1 Your solution works, but I'd like to suggest one improvement: When the folder names contain square brackets, the `Remove-Item` calls fail (silently). The fix is simple; add the `-LiteralPath` parameter like so: `Remove-Item -Recurse -LiteralPath $mydir` and `Remove-Item -LiteralPath $mydir`. Thanks. – Sabuncu Jun 16 '13 at 20:54