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.

Determining the Size of a Folder

As a general rule, Windows PowerShell makes your system administration life much, much easier. And that’s good, except for one thing: when something does go wrong people assume that they must be the ones at fault.

For example, in the past few weeks we’ve gotten several emails similar to this: “Hey, Scripting Guys. I’m having the darnedest time with the simplest PowerShell script you can imagine. All I want to do is determine the size of a folder, but I just can’t do it; no matter what I try the folder size comes out blank. What am I doing wrong?”

As it turns out, you’re not doing anything wrong. (Well, other than asking the Scripting Guys for help, that is.) Most likely you’re using a command similar to this one, a command that binds to the folder C:\Scripts and then returns some basic information, including the folder size (Length):

Get-Item C:\Scripts

And here’s the kind of information you’re likely to get back:

Directory: Microsoft.PowerShell.Core\FileSystem::C:\

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d----          4/1/2008  12:39 PM            scripts

And there you have it: a folder size of, well, nothing. We must have done something wrong!

Listen, take it easy, and, whatever you do, don’t panic. Like we said, this isn’t your fault; it’s actually PowerShell’s fault. For some reason PowerShell is unable to directly return the size of a folder. (But at least it’s in good company: WMI can’t return the size of a folder, either.)

So does that mean you’re out of luck when it comes to determining the size of a folder? Believe it or not, the answer is no, you’re not out of luck. Maybe you can’t directly determine the size of a folder, but you can indirectly determine the size of a folder by using code similar to this:

Get-ChildItem C:\Scripts | Measure-Object -property length -sum

Granted, this seems like a somewhat convoluted way to determine the size of a folder, but at least it does determine the size of a folder. What we’re doing with this command is using the Get-ChildItem cmdlet to return a collection of all the items found in the folder C:\Scripts. We’re then piping that collection to the Measure-Object cmdlet and asking Measure-Object to do two things:

  1. Look at the Length property for each item in the collection.

  2. Report back the sum of each and every item length.

What will that give us? That will give us output similar to this:

Count    : 58
Average  :
Sum      : 1244611
Maximum  :
Minimum  :
Property : length

So what’s the size of the folder C:\Scripts? According to Measure-Object it’s 1,244,611 bytes. (The Sum property will tell you the answer.)

That’s not bad; like we said, we did come up with the answer we were looking for. On the other hand, it’s still not easy to tell, at a single glance, what that answer is; that’s due both to the somewhat-cluttered output and to the fact that the answer is reported in bytes rather than, say, megabytes. With that in mind, let’s try running this souped-up block of code instead:

$colItems = (Get-ChildItem C:\Scripts | Measure-Object -property length -sum)
"{0:N2}" -f ($colItems.sum / 1MB) + " MB"

OK, so these two lines look really convoluted, don’t they? Maybe we better explain what’s going on here. In line 1 we’re doing what we just did: using Get-ChildItem to return a collection of all the items in the folder C:\Scripts, then asking Measure-Object to calculate the sum of the all the item lengths. However, this time we aren’t immediately reporting back the answer; instead, we’re taking that output and stashing it in a variable named $colItems.

In line 2 we then apply a little formatting to the output. To begin with, we use this construction to take the value of the Sum property and convert it to megabytes:

($colItems.sum / 1MB)

Note. You say you don’t understand how that line of code converts bytes to megabytes? Then you should take a look at our tip on Byte Conversion.

Once we’ve made the conversion we use this bit of code to display the value using just two decimal places:

"{0:N2}" -f

And yes, as a matter of fact we do have a tip on formatting numbers that explains how this all works. Thanks for asking!

Finally, we tack MB onto the end of the output. The net result? This:

1.19 MB

Much better.

As you can see, that all works great. Except for one thing: if we open up Windows Explorer and check the size of the C:\Scripts folder, Windows Explorer insists that the size should really be 3.60 megabytes:

Yikes! Did we do something wrong again?

Probably. But not when it comes to calculating the size of a folder using Windows PowerShell. When you look at the size of a folder in Windows Explorer, Windows Explorer tells you the total size of the folder; that includes not just the files found in the parent folder (C:\Scripts), but also any files found in subfolders of that folder. If we add up the files in the parent folder (which is what our script did) then we end up with a folder size of 1.19 megabytes. If we add the size of all the files found in all the subfolders of C:\Scripts then we end up with a folder size of 3.60 megabytes.

Of course, the script we just showed you doesn’t add up the size of all the files found in the subfolders of C:\Scripts. And that’s a problem if we want the absolute total size of the folder. If we do, well, then we’re going to have to modify our script a bit.

