72

I need to recursively remove all empty folders for a specific folder in PowerShell (checking folder and sub-folder at any level).

At the moment I am using this script with no success.

Could you please tell me how to fix it?

$tdc='C:\a\c\d\'
$a = Get-ChildItem $tdc -recurse | Where-Object {$_.PSIsContainer -eq $True}
$a | Where-Object {$_.GetFiles().Count -eq 0} | Select-Object FullName

I am using PowerShell on Windows 8.1 version.

Zhaph - Ben Duguid
  • 26,785
  • 5
  • 80
  • 117
GibboK
  • 71,848
  • 143
  • 435
  • 658
  • 2
    It seems to me the only thing you are missing is the actual removal of the folders. Added the -WhatIf... `$a | Where-Object {$_.GetFiles().Count -eq 0} | select -expand FullName | remove-item -whatif` – notjustme Feb 20 '15 at 14:44
  • thanks for your comment, but using your code, I get a popup window saying... has children and has Recurse parameter was not specified.. I need it avoid this warning. – GibboK Feb 20 '15 at 14:53
  • `Remove-Item -Recurse` should do the trick. If however both the folder `~\a\b` and the subfolder `~\a\b\c` have no files in them you'll get an error saying the path couldn't be found when the `$_.GetFiles()`-part is being run for `~a\b\c`, which is true if `~\a\b` already was deleted. – notjustme Feb 20 '15 at 15:18
  • This is where powershell is yet again ridiculous. There's a ISO standard C function for that: rmdir. Here is the implementation on the Windows platform (as OP tagged Windows): https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/rmdir-wrmdir . Workarounds like listing each directory content another time just to check whether its empty are really poor in terms of performance. – Johan Boulé Jul 26 '18 at 15:36
  • 1
    I got troubles with many answers here. Please, if you use `.FullName` then use Get-ChildItem's [-LiteralPath](https://stackoverflow.com/questions/33721892/is-get-childitem-recurse-broken-when-there-are-square-brackets-in-the-input-pat/55367757#55367757) !!! – it3xl Jun 24 '19 at 20:47

15 Answers15

92

You need to keep a few key things in mind when looking at a problem like this:

  1. Get-ChildItem -Recurse performs head recursion, meaning it returns folders as soon as it finds them when walking through a tree. Since you want to remove empty folders, and also remove their parent if they are empty after you remove the empty folders, you need to use tail recursion instead, which processes the folders from the deepest child up to the root. By using tail recursion, there will be no need for repeated calls to the code that removes the empty folders -- one call will do it all for you.
  2. Get-ChildItem does not return hidden files or folders by default. As a result you need to take extra steps to ensure that you don't remove folders that appear empty but that contain hidden files or folders. Get-Item and Get-ChildItem both have a -Force parameter which can be used to retrieve hidden files or folders as well as visible files or folders.

With those points in mind, here is a solution that uses tail recursion and that properly tracks hidden files or folders, making sure to remove hidden folders if they are empty and also making sure to keep folders that may contain one or more hidden files.

First this is the script block (anonymous function) that does the job:

# A script block (anonymous function) that will remove empty folders
# under a root folder, using tail-recursion to ensure that it only
# walks the folder tree once. -Force is used to be able to process
# hidden files/folders as well.
$tailRecursion = {
    param(
        $Path
    )
    foreach ($childDirectory in Get-ChildItem -Force -LiteralPath $Path -Directory) {
        & $tailRecursion -Path $childDirectory.FullName
    }
    $currentChildren = Get-ChildItem -Force -LiteralPath $Path
    $isEmpty = $currentChildren -eq $null
    if ($isEmpty) {
        Write-Verbose "Removing empty folder at path '${Path}'." -Verbose
        Remove-Item -Force -LiteralPath $Path
    }
}

If you want to test it here's code that will create interesting test data (make sure you don't already have a folder c:\a because it will be deleted):

