Windows PowerShell Tip of the Week

Here’s a quick tip on working with Windows PowerShell. These are published every week for as long as we can come up with new tips. If you have a tip you’d like us to share or a question about how to do something, let us know.

Find more tips in the Windows PowerShell Tip of the Week archive.

Finding All the Empty Folders in a Directory Tree

Awhile back one of our weekly tips explained how to use Windows PowerShell to determine the size of a folder. That was one of the worst mistakes the Scripting Guys have ever made. Not because the approach we presented was wrong, and not because it wasn’t a useful thing for system administration scripters to know about. No, it was a mistake because it triggered a flood of email, with most of these messages saying pretty much the same thing: “It’s useful to know the size of a folder, but what I really need to do is identify all the empty folders in a directory tree. Now that would make a good PowerShell tip of the week.”

In turn, that can mean only one thing: for once, at least, we’re going to have a good Windows PowerShell tip of the week. Here’s a simply little script that reports back all the empty folders (for our purposes, defined as any folder that doesn’t have at least one file in it) in the specified directory tree:

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

As you can see, there’s not that much to this script, but there are definitely a few things that need explaining. To begin with, we use the Get-ChildItem cmdlet to retrieve a collection of all the items found in the folder C:\Scripts. Note that we included the –recurse parameter as well. That causes Get-ChildItem to retrieve data from all the subfolders of the target folder, in addition to the files and folders found in C:\Scripts itself.

Of course, we don’t really need all the items found in the folder C:\Scripts; the only items we really need to look at are the folders. Therefore, we pipe the entire collection to the Where-Object cmdlet, which promptly weeds out everything that isn’t a folder. And how does Where-Object do that? By picking out only items where the PSIsContainer property is equal to True ($True). As the name sort of implies, the PSIsContainer property will return True only if the object happens to be a container; in other words, if the object happens to be a folder. If the object in question turns out to be a file then Where-Object will simply discard it.

After Where-Object finishes doing its thing we then pipe the filtered collection to yet another instance of Where-Object; that’s what we do in the first part of line 2:

$a | Where-Object {$_.GetFiles().Count -eq 0}

Believe it or not, this chunk of code picks out all the empty folders for us. How? Well, as it turns out, any folder object returned by Get-ChildItem or Get-Item includes a method named GetFiles. As this name clearly implies, GetFiles can be used to retrieve a collection of all the files found in the specified folder.

Don’t believe us? Then try running this little script and see what happens:

$a = Get-Item C:\Test
$a.GetFiles()

When you run this script you should get back a list of all the files found in the folder C:\Test:

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---         2/29/2008   9:20 PM       1059 280.txt
-a---         5/22/2008   9:33 AM        306 test.txt
-a-hs         5/16/2008  11:34 AM       7168 Thumbs.db

Note. Incidentally, there’s an analogous method – GetDirectories – that can return all the top-level subfolders found in a folder.

You know, now that we think about it, we weren’t completely accurate when we said that GetFiles returns a list of files; instead, it brings back an array (a FileInfo array, to be precise). Do we care about that? You bet we do: because this is an array, we can use the Count property to determine the number of items in the array. And, if you look closely at the code, you’ll see that this is exactly what we do with Where-Object:

{$_.GetFiles().Count -eq 0}

Admittedly, this is a bit odd-looking; nevertheless, it makes sense. As you know, the variable $_ simply represents the current object in the pipeline. Because all the objects in the pipeline are folders, we can use the GetFiles method to retrieve all the files in the folder; in addition, we can use the Count property to determine the number of files GetFiles returned. And why do we need to know the number of files in the folder? You got it: if the folder contains 0 files then, by definition, this is an empty folder.

Amazing how that all worked out, isn’t it?

At this point we’re almost done: all we have left to do is pipe the collection of empty folders to the Select-Object cmdlet; in turn, we ask Select-Object to pick out – and display – only the FullName property:

$a | Where-Object {$_.GetFiles().Count -eq 0} | Select-Object FullName

And what will that give us? That will give us a list of all the empty folders found in the C:\Scripts directory tree:

FullName
--------
C:\Scripts\Empty
C:\Scripts\Empty Folder 2
C:\Scripts\Empty\ Empty Subfolder
C:\Scripts\New Folder\Empty Subfolder Three Levels Deep

Nice, huh?

And yes, we know: PowerShell people love to carry out tasks like this using a single line of code. Well, that’s fine; this command should do the trick:

(Get-ChildItem C:\Scripts -recurse | Where-Object {$_.PSIsContainer -eq $True}) |
Where-Object {$_.GetFiles().Count -eq 0} | Select-Object FullName

Note. Yes, we know: that looks like two lines of code. But that’s just because we can’t display long lines of code on our Web pages.

Or, if you don’t mind using aliases (like gci for Get-ChildItem and ? for Where-Object), you can return a list of empty folders with even less typing:

(gci C:\Scripts -r | ? {$_.PSIsContainer -eq $True}) | ? {$_.GetFiles().Count -eq 0} | select FullName

And there you have it. We’ll see you all next week.