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.

Using Calculated Properties

One of the things that make Windows PowerShell so much fun to play around with is this: no sooner do you learn something very cool (and very useful) then you discover that there are even cooler (and more useful) ways to carry out this very same task.

For example, when you first started trying your hand at PowerShell you were probably excited to learn about the Get-ChildItem cmdlet; after all, Get-ChildItem makes it possible for you to retrieve information about all the files in a folder, and to do so using a single command:

Get-ChildItem C:\Test

Type the preceding at the command prompt (or call it from a script) and you’ll get back something similar to this:

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---         4/2/2008    9:11 AM     905216 challenge.mdb
-a---        2/12/2008   10:21 AM     229376 pool.mdb
-a---         7/3/2007    2:14 PM     266240 scores.mdb
-a---        1/23/2008    1:39 PM     328620 wordlist.txt

Pretty cool, huh?

Yes, pretty cool indeed. Of course, that’s not quite the information you were looking for; what you were really hoping to get back was the Name, CreationTime, and Length (size) of each file in the folder C:\Test. But, hey, 2 out of 3 ain’t bad, right?

Wrong. Why settle for 2 out of 3 when you get back all 3 property values? Most likely somewhere down the road you learned about the Select-Object cmdlet, a cmdlet that lets you specify the property values you want to retrieve, even if – as is the case with CreationTime – those property values don’t appear by default. (In other words, if you just call Get-Cmdlet you won’t see the value of the CreationTime property, even though that’s a legitimate property of a file or folder.) By piping your data to Select-Object you can pick the property values you want returned, and even specify the order in which those values will appear in your output:

Get-ChildItem C:\Test | Select-Object Name, CreationTime, Length

What are we going to get back when we run that command? We’re going to get back something that looks like this:

Name                                    CreationTime                       Length
----                                    -------------                       ------
challenge.mdb                           12/17/2007 9:33:24 PM               905216
pool.mdb                                1/14/2008 8:16:15 AM                229376
scores.mdb                              1/3/2007 8:00:00 AM                 266240
wordlist.txt                            1/3/2007 8:00:00 AM                 328620

Now that is cool, and it’s everything you could ever ask for: we see the Name, CreationTime, and the length of each file. It’s the perfect command, and the perfect output.

Well, OK: it’s almost the perfect command, and almost the perfect output. It’s pretty good, except that the file size is reported in bytes, and we’re used to seeing file sizes reported in kilobytes. But hey, short of writing a more complicated script that can loop through all the files in the collection and calculate the size in kilobytes, well, what are you going to do?

Here’s what you’re going to do:

Get-ChildItem C:\Test | Select-Object Name, CreationTime,  @{Name="Kbytes";Expression={$_.Length / 1Kb}}

What we’re doing here is taking advantage of a cool – but little-know – PowerShell feature: the calculated property. A calculated property is pretty much what the name implies: it’s a property of an object, but not an inherent, built-in property of the object. Instead, it’s a property we create ourselves by, well, performing a calculation (i.e., running a script block). As you probably know by now, files don’t have a built-in property named Kbytes, a property that returns the size of the file in kilobytes. Therefore, we went ahead and created that property ourselves. And here’s the output we got back:

Name                                    CreationTime                       Kbytes
----                                    -------------                       ------
challenge.mdb                           12/17/2007 9:33:24 PM                  884
pool.mdb                                1/14/2008 8:16:15 AM                   224
scores.mdb                              1/3/2007 8:00:00 AM                    260
wordlist.txt                            1/3/2007 8:00:00 AM           320.91796875

That’s nice, but how exactly did we go about creating a calculated property? Well, as you can see, the first part of our command is pretty simple; we just use Get-ChildItem to return a collection of all the items found in the folder C:\Test:

Get-ChildItem C:\Test

The next part is pretty simple, too: we pipe the data returned by Get-ChildItem to the Select-Object cmdlet, and ask Select-Object to grab two property values for us, Name and Creation Time. That’s what this chunk of code is for:

Select-Object Name, CreationTime,

But wait, we’re done yet. We also ask Select-Object to grab a third property value, a calculated property we named Kbytes:

@{Name="Kbytes";Expression={$_.Length / 1Kb}}

Now, don’t panic; this calculated property is much simpler than it might first appear. To specify a calculated property we need to create a hash table; that’s what the @{} syntax does for us. Inside the curly braces we specify the two elements of our hash table: the property Name (in this case, Kybtes) and the property Expression (that is, the script block we’re going to use to calculate the property value). The Name property is easy enough to specify; we simply assign a string value to the Name, like so:

Name="Kbytes"

And, believe it or not, the Expression property (which is separated from the name by a semicolon) isn’t much harder to configure; the only difference is that Expression gets assigned a script block rather than a string value:

Expression={$_.Length / 1Kb}

So what goes inside that script block? Well, we want to know the size of the file in kilobytes; that’s something we can do by taking the Length property and dividing it by the numeric constant 1Kb. In other words, this is the calculation we want to perform:

$_.Length / 1Kb

So guess what goes inside our script block? You got it: in this case, our script block is simply the command that converts the file length to kilobytes. That’s all there is to it..

Let’s show you a simpler – if somewhat less useful – example. Suppose you’d like to display the name of each file plus the name of that file in all uppercase letters. (We told you that this example might not be all that useful.) Let’s take a look at a command that can carry out this somewhat-less-than-useful task:

Get-ChildItem C:\Test | Select-Object Name, @{Name="UCaseName"; Expression={$_.Name.ToUpper()}}

As you can see, we’re asking Select-Object to give us back the Name property as well as a calculated property we named UCaseName. Let’s take a look at the Expression for this calculated property:

Expression={$_.Name.ToUpper()}

Again, we’re doing nothing more than assigning a script block to the Expression property. Inside that script block we’re simply taking the Name property and applying the ToUpper method; needless to say, that’s going to create an all-uppercase version of the file name. When we execute this command we should get back the following:

Name                   UCaseName
----                   ---------
challenge.mdb          CHALLENGE.MDB
pool.mdb               POOL.MDB
scores.mdb             SCORES.MDB
wordlist.txt           WORDLIST.TXT

Incidentally, UCaseName really is a property that gets applied to the objects returned by Get-ChildItem. Suppose we take our previous command and, rather than directly outputting the information to the screen, store that data in a variable named $a:

$a = (Get-ChildItem C:\Test | Select-Object Name, @{Name="UCaseName"; Expression={$_.Name.ToUpper()}})

Now let’s set up a foreach loop to loop through each item in $a. Inside that loop, we’re going to echo back the value of the UCaseName property, a property which we just invented a minute ago:

foreach ($i in $a) {$i.UCaseName}

And what do you suppose we’re going to get back? You got it:

CHALLENGE.MDB
POOL.MDB
SCORES.MDB
WORDLIST.TXT

Nice.

Here’s a slightly more-useful command for you. This one retrieves all the files in C:\Test and then report back the age of those files. To do that, we’re going to use a calculated property named Age the takes the current date and time, subtracts the creation date and time of each file, and then reports back the file age in days. That command looks like this:

Get-ChildItem C:\Test | Select-Object Name, @{Name="Age";Expression={ (((Get-Date) - $_.CreationTime).Days) }}

And the output looks like this:

Name                                      Age
----                                      ---
challenge.mdb                             128
pool.mdb                                  101
scores.mdb                                477
wordlist.txt                              477

And remember, Age is a real, live property of the objects returned by this instance of Get-ChildItem. Would you like to see these files sorted by age? Then just pipe the results to Sort-Object:

Get-ChildItem C:\Test | Select-Object Name, @{Name="Age";Expression={ (((Get-Date) - $_.CreationTime).Days) }} |
Sort-Object Age

Here’s what we get back:

Name                                      Age
----                                      ---
pool.mdb                                  101
challenge.mdb                             128
wordlist.txt                              477
scores.mdb                                477

It doesn’t get any better that that.

Or does it? Before we go let’s take one last look at our original calculated property, the one that determined the size of a file in kilobytes. As you might recall, that gave us output that looked like this:

Name                                    CreationTime                       Kbytes
----                                    -------------                       ------
challenge.mdb                           12/17/2007 9:33:24 PM                  884
pool.mdb                                1/14/2008 8:16:15 AM                   224
scores.mdb                              1/3/2007 8:00:00 AM                    260
wordlist.txt                            1/3/2007 8:00:00 AM           320.91796875

That’s pretty good output, too, except for one thing. Take a look at the size of the file Wordlist.txt:

320.91796875

Yuck. Isn’t there anything we can do about that?

You already knew the answer to that one, didn’t you? Let’s take a look at another one-line PowerShell command, a command that reports back the Name and Length of each file in C:\Test. This time around, however, we’ve used a .NET Framework formatting string to specify that we want 0 decimal places ({0:N0}) in our answer:

Get-ChildItem C:\Test | Select-Object Name, @{Name="Kbytes";Expression={ "{0:N0}" -f ($_.Length / 1Kb) }}

Good point: this is a complicated little script block now, isn’t it? After all, it not only needs to calculate the size of the file in kilobytes but it also needs to properly format the resulting file size. Are PowerShell and its calculated properties up to the task? Take a peek for yourself:

Name                   Kbytes
----                   ------
challenge.mdb          884
pool.mdb               224
scores.mdb             260
wordlist.txt           321

We’ll see you all next week.