# This creates some test data under C:\a (make sure this is not
# a directory you care about, because this will remove it if it
# exists). This test data contains a directory that is hidden
# that should be removed as well as a file that is hidden in a
# directory that should not be removed.
Remove-Item -Force -Path C:\a -Recurse
New-Item -Force -Path C:\a\b\c\d -ItemType Directory > $null
$hiddenFolder = Get-Item -Force -LiteralPath C:\a\b\c
$hiddenFolder.Attributes = $hiddenFolder.Attributes -bor [System.IO.FileAttributes]::Hidden
New-Item -Force -Path C:\a\b\e -ItemType Directory > $null
New-Item -Force -Path C:\a\f -ItemType Directory > $null
New-Item -Force -Path C:\a\f\g -ItemType Directory > $null
New-Item -Force -Path C:\a\f\h -ItemType Directory > $null
Out-File -Force -FilePath C:\a\f\test.txt -InputObject 'Dummy file'
Out-File -Force -FilePath C:\a\f\h\hidden.txt -InputObject 'Hidden file'
$hiddenFile = Get-Item -Force -LiteralPath C:\a\f\h\hidden.txt
$hiddenFile.Attributes = $hiddenFile.Attributes -bor [System.IO.FileAttributes]::Hidden

Here's how you use it. Note that this will remove the top folder (the C:\a folder in this example, which gets created if you generated the test data using the script above) if that folder winds up being empty after deleting all empty folders under it.

& $tailRecursion -Path 'C:\a'
techguy1029
  • 743
  • 10
  • 29
