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
$a | Where-Object {$_.GetFiles().Count -eq 0} | select -expand FullName | remove-item -whatif
– PyjamasRemove-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. – Pyjamas.FullName
then use Get-ChildItem's -LiteralPath !!! – Perineuritis