What’s that? How much is “a bit?” Well, as it turns out, not much at all; all we have to do is add the –recurse parameter to Get-ChildItem; that tells Get-ChildItem to return all the files in C:\Scripts plus all the files found in any (and all) subfolders of C:\Scripts. Here’s what our modified script looks like:

$colItems = (Get-ChildItem C:\Scripts -recurse | Measure-Object -property length -sum)
"{0:N2}" -f ($colItems.sum / 1MB) + " MB"

And here’s what we get back when we run the script:

3.60 MB

Cool, huh?

Let’s try something a little fancier. Suppose you’d like a complete breakdown on the size of C:\Scripts and each of its subfolders; you know, a report similar to this:

C:\Scripts -- 1.19 MB
C:\Scripts\BCD -- 0.02 MB
C:\Scripts\Forms -- 0.08 MB
C:\Scripts\Games -- 0.04 MB
C:\Scripts\Games\New Folder -- 0.02 MB
C:\Scripts\New Folder -- 2.23 MB
C:\Scripts\Test Folder -- 0.02 MB

How can we generate a report similar to that? Here’s one way:

$startFolder = "C:\Scripts"

$colItems = (Get-ChildItem $startFolder | Measure-Object -property length -sum)
"$startFolder -- " + "{0:N2}" -f ($colItems.sum / 1MB) + " MB"

$colItems = (Get-ChildItem $startFolder -recurse | Where-Object {$_.PSIsContainer -eq $True} | Sort-Object)
foreach ($i in $colItems)
    {
        $subFolderItems = (Get-ChildItem $i.FullName | Measure-Object -property length -sum)
        $i.FullName + " -- " + "{0:N2}" -f ($subFolderItems.sum / 1MB) + " MB"
    }

We’ve actually got a two-step process going on here. To begin with, we use this block of code to determine the size of the folder C:\Scripts, not including any subfolders:

$startFolder = "C:\Scripts"

$colItems = (Get-ChildItem $startFolder | Measure-Object -property length -sum)
"$startFolder -- " + "{0:N2}" -f ($colItems.sum / 1MB) + " MB"

That part we’ve already seen. Next, we use this line of code to return a collection of all the subfolders found in C:\Scripts (as well as any subfolders of those subfolders), all sorted by folder path:

$colItems = (Get-ChildItem $startFolder -recurse | Where-Object {$_.PSIsContainer -eq $True} | Sort-Object)

As you can see, we again use Get-ChildItem (and the –recurse parameter) to return a collection of items found in C:\Scripts. However, once we have the collection in hand we immediately pipe it to the Where-Object cmdlet; we then ask Where-Object to limit the collection to items where the PSIsContainer property is true ($True). What’s the point of that? You got it: if the PSIsContainer property is true that means we’re dealing with a folder rather than a file. The net effect here is that we’ve stripped all the files out of the collection, leaving us with nothing but the subfolders found in C:\Scripts.

And then, just to make things nice and neat, we pipe this filtered collection to Sort-Object, which will sort the folders by folder path.

The rest is easy. For starters, we set up a foreach loop that runs through all the subfolders in the collection:

foreach ($i in $colItems)

Inside that foreach loop we use these two lines of code to connect to each subfolder (something we can do by referencing the FullName property, which is the same thing as the folder path) and calculate its size:

$subFolderItems = (Get-ChildItem $i.FullName | Measure-Object -property length -sum)
$i.FullName + " -- " + "{0:N2}" -f ($subFolderItems.sum / 1MB) + " MB"

That’s all there is to it.

Incidentally, there is another way to determine the total size of a folder. It might seem a little like cheating, and it’s not a “pure” PowerShell solution, but the following script will return the size of the folder C:\Scripts (including all its subfolders):

$objFSO = New-Object -com  Scripting.FileSystemObject
"{0:N2}" -f (($objFSO.GetFolder("C:\Scripts").Size) / 1MB) + " MB"

What we’re doing here is using our old pal the Scripting.FileSystemObject to determine the size of the folder. In line 1 we use the New-Object cmdlet (plus the –com parameter, required because we’re creating a COM object) to create a new instance of the FileSystemObject. In line 2 we use this syntax (and the GetFolder method) to bind to the folder C:\Scripts and return just the value of the Size property:

($objFSO.GetFolder("C:\Scripts").Size)

And then we fancy things up a bit, converting the size to megabytes, formatting the output so it displays just two decimal places, and tacking an MB onto the end of that output. The net result should look a little something like this:

3.60 MB

Admittedly, the FileSystemObject is old and – allegedly – outdated technology, and it’s hardly the most glamorous thing in the world. On the other hand, it does get the job done.

Which, all things considered, makes it an awful lot like the Scripting Guys.

Well, except for one thing: like we said, the FileSystemObject does get the job done.

We’ll see you all next week.