Kirk Munro
  • 1,190
  • 8
  • 9
  • 8
    This is a fabulous answer. Edit pending to remark that the code also removes the start folder if that winds up empty. – RichardHowells Jul 30 '15 at 18:46
  • 1
    Thanks Richard, both for the positive feedback and for the edit -- it's definitely worth noting that the root/start folder may be removed if it is empty as well. – Kirk Munro Jul 31 '15 at 19:42
  • 4
    @KirkMunro I came across a number of answers on technet and elsewhere that do not work properly, you've got the only good one I've seen so far. – Hart CO Mar 05 '16 at 05:54
  • 1
    I second what @Hart-CO said. This is the only answer I've found that really gets it all right. Thanks for sharing! – Jessie Westlake May 26 '17 at 01:19
  • 2
    Note that this will also remove the root folder, my contribution would be to validate if RootPath <> Path, then remove { if ($IsEmpty && $Path -ne $RootPath) { Write-Verbose "Removing empty folder at path '${Path}'." -Verbose }} – Kat Lim Ruiz Mar 11 '19 at 19:09
  • @KatLimRuiz what would that look like? – Bricktop May 08 '19 at 16:12
  • 1
    @bricktop if ($IsEmpty) { # only remove if it is not the root if ($Path -ne $InPath) { Write-Output ">> Deleting empty folder ${InPath}" Remove-Item -Force -LiteralPath $InPath } } – Kat Lim Ruiz May 08 '19 at 19:33
  • 1
    Nube question: Is there something special about the $Path param? It was working fine, and then I was trying to rename it using a different parameter name instead of '$Path' and when I bulk changed all the $Path references to (e.g.) $foo, the anonymous function doesn't work - it says "Get-ChildItem : Cannot bind argument to parameter 'LiteralPath' because it is null." <-- this is happening by calling "& $tailRecursion -Path 'C:\a'" in the same manner as when it the anonymous function uses $Path vs $foo – tb1 Nov 19 '20 at 18:07
  • 1
    @tb1: No there isn't anything special about the `$Path` parameter. To change the `$Path` parameter name, you need to change both the parameter itself in the 5 locations where it is used as `$Path` or `${Path}` as well as the parameter in the 2 locations where it is used to invoke the anonymous function recursively and outside the function (e.g. `-Path`). – Kirk Munro Nov 20 '20 at 22:27
  • @KirkMunro: This is my result which fails (using same string path that works in original eg calling "& $tailRecursion -Path 'C:\a'"): $tailRecursion = { param( $foo ) foreach ($childDirectory in Get-ChildItem -Force -LiteralPath $foo -Directory) { & $tailRecursion -Path $childDirectory.FullName } $currentChildren = Get-ChildItem -Force -LiteralPath $foo $isEmpty = $currentChildren -eq $null if ($isEmpty) { Write-Verbose "Removing empty folder at path '${foo}'." -Verbose Remove-Item -Force -LiteralPath $foo } } – tb1 Nov 25 '20 at 13:19
  • @tb1: You replaced the parameter variable name in the 5 locations where it needed to be replaced, but you didn't replace the parameter name in the invocations. Make sure you replace `& $tailRecursion -Path` with `& $tailRecursion -foo`. There are two instances of this in your modified code, one outside the anonymous function, and one inside of it. Once you make those additional changes, then it will work. – Kirk Munro Nov 26 '20 at 16:37
  • @KirkMunro *thank you* I couldn't see the trees through the forest! – tb1 Nov 27 '20 at 13:43
  • @KatLimRuiz I don't see InPath ever being set ?? – Tony Dec 04 '22 at 18:34
74

You can use this:

$tdc="C:\a\c\d"
$dirs = gci $tdc -directory -recurse | Where { (gci $_.fullName).count -eq 0 } | select -expandproperty FullName
$dirs | Foreach-Object { Remove-Item $_ }

$dirs will be an array of empty directories returned from the Get-ChildItem command after filtering. You can then loop over it to remove the items.

Update

If you want to remove directories that contain empty directories, you just need to keep running the script until they're all gone. You can loop until $dirs is empty:

$tdc="C:\a\c\d"
do {
  $dirs = gci $tdc -directory -recurse | Where { (gci $_.fullName).count -eq 0 } | select -expandproperty FullName
  $dirs | Foreach-Object { Remove-Item $_ }
} while ($dirs.count -gt 0)

If you want to ensure that hidden files and folders will also be removed, include the -Force flag:

do {
  $dirs = gci $tdc -directory -recurse | Where { (gci $_.fullName -Force).count -eq 0 } | select -expandproperty FullName
  $dirs | Foreach-Object { Remove-Item $_ }
} while ($dirs.count -gt 0)
Community
  • 1
  • 1
arco444
  • 22,002
  • 12
  • 63
  • 67
  • thanks, but I found a possible problem, it work for first level nesting of folder. I need to go deep at any level. Could you please double check it? – GibboK Feb 20 '15 at 15:04
  • I don't understand what you mean... `-recurse` will ensure it traverses all the way. It won't recursively remove folders that contain empty folders, if that's what you mean, because they're not empty. – arco444 Feb 20 '15 at 15:06
  • thanks for your comment, I would need ...remove folders that contain empty folders..... do you think would be possible to tweak your script? Is it possible in some how? – GibboK Feb 20 '15 at 15:09
  • Thanks for your edit, but using your new code I get error: The property 'count' cannot be found on this object. Verify that the property exists... any idea? thanks for your help – GibboK Feb 20 '15 at 15:24
  • Did you type it correctly..? Anything `.count` **always** returns a number. Just try `$null.count` in your console window. – arco444 Feb 20 '15 at 15:27
  • yes I am coping and pastin..., I have tried several times, I get error multiple times at: $dirs = gci $tdc -directory -recurse | Where { (gci $_.fullName).count -eq 0 } – GibboK Feb 20 '15 at 15:29
  • I don't know what you've done, works flawlessly for me. Make sure you have permission to everything you're trying. And you only need to run the second bit of the solution. Don't combine it with the first bit. – arco444 Feb 20 '15 at 15:33
  • Try this `gci $_.fullName |select -expand count ` – Loïc MICHEL Feb 20 '15 at 15:39
  • 1
    @Kayasax that won't work. Count is not a property of a `FileInfo` object. It's a property of the array that `gci` returns, hence why the command needs to be in parenthesis – arco444 Feb 20 '15 at 15:43
  • It has to be `(gci $_.fullName -Force).count` otherwise it won't count hidden files. – Saintali Aug 08 '16 at 18:57
  • 1
    wonderul, I'd just add `-verbose` to `remove-item` to know what is being deleted – inwenis Mar 12 '19 at 17:42
  • You must use [-LiteralPath](https://stackoverflow.com/questions/33721892/is-get-childitem-recurse-broken-when-there-are-square-brackets-in-the-input-pat/55367757#55367757) with `.FullName`!!! – it3xl Jun 24 '19 at 20:44
  • How do I leave first level folders not deleted? I mean lets say I have folders Cust1, Cust2, Cust3 under "C:\a\c\d" which I want to be left there even they are empty. In other words how to make the script working starting only from the second level? All empty folders (and subfolders) under Cust1, Cust2, Cust3 to be deleted. – pyfyc Oct 16 '19 at 10:30
  • The answer works pretty fine when I test it with "junk" folders. But I have somewhere like +60k folders and around +1000 are empty and I want to delete these +1000 folders but I'm afraid it would delete other files too. Where can I find the deleted empty/not empty folders after running this script because they are not visible in the RecycleBin ? – Leo Ramadani Dec 10 '21 at 12:19
42
Get-ChildItem $tdc -Recurse -Force -Directory | 
    Sort-Object -Property FullName -Descending |
    Where-Object { $($_ | Get-ChildItem -Force | Select-Object -First 1).Count -eq 0 } |
    Remove-Item -Verbose

The only novel contribution here is using Sort-Object to reverse sort by the directory's FullName. This will ensure that we always process children before we process parents (i.e., "tail recursion" as described by Kirk Munro's answer). That makes it recursively remove empty folders.

Off hand, I'm not sure if the Select-Object -First 1 will meaningfully improve performance or not, but it may.

Bacon Bits
  • 30,782
  • 5
  • 59
  • 66
  • It works for me. I think it bettter than orther answer. – tjorchrt Oct 30 '19 at 02:09
  • This should be the best answer. Works really well. – Oleg Kazakov Sep 29 '20 at 01:50
  • 1
    In case you wonder, `Select-Object -First 1` actually makes the nested `Get-ChildItem` stop when it has found the first file. So it definitely improves performance for directories that contain many items. You can slightly simplify it as `Get-ChildItem` returns `$null` if directory is empty, which evaluates to `$false` in a boolean context: `Where-Object { ! ($_ | Get-ChildItem -Force | Select-Object -First 1) }` – zett42 Dec 18 '20 at 11:34
  • 1
    For large directory structures this method will consume more memory than actual tail recursion, because it loads information for all sub directories into memory to be able to sort them. Also the method will only report deletion progress once the whole directory structure has been traversed, while actual tail recursion will report progress much earlier, as soon as first empty directory has been found during traversion. – zett42 Dec 18 '20 at 11:46
  • @zett42 I think if your directory structure is *so large* that you can't even enumerate just the directories in memory long enough to sort them you have a problem that will need to be broken down into several parts already or may already require on-disk storage. A recursive function is, IMX, much more likely to run into memory problems because the system has to store not just the objects in memory, but the stack of parent functions still executing. – Bacon Bits Dec 19 '20 at 06:18
  • 1
    However, if memory is really a concern, you can just use `Get-ChildItem ... | Select-Object -ExpandProperty FullName | Sort-Object | ...` so that you're only putting the string path names into memory instead of a DirectoryInfo. If you're still running into problems with *that*, then I really fail to see how you're not going to want to use something like an SQLite database to store the folder names long enough to process. – Bacon Bits Dec 19 '20 at 06:20
13

Just figured I would contribute to the already long list of answers here.

Many of the answers have quirks to them, like needing to run more than once. Others are overly complex for the average user (like using tail recursion to prevent duplicate scans, etc).

Here is a very simple one-liner that I've been using for years, and works great...

It does not account for hidden files/folders, but you can fix that by adding -Force to the Get-ChildItem command

This is the long, fully qualified cmdlet name version:

Get-ChildItem -Recurse -Directory | ? { -Not ($_.EnumerateFiles('*',1) | Select-Object -First 1) } | Remove-Item -Recurse

So basically...here's how it goes:

  • Get-ChildItem -Recurse -Directory - Start scanning recursively looking for directories
  • $_.EnumerateFiles('*',1) - For each directory...Enumerate the files
    • EnumerateFiles will output its findings as it goes, GetFiles will output when it is done....at least, that's how it is supposed to work in .NET...for some reason in PowerShell GetFiles starts spitting out immediately. But I still use EnumerateFiles because in testing it was reliably faster.
    • ('*',1) means find ALL files recursively.
  • | Select-Object -First 1 - Stop at the first file found
    • This was difficult to test how much it helped. In some cases it helped tremendously, other times it didn't help at all, and in some cases it slowed it down by a small amount. So I really don't know. I guess this is optional.
  • | Remove-Item -Recurse - Remove the directory, recursively (ensures directories that contain empty sub directories gets removed)

If you're counting characters, this could be shortened to:

ls -s -ad | ? { -Not ($_.EnumerateFiles('*',1) | select -First 1) } | rm -Recurse
  • -s - alias for -Recurse
  • -ad - alias for -Directory

If you really don't care about performance because you don't have that many files....even more so to:

ls -s -ad | ? {!($_.GetFiles('*',1))} | rm -Recurse

Side note: While playing around with this, I started testing various versions with Measure-Command against a server with millions of files and thousands of directories.

This is faster than the command I've been using (above):

(gi .).EnumerateDirectories('*',1) | ? {-Not $_.EnumerateFiles('*',1) } | rm -Recurse
Chad Baldwin
  • 2,239
  • 18
  • 32
  • is there a missing bracket after -Not operator? e.g. ? `{ -Not () }` – tkokasih May 28 '21 at 13:09
  • @tkokasih , huh, you appear to be right...I don't know why that's necessary or how I didn't catch this when posting. But I will edit the post. Thanks! – Chad Baldwin May 29 '21 at 20:03
  • @tkokasih it appears this is only necessary if using `EnumerateFiles`, if you switch the scripts to use `GetFiles`, then the parens aren't needed, even with the `select -First 1`, very odd behavior. – Chad Baldwin May 29 '21 at 20:09
  • Does this not need to get a folder passed to it? I tried it and it didn't work but `Get-ChildItem $rootPath -Recurse` instead of `Get-ChildItem -Recurse` worked. – Ste Jun 24 '21 at 16:24
  • @Ste it shouldn't. Supplying the directory just tells it where you want to cleanup. Not supplying a directory says to cleanup the current directory. – Chad Baldwin Jun 26 '21 at 00:10
  • 1
    Quite destructive that way. I know it's only empty folders but something in the answer to warn people would be good. – Ste Jun 26 '21 at 09:38
  • @Ste , StackOverflow Rule #1...don't copy paste and run things from StackOverflow without knowing what it does and how it works. – Chad Baldwin Jun 26 '21 at 15:24
  • 1
    It still should be clear in the answer. After all the answer should fully explain what it does and how it works. – Ste Jun 26 '21 at 19:03
6
ls c:\temp -rec |%{ if ($_.PSIsContainer -eq $True) {if ( (ls $_.fullname -rec | measure |select -expand count ) -eq "0"  ){ ri $_.fullname -whatif}  }  }  
Loïc MICHEL
  • 24,935
  • 9
  • 74
  • 103
  • 1
    What does this script do exactly? It tried to access files in c:\windows\system32 – Stephen Oberauer Mar 05 '18 at 15:11
  • it deletes empty directory in c:\temp folder – Loïc MICHEL Mar 05 '18 at 17:42
  • 2
    thanks for explaining. It does, however seem to try to access the entire hard drive if c:\temp doesn't exist, so it's probably a good idea to check that the directory exists first. – Stephen Oberauer Mar 06 '18 at 12:04
  • 1
    You must use [-LiteralPath](https://stackoverflow.com/questions/33721892/is-get-childitem-recurse-broken-when-there-are-square-brackets-in-the-input-pat/55367757#55367757) with `.FullName`!!! – it3xl Jun 24 '19 at 20:43
  • 1
    Usually code-only answers get heavily downvoted. Some kind of insight as to how the code works or how the code addresses the submitted question would be helpful. – Joshua K Aug 18 '22 at 14:12
  • @StephenOberauer, that is an unfortunate side effect of how the (possibly positionally) implied `-Path` parameter works - see [GitHub issue #5699](https://github.com/PowerShell/PowerShell/issues/5699). Using `-LiteralPath c:\temp` instead of `c:\temp` (which is the same as `-Path c:\temp`) avoids the problem, as it fails instantly if the target dir. doesn't exist. – mklement0 Dec 04 '22 at 23:27
1

Assuming you're inside the parent folder of interest

gci . -Recurse -Directory | % { if(!(gci -Path $_.FullName)) {ri -Force -Recurse $_.FullName} }

For your case with $tdc it'll be

gci $tdc -Recurse -Directory | % { if(!(gci -Path $_.FullName)) {ri -Force -Recurse $_.FullName} }

DeepSpace101
  • 13,110
  • 9
  • 77
  • 127
  • 1
    You must use [-LiteralPath](https://stackoverflow.com/questions/33721892/is-get-childitem-recurse-broken-when-there-are-square-brackets-in-the-input-pat/55367757#55367757) with `.FullName`!!! – it3xl Jun 24 '19 at 20:40
1

If you just want to make sure, that you delete only folders that may contain subfolders but no files within itself and its subfolders, this may be an easier an quicker way.

$Empty = Get-ChildItem $Folder -Directory -Recurse |
Where-Object {(Get-ChildItem $_.FullName -File -Recurse -Force).Count -eq 0}

Foreach ($Dir in $Empty)
{
    if (test-path $Dir.FullName)
    {Remove-Item -LiteralPath $Dir.FullName -recurse -force}
}
  • 1
    You must use [-LiteralPath](https://stackoverflow.com/questions/33721892/is-get-childitem-recurse-broken-when-there-are-square-brackets-in-the-input-pat/55367757#55367757) with `.FullName`!!! – it3xl Jun 24 '19 at 20:40
0

Recursively removing empty subdirectories can also be accomplished using a "For Loop".

Before we start, let's make some subdirectories & text files to work with in $HOME\Desktop\Test

MD $HOME\Desktop\Test\0\1\2\3\4\5 
MD $HOME\Desktop\Test\A\B\C\D\E\F
MD $HOME\Desktop\Test\A\B\C\DD\EE\FF
MD $HOME\Desktop\Test\Q\W\E\R\T\Y
MD $HOME\Desktop\Test\Q\W\E\RR
"Hello World" > $HOME\Desktop\Test\0\1\Text1.txt
"Hello World" > $HOME\Desktop\Test\A\B\C\D\E\Text2.txt
"Hello World" > $HOME\Desktop\Test\A\B\C\DD\Text3.txt
"Hello World" > $HOME\Desktop\Test\Q\W\E\RR\Text4.txt

First, store the following Script Block in the variable $SB. The variable can be called later using the &SB command. The &SB command will output a list of empty subdirectories contained in $HOME\Desktop\Test

$SB = {
    Get-ChildItem $HOME\Desktop\Test -Directory -Recurse |
    Where-Object {(Get-ChildItem $_.FullName -Force).Count -eq 0}
}

NOTE: The -Force parameter is very important. It makes sure that directories which contain hidden files and subdirectories, but are otherwise empty, are not deleted in the "For Loop".

Now use a "For Loop" to recursively remove empty subdirectories in $HOME\Desktop\Test

For ($Empty = &$SB ; $Empty -ne $null ; $Empty = &$SB) {Remove-Item (&$SB).FullName}

Tested as working on PowerShell 4.0

Zelda64
  • 97
  • 1
  • 3
  • could you explain the "For Loop" bit? why do you keep invoking the $SB that returns a collection of all empty folders? – rob Nov 15 '16 at 14:18
  • I had the problem that `(Get-ChildItem $_.FullName -Force).Count` evaluated to the empty string instead of "0" when the directory was empty (this happens prior to Powershell 2.0, I guess). In that case, enclose the where statement's result with `@()`, like so: `@((Get-ChildItem $_.FullName -Force)).Count`. This will turn the statements's result into an array and "count" should always return a number. – user3792852 Nov 17 '16 at 10:06
  • 1
    @rob. I'm reposting for clarity. The following example will explain the for loop's purpose: Directory A (DirA) contains only empty subdirectories and nothing else. DirA itself will become empty once the empty subdirectories contained within it are deleted. DirA is not currently stored in the variable $SB because &$SB was called before DirA was empty. Therefore, &$SB must be called every time empty subdirectories are deleted because there might be newly emptied directories which need to be added to $SB. – Zelda64 Dec 07 '16 at 15:02
  • You must use [-LiteralPath](https://stackoverflow.com/questions/33721892/is-get-childitem-recurse-broken-when-there-are-square-brackets-in-the-input-pat/55367757#55367757) with `.FullName`!!! – it3xl Jun 24 '19 at 20:39
0

I have adapted the script of RichardHowells. It doesn't delete the folder if there is a thumbs.db.

##############
# Parameters #
##############
param(
    $Chemin = "" ,  # Path to clean
    $log = ""       # Logs path
)




###########
# Process #
###########


if (($Chemin -eq "") -or ($log-eq "") ){

    Write-Error 'Parametres non reseignes - utiliser la syntaxe : -Chemin "Argument"  -log "argument 2" ' -Verbose 
    Exit
}



#loging 
$date = get-date -format g
Write-Output "begining of cleaning folder : $chemin at $date" >> $log
Write-Output "------------------------------------------------------------------------------------------------------------" >> $log


<########################################################################
    define a script block that will remove empty folders under a root folder, 
    using tail-recursion to ensure that it only walks the folder tree once. 
    -Force is used to be able to process hidden files/folders as well.
########################################################################>
$tailRecursion = {
    param(
        $Path
    )
    foreach ($childDirectory in Get-ChildItem -Force -LiteralPath $Path -Directory) {
        & $tailRecursion -Path $childDirectory.FullName
    }
    $currentChildren = Get-ChildItem -Force -LiteralPath $Path
    Write-Output $childDirectory.FullName



    <# Suppression des fichiers Thumbs.db #>
    Foreach ( $file in $currentchildren )
    {
        if ($file.name -notmatch "Thumbs.db"){break}
        if ($file.name -match "Thumbs.db"){
            Remove-item -force -LiteralPath $file.FullName}

    }



    $currentChildren = Get-ChildItem -Force -LiteralPath $Path
    $isEmpty = $currentChildren -eq $null
    if ($isEmpty) {
        $date = get-date -format g
        Write-Output "Removing empty folder at path '${Path}'.  $date" >> $log
        Remove-Item -Force -LiteralPath $Path
    }
}

# Invocation of the script block
& $tailRecursion -Path $Chemin

#loging 
$date = get-date -format g
Write-Output "End of cleaning folder : $chemin at $date" >> $log
Write-Output "------------------------------------------------------------------------------------------------------------" >> $log
0

I wouldn't take the comments/1st post to heart unless you also want to delete files that are nested more than one folder deep. You are going to end up deleting directories that may contain directories that may contain files. This is better:

$FP= "C:\Temp\"

$dirs= Get-Childitem -LiteralPath $FP -directory -recurse

$Empty= $dirs | Where-Object {$_.GetFiles().Count -eq 0 **-and** $_.GetDirectories().Count -eq 0} | 

Select-Object FullName

The above checks to make sure the directory is in fact empty whereas the OP only checks to make sure there are no files. That in turn would result in files nexted a few folders deep also being deleted.

You may need to run the above a few times as it won't delete Dirs that have nested Dirs. So it only deletes the deepest level. So loop it until they're all gone.

Something else I do not do is use the -force parameter. That is by design. If in fact remove-item hits a dir that is not empty you want to be prompted as an additional safety.

Zack A
  • 688
  • 5
  • 11
  • 1
    $.getfiles and $.getdirectories() should be $_.getfiles and $_.getdirectories(). You may want to add a final line $empty.fullname | remove-item -whatif – Albert Oct 05 '19 at 17:37
  • You're right. I forgot to tag my post as code and the site removed the characters for some reason. I have corrected. Thanks – Zack A Jan 08 '20 at 18:13
0

Something like this works for me. The script delete empty folders and folders containing only folder (no files, no hidden files).

$items = gci -LiteralPath E:\ -Directory -Recurse
$dirs = [System.Collections.Generic.HashSet[string]]::new([string[]]($items |% FullName))
for (;;) {
    $remove = $dirs |? { (gci -LiteralPath $_ -Force).Count -eq 0 }
    if ($remove) {
        $remove | rm
        $dirs.ExceptWith( [string[]]$remove )
    }
    else {
        break
    }
}
unlikely
  • 398
  • 2
  • 8
0
$files = Get-ChildItem -Path c:\temp -Recurse -Force | where psiscontainer ; [array]::reverse($files)

[Array]::reverse($files) will reverse your items, so you get the lowest files in hierarchy first. I use this to manipulate filenames that have too long filepaths, before I delete them.

Muhammad Dyas Yaskur
  • 6,914
  • 10
  • 48
  • 73
Tommy
  • 1
0

This will remove up all empty folders in the specified directory $tdc. It is also a lot faster since there's no need for multiple runs.

    $tdc = "x:\myfolder" # Specify the root folder
    gci $tdc -Directory -Recurse `
        | Sort-Object { $_.FullName.Length } -Descending `
        | ? { $_.GetFiles().Count -eq 0 } `
        | % {
            if ($_.GetDirectories().Count -eq 0) { 
                Write-Host " Removing $($_.FullName)"
                $_.Delete()
                }
            }
0
#By Mike Mike Costa Rica
$CarpetasVacias = Get-ChildItem -Path $CarpetaVer -Recurse -Force -Directory | Where {(gci $_.fullName).count -eq 0} | select Fullname,Name,LastWriteTime
$TotalCarpetas = $CarpetasVacias.Count
$CountSu = 1
ForEach ($UnaCarpeta in $CarpetasVacias){
    $RutaCarp = $UnaCarpeta.Fullname
    Remove-Item -Path $RutaCarp -Force -Confirm:$False -ErrorAction Ignore
    $testCar = Test-Path $RutaCarp
    if($testCar -eq $true){ 
        $Datem = (Get-Date).tostring("MM-dd-yyyy HH:mm:ss")
        Write-Host "$Datem ---> $CountSu de $TotalCarpetas Carpetas Error Borrando Directory: $RutaCarp" -foregroundcolor "red"
    }else{
        $Datem = (Get-Date).tostring("MM-dd-yyyy HH:mm:ss")
        Write-Host "$Datem ---> $CountSu de $TotalCarpetas Carpetas Correcto Borrando Directory: $RutaCarp" -foregroundcolor "gree"
    }
    $CountSu += 1
}
General Grievance
  • 4,555
  • 31
  • 31
  • 45
  • 5
    While this code may solve the question, [including an explanation](//meta.stackexchange.com/q/114762) of how and why this solves the problem would really help to improve the quality of your post, and probably result in more up-votes. Remember that you are answering the question for readers in the future, not just the person asking now. Please [edit] your answer to add explanations and give an indication of what limitations and assumptions apply. – Suraj Rao Sep 09 '21 at 05:48
-1

This is a simple approach

dir -Directory | ? { (dir $_).Count -eq 0 } | Remove-Item
Mikeb
  • 781
  • 7
  • 24