The Dictionary Object

By The Microsoft Scripting Guys

Sesame Script

Welcome to Sesame Script, the column for beginning script writers. The goal of this column is to teach the very basics of Windows scripting for system administration automation. We’ll provide you with the information you’ll need to begin reading and understanding scripts and to start modifying those scripts to suit your own needs. If there’s anything in particular about scripting you’re finding confusing, let us know; you’re probably not alone.

Check the Sesame Script Archives to see past articles.

On This Page

Introduction
The Dictionary Object
A Unique Individual
An Example that Doesn’t Use the Dictionary Object
An Example that Does Use the Dictionary Object
Other Things You Should Know About the Dictionary Object
More Information
In Closing

Introduction

Was that occassionally, occasionally, or occassionaly? What in the world is a clevis? Should I be using e-mail as a verb? How do you pronounce puisne? No, these are not difficult questions. Granted, you might not know the answers to all of them off the top of your head, but it’s easy enough to find those answers: just check the dictionary. Dictionaries are great inventions, aren’t they? Everything is in order, everything is easy to find, and dictionaries are packed with information about practically every word in a given language, and even some in other languages. Where would we be without dictionaries?

Let’s see - we’d probably spell though as tho or thoe, laugh would be a thing of the past, and many people would never know what scabies is. Maybe dictionaries weren’t such a great idea after all….

Okay, despite the difficulties proliferated by dictionaries (at least in English), they really are incredibly useful, so useful that there are even different types of dictionaries. There are crossword puzzle dictionaries, Scrabble dictionaries, medical dictionaries, and - believe it or not - a scripting Dictionary.

The Dictionary Object

If you’re now imagining a book with entries like CScript and For-Next, all nicely alphabetized and defined, well, that’s not what we’re talking about. You can find something like that on MSDN, but it’s called an SDK and it more closely resembles an encyclopedia (but without all the cool pictures). The dictionary we’re talking about is the Scripting.Dictionary object.

The Dictionary object doesn’t provide pronunciations or even definitions, but it does provide an easy way to store and look up information within a script. Here’s how it works.

A Unique Individual

A Dictionary object is a little bit like an array in that it can hold a list of values. Those values can be of just about any type: strings, dates, numbers, and so on. You can use a For Each loop to loop through the objects in a Dictionary object. However, while an array is a great way to iterate through a list of values, its functionality is somewhat limited compared to the Dictionary object.

Rather than being just a simple list of values, a Dictionary object consists of a collection of what are called key-item pairs. That sounds like a fancy technical term, doesn’t it? It’s really not so fancy - it just means that each entry in the list consists of two elements: a key and an item. See, now “key-item pair” just seems very unimaginative doesn’t it? A key is a unique entry: no two keys within a single Dictionary object can be the same. The item is just a value that goes along with a particular key. You can have as many duplicate items as you want, it’s only the keys that need to be unique. For example, your Dictionary could look like this:

Key Myer

Accountant

Pilar Ackerman

Clerk

Ellen Adams

Accountant

Michael Holm

Gardener

The names are the keys, and the occupations are the items. As you can see, no two names are alike, but we do have more than one Accountant.

Note: You can use the same value for the key and the item, like this:

Key Myer Key Myer
Pilar Ackerman Pilar Ackerman
Ellen Adams Ellen Adams
Michael Holm Michael Holm

We’re not entirely sure why you’d want to, but it does come up sometimes. Doing this does not change the fact that the keys all must be unique.

If you’ve ever worked with a database you might be thinking that this looks pretty familiar. This is amazing: so far we’ve compared the Dictionary object to a dictionary, an array, and now a database. Wow, what is this thing, really?

It’s a little bit of everything. It gives you the power of looking up entries, as well as the unique identification provided in dictionaries and databases; it gives you, the ease of use of dictionaries and arrays;, and, here’s the one we haven’t mentioned yet, the Dictionary object works in memory, just like an array. You create the Dictionary object within your script, use the object for whatever you need to use it for, then it disappears when the script ends.

An Example that Doesn’t Use the Dictionary Object

All right, enough of this theory of the dictionary universe; we’re sure you’re wondering by now how this thing works and what in the world you’d actually use it for. We can help you with the former, but while we can give you a suggestion for the latter you’ll be much better able to determine good uses for this object in your own situation than we are.

The best thing to do, of course, is to give you an example. Suppose you want to find out the state of all services on a machine. That’s simple; just use a WMI query, like this:

strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colServices = objWMIService.ExecQuery _
    ("Select * from Win32_Service")

For Each objService in colServices
    Wscript.Echo "Name: " & objService.Name
    Wscript.Echo "State: " & objService.State
Next

In this script we connect to the WMI service on the local computer and then query the Win32_Service class to get a collection of all the services on the computer. (You could run this query against a remote computer by changing the value of strComputer from a dot [.] to the name of the remote computer.) We then simply loop through the collection of services using a For Each loop and echo back the Name and State properties of each service. When we run the script we get back something like this:

Name: Alerter
State: Stopped
Name: ALG
State: Running
Name: AppMgmt
State: Stopped
Name: aspnet_state
State: Stopped
...

Nice, huh? True, we could have formatted it a little better and made it a little more readable, or even written the output to an Excel spreadsheet. But what if what we really wanted to do was list the services that are stopped? Well, in that case we could just change our query from

Select * from Win32_Service

to

Select * from Win32_Service Where State = 'Stopped'

This gives us a collection of all the stopped services, which enables us to echo back the name of the stopped services, like this:

strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colServices = objWMIService.ExecQuery _
    ("Select * from Win32_Service Where State = 'Stopped'")

Wscript.Echo "Stopped Services:"
For Each objService in colServices
    Wscript.Echo objService.Name
Next

This script is almost identical to the previous one; for one thing, it starts out by connecting to the WMI service on the local computer. We then query for all services, but this time specifying that we want only the services where the State is equal to “Stopped”. We then loop through the collection of stopped services and echo the name of each service.

But now suppose you also want some information about the services that are running? You wouldn’t be able to read the query results into an array because an array (at least not the kind of arrays beginning scripters are used to) can hold either the name of each service or the state but not both. Instead you’d end up doing multiple queries trying to get the information you’re after.

Or, you could go the easy route and use the Dictionary object.

An Example that Does Use the Dictionary Object

First things first. We can’t list services or do anything else with the Dictionary object until we have keys and items in the object. So, as we typically try to do here in Sesame Script, we’ll start with step one, creating and populating the Dictionary object.

strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colServices = objWMIService.ExecQuery _
    ("Select * from Win32_Service")

Set objDictionary = CreateObject("Scripting.Dictionary")

For Each objService in colServices
    objDictionary.Add objService.Name, objService.State
Next

In this script we once again connect to WMI on the local computer, then run a query to retrieve all the services on the computer. Nothing new there. Then we run into this line:

Set objDictionary = CreateObject("Scripting.Dictionary")

All we’re doing here is creating an instance of the Dictionary object, or, more precisely, the Scripting.Dictionary object. Once we have a Dictionary object we set up a For Each loop to loop through our collection of services and add each one to the Dictionary object. We add the services to the Dictionary object by using, what else, the Add method:

objDictionary.Add objService.Name, objService.State

As you can see, the Add method takes two parameters. The first is the value we want to use as the key to our dictionary entry, in this case the Name of the service. The second is the value we want to use for the item; here we’re using the State of the service.

We loop through the collection of services, adding the names of all the services and the corresponding states of those services to the Dictionary object. Because we don’t echo anything or do anything else the script just ends at that point. How do we know those entries were actually added to the Dictionary? Well, the fact that the script didn’t crash is one clue. A better clue would be to actually do something with those entries. Let’s start by simply listing all the stopped services:

strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colServices = objWMIService.ExecQuery _
    ("Select * from Win32_Service")

Set objDictionary = CreateObject("Scripting.Dictionary")

For Each objService in colServices
    objDictionary.Add objService.Name, objService.State
Next

colKeys = objDictionary.Keys
For Each strKey in colKeys
    If objDictionary.Item(strKey) = "Stopped" Then
        Wscript.Echo strKey
    End If
Next

We start out with our previous script, retrieving all the services and adding their names and states to the Dictionary object. Now it starts to get interesting. (You mean it hasn’t been interesting so far?) The first thing we do is get a collection of all the keys we added to our Dictionary object. We do that by calling the Keys method of the Dictionary object:

colKeys = objDictionary.Keys

The colKeys variable now contains a collection of all the keys in our Dictionary object, which, as we know, happens to be the names of all the services running on the computer. (We know that because we just added them with the Add method, remember?) The next thing we do is loop through that collection of keys (services) using a For Each loop. For each key in our collection, we want to check the Item property, which contains the state of the service, and see if the state is “Stopped”. How do we check the Item property? Like this:

