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.

Working with Hash Tables

In last week’s Windows PowerShell Tip we introduced you to the .NET Framework class System.Collections.ArrayList, positioning this class as an alternative to the array class built into Windows PowerShell. (Why do you even need an alternative to the array class built into Windows PowerShell? Well, for one thing, the ArrayList class makes it easy to remove items from the array, something that’s nearly impossible to do otherwise.) In that same column we promised that, this week, we’d discuss another alternative to the generic Windows PowerShell array: the hash table. Well, never let it be said that the Scripting Guys don’t always keep their promises.

Note. OK, so technically we don’t always keep our promises. We just never want that to be said.

Although the term hash table might be new to many of you, there’s a good chance that you’re familiar with the concept; after all, a hash table is simply a collection of name-value pairs, very much like the FileSystemObject’s Dictionary object. For example, suppose you have a collection of US states and their capitals. Each state has one – and only one – capital (e.g., the state capital of Washington is Olympia), and a given city can be the capital of only one state. If you wanted to keep track of – and make use of – that information, you could construct a two-dimensional array. Or, you could take a much easier route and create a hash table.

Note. Yes, we know: we didn’t really go over the concept of key-value pairs, did we? But that’s OK. The Dictionary object explanation found in the Microsoft Windows 2000 Scripting Guide applies equally well to the Windows PowerShell hash table.

But enough with the talk; let’s see some code. Suppose you really would like a hash table containing a list of US states and their capitals; how would you go about creating such a thing? Well, here’s one way:

$states = @{"Washington" = "Olympia"; "Oregon" = "Salem"; California = "Sacramento"}

Granted this is a bit cryptic-looking at least at first glance; fortunately, however, it’s nowhere near as complicated as it might appear. All we’re doing here is assigning three sets of key-value pairs to a hash table named $states. As you can see, the syntax for creating a hash table involves using an at sign (@) followed by a pair of curly braces.

Note. The @ symbol is known as the at sign only in English; in other languages, it’s known as way cooler things, like maggot, monkey’s tail, and pickled herring.

So what goes inside those curly braces? Well, that’s where we specify the key-value pairs, using the syntax key = value; for example, "Washington" = "Olympia". Note to that we separate the individual key-value pairs using semicolons; hence the syntax "Washington" = "Olympia"; "Oregon" = "Salem".

And what will the variable $states be equal to after we issue our hash table command? Why, this, of course:

Name                           Value
----                           -----
California                     Sacramento
Washington                     Olympia
Oregon                         Salem

That’s pretty good, except for one thing: our hash table is far from complete. (After all, we only list 3 US states, and there are actually … uh … more than 3 states in the US.) So how do you add a new key-value pair to an existing hash table? Well, we don’t know about you, but here’s how the Scripting Guys do it:

$states.Add("Alaska", "Fairbanks")

Note. Yes, yes, we know: Fairbanks is not the capital of Alaska. We’ll get to that in just a moment.

Needless to say, there’s nothing too fancy going on here: we simply call the Add method, passing this method two parameters: the new key ("Alaska") and the new value ("Fairbanks"). Now take a look at the value of $states:

Name                           Value
----                           -----
California                     Sacramento
Alaska                         Fairbanks
Washington                     Olympia
Oregon                         Salem

Incidentally, that’s a good observation: you never know exactly where a new entry will appear in the hash table. In this case, for example, our new entry somehow became item 2 in a 4-item table. But that’s OK; before we go we’ll show you how to test for the existence of a key or a value regardless of their position in the hash table. And, just for the heck of it, we’ll show you how to sort the table as well.

But first things first. As the tidal wave of emails and phone calls has made very clear, the state capital of Alaska isn’t Fairbanks after all; it’s Juneau. Unfortunately, however, we’ve already added this incorrect key-value pair to our hash table. Now what are we going to do?

Well, one thing we could do is simply remove the offending item. Want to get rid of the key-value pair for Alaska? Okey-doke; what else would you use the Remove method for?

$states.Remove("Alaska")

Just to be on the safe side, let’s take a look at the value of $states:

Name                           Value
----                           -----
California                     Sacramento
Washington                     Olympia
Oregon                         Salem

That’s better, isn’t it?

Alternatively, we could have used the Set_Item method to change the value assigned to Alaska:

$states.Set_Item("Alaska", "Juneau")

Run that command and then take a look at the value of $states; it should be equal to the following:

Name                           Value
----                           -----
California                     Sacramento
Alaska                         Juneau
Washington                     Olympia
Oregon                         Salem

Incidentally, the Set-Item method has a corollary method named Get_Item; this method enables us to retrieve the value associated with a specific item in the hash table. Not sure which city is the capital of Oregon? Then run this command and find out for yourself:

$states.Get_Item("Oregon")

Now, suppose that the state of Oregon didn’t appear anywhere in your hash table; what happens if you issue the preceding command and the item can’t be found? Well, actually, nothing: you don’t get a return value of any kind, but you don’t get an error message, either. And that’s actually a bit of problem: it’s often a bit tricky to try to figure out what really happened when nothing seems to have happened.

Fortunately, you can use the ContainsKey and the ContainsValue methods to search for items in a hash table. Need to know if an item named Oregon really does exist in your hash table? Then just call the ContainsKey method, like so:

$states.ContainsKey("Oregon")

This method returns True if it finds an item named Oregon and False if it doesn’t find an item named Oregon. Likewise, you can search for a specified value by using the ContainsValue method:

$states.ContainsValue("Salem")

Note. If you just need to know how many items are in a hash table you can simply report back the value of the Count property: $states.Count.

Last, but surely not least, let’s talk about sorting a hash table. This can be a little tricky; this command will not work:

$states | Sort-Object

Why not? Well, in the preceding command the hash table is sent as a single object; thus there’s nothing for the Sort-Object cmdlet to sort. If we want to sort a hash table by Name we need to use the GetEnumerator method, which effectively sends each entry in the hash table across the pipeline as a separate object:

$states.GetEnumerator() | Sort-Object Name

Which, in turn, gives us output like this:

Name                           Value
----                           -----
Alaska                    Juneau
California                     Sacramento
Oregon                         Salem
Washington                     Olympia

Here’s a similar command, except that this one sorts the hash table by Value and (just because we wanted to show off a little) in descending order to boot:

$states.GetEnumerator() | Sort-Object Value -descending

And here’s what the output of that command looks like:

Name                           Value
----                           -----
Oregon                         Salem
California                     Sacramento
Washington                     Olympia
Alaska                         Juneau

Pretty cool, huh?