If objDictionary.Item(strKey) = "Stopped" Then

As we already mentioned, keys must be unique, but not items. That means there’s really no way to directly access a particular Dictionary entry based on item, you have to access an entry based on the unique key. (You can look for all the items - we’ll show you how to do that in a minute.) To do that we use the Item property and pass it the key of the entry we’re interested in. Since we’re looping through all the keys, we use the value of each key, strKey, and pass that to the Item property to get the value of the item associated with that key. Does that make sense? Okay, let’s say our key contains the service Alerter. We pass Alerter to the Item property to find out the state of the Alerter service, which is contained in the item part of the key-item pair for that entry. Does it make sense now? We hope so.

In our If Then statement we retrieve the item associated with the key and compare that to “Stopped,” the state we’re looking for. If the item is equal to Stopped we echo back the name of the key (service):

If objDictionary.Item(strKey) = "Stopped" Then
        Wscript.Echo strKey
    End If

If the value of the item is not Stopped we ignore it and loop around to the next key in the collection. When we’re done we’ll have a list of all the services that were stopped at the time the query was run. (And yes, it is possible that a service could have been started in the fraction of a second it took us to get the values into and out of the Dictionary object, That means that there’s a small possibility you’re results aren’t 100% accurate. But, hey, give us a break: this is just an example!)

What have we accomplished so far? Well, basically the same thing we had accomplished in our non-Dictionary example. Pretty compelling, isn’t it? All right then, maybe we should take this a step farther. How about we get a list of all the Stopped processes, and a list of all the Running processes? This requires just a small addition to our previous script:

strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colServices = objWMIService.ExecQuery _
    ("Select * from Win32_Service")

Set objDictionary = CreateObject("Scripting.Dictionary")

For Each objService in colServices
    objDictionary.Add objService.Name, objService.State
Next

colKeys = objDictionary.Keys
Wscript.Echo "Stopped:"
For Each strKey in colKeys
    If objDictionary.Item(strkey) = "Stopped" Then
        Wscript.Echo strKey
    End If
Next

Wscript.Echo
Wscript.Echo "Running:"
For Each strKey in colKeys
    If objDictionary.Item(strkey) = "Running" Then
        Wscript.Echo strKey
    End If
Next

All we’ve done is add a few Wscript.Echo statements, copied our existing For Each loop ,and made one small change. Really simple. (Yes, really.) After we retrieve our collection of keys we echo “Stopped:” because we want to separate our Stopped list from our Running list. After we’ve echoed all the stopped services we put in a Wscript.Echo statement to insert a blank line - we want an obvious separation between our two lists - and then we echo “Running:” to introduce our list of running services. After that we have a For Each loop that’s identical to the loop that checks for stopped services, but we replace Stopped with Running in the If Then statement.

See, it really is that simple. Here’s what the results look like:

Stopped:
Alerter
AppMgmt
aspnet_state
Ati HotKey Poller
...

Running:
AdobeActiveFileMonitor
AudioSrv
BITS
CcmExec
CryptSvc
DcomLaunch
Dhcp
...

Other Things You Should Know About the Dictionary Object

There are plenty of other things you can do with the Dictionary object.

Retrieving all the Items

For starters, we said we’d show you how to get a list of all the items in a Dictionary. Remember how we got a list of all the keys?

colKeys = objDictionary.Keys
For Each strKey in colKeys
    If objDictionary.Item(strkey) = "Stopped" Then
        Wscript.Echo strKey
    End If
Next

We used the Keys method to get a collection of all the keys. We then used a For Each loop to loop through that collection. We used an If Then statement to check the item associated with each key, then echoed back the keys we were interested in. If we take out the If Then statement we’re simply echoing back all the keys:

colKeys = objDictionary.Keys
For Each strKey in colKeys
    Wscript.Echo strKey
Next

We do the exact same thing to list all the items:

colItems = objDictionary.Items
For Each strItem in colItems
        Wscript.Echo strItem
Next

As you can see, all we did was replace the Keys method with the Items method. (Yes, we also changed the variable names, since it’s kind of nice for the variable names to have something to do with the data the variable contains.) Retrieving a list of items isn’t always quite as useful as a list of keys because, depending on the values in your key-item pair, you could end up with a list something like this:

Running
Stopped
Running
Stopped
Stopped
Stopped
Stopped
...

Checking for Existing Keys

We’ve mentioned - more than once, which you know if you’ve been paying attention - that each key within a Dictionary object must be unique. What happens if you try to add a key that already exists? For example, what happens if you run this script:

Set objDictionary = CreateObject("Scripting.Dictionary")

objDictionary.Add "Key1", "Item"
objDictionary.Add "Key2", "Item"
objDictionary.Add "Key1", "Item"

Give it a try. We’ll wait.

Did you try it? No, don’t just wait for us to give you the answer, try it first.

Okay, as you’ve discovered, here’s what happens:

C:\Scripts\test.vbs(5, 1) Microsoft VBScript runtime error: This key is already
associated with an element of this collection

Yes, you get an error. On line 5, the line where we tried to add “Key1” for the second time, we’re told that the key we’re trying to add is already associated with an element in this Dictionary object. While it’s nice that we get a comprehensible error message, it would be even nicer if our script didn’t crash. We can stop the script from crashing by checking for the existence of keys before we try to assign them. We do this with the Exists method. (Wow, this is so intuitive it’s almost scary.)

Set objDictionary = CreateObject("Scripting.Dictionary")

objDictionary.Add "Key1", "Item"
objDictionary.Add "Key2", "Item"

If objDictionary.Exists("Key1") Then
    Wscript.Echo "Key1 exists"
Else
    Wscript.Echo "Adding Key1"
    objDictionary.Add "Key1", "Item"
End If

We start this script by creating the Scripting.Dictionary object and adding two key-item pairs. We then use the Exists method in an If Then statement to check whether an entry exists in the Dictionary with the key Key1. If it does we echo back that fact; if it doesn’t we echo a message that we’re adding the key, and we add the key to the Dictionary. Here’s what we get back:

Key1 exists

The Exists method comes in really handy when you’re doing something like reading in a list of servers from a text file and you want to ensure a server isn’t listed twice.

Case Sensitivity

One really important thing to know about Dictionary keys is that, by default, they’re case-sensitive. That means that you can have these two keys and they’ll still be considered unique:

Server1
SERVER1

This could cause problems if you’re reading from a text file, a spreadsheet, or a database, any place where the data entry might not have been consistent when it comes to letter casing. Let’s try our Exists script (from the previous section) again, but this time we’ll try to add what we might assume to be a duplicate key:

Set objDictionary = CreateObject("Scripting.Dictionary")

objDictionary.Add "KEY1", "Item"

If objDictionary.Exists("Key1") Then
    Wscript.Echo "Key1 exists"
Else
    Wscript.Echo "Adding Key1"
    objDictionary.Add "Key1", "Item"
End If

As you can see, we’re adding an entry to the Dictionary with a key of KEY1. When we check for the existence of Key1, we get this message:

Adding Key1

In other words, the Dictionary object didn’t recognize Key1 as being the same as KEY1; therefore, it added a new entry to the Dictionary. We can change this default behavior by changing the value of the CompareMode property. To make the keys case-insensitive we need to change this value to 1:

Const TextMode = 1
Set objDictionary = CreateObject("Scripting.Dictionary")

objDictionary.CompareMode = TextMode

objDictionary.Add "KEY1", "Item"

If objDictionary.Exists("Key1") Then
    Wscript.Echo "Key1 exists"
Else
    Wscript.Echo "Adding Key1"
    objDictionary.Add "Key1", "Item"
End If

We start by setting a constant, TextMode, to 1. Why did we call this constant TextMode? Because that’s the technical term for making this case-insensitive, and we like to throw technical terms in every once in a while so we can sound smart (we need all the help we can get). But really, that helps the CompareMode make more sense; we’re setting the mode that the Dictionary object will use when keys are compared for uniqueness. There are two possible modes: text mode, which means not case sensitive; and binary mode, the default, which means case-sensitive. The CompareMode value for text mode is 1, the CompareMode value for binary mode is 0. Now that we’ve set CompareMode to 1, our script returns this:

Key1 exists

More Information

There are other things you can do with the Dictionary object. You can count objects (using Dictionary.Count), you can remove entries (using RemoveAll or Remove), and you can modify entries. For information on all of these, see the Windows 2000 Scripting Guide.

In Closing

The word is spelled occasionally. A clevis is “a U-shaped yoke at the end of a chain or rod, between the ends of which a lever, hook, etc., can be pinned or bolted.” Yes, you can use e-mail as a verb. And last but not least, puisne is pronounced like “puny”: pyoonee. Oh yes, and scabies is an itchy skin disease. Bet you’re glad you know that now.