Previous Page
Next Page

For Each...Next

For Each...Next lets you walk through a collection of objects and do something with an individual object from the collection. Once it is done, it goes to the next object in the collection. It is impossible to overemphasize how important this structure is. It is basic to working with information retrieved from Windows Management Instrumentation (WMI, see Chapter 8, "Using WMI"). But there are other common situations where you will be using For Each... Next as well: files, and folders are returned as a collection from the fileSystemObject as well. Whenever you hear the word collection, think For Each...Next.

In the CollectionOfDrives.vbs script, you use For Each...Next to examine disk space utilization of fixed drives on a server:

CollectionOfDrives.vbs

Option Explicit
On Error Resume Next
Dim colDrives 'the collection that comes from WMI
Dim drive   'an individual drive in the collection
Const DriveType = 3 'Local drives. From the SDK

set colDrives =_
GetObject("winmgmts:").ExecQuery("select size,freespace " &_
 "from Win32_LogicalDisk where DriveType =" & DriveType)

For Each drive in colDrives 'walks through the collection
WScript.Echo "Drive: " & drive.DeviceID
WScript.Echo "Size: " & drive.size
WScript.Echo "Freespace: " & drive.freespace
Next

Let's peruse this script and see what it's doing. In your initial reading, you see some common elements you learned about in Chapter 1, "Starting from Scratch": the Header information section of the script (Option Explicit, On Error Resume Next, and Dim); and the Reference section (the part with Const DriveType). The Worker section of the script contains the GetObject statement. Because Windows Management Instrumentation (WMI) is already running, we do not need to create an instance of the WMI object (by using CreateObject); we can simply go get it by using GetObject. The Output section consists of the WScript.Echo statements. By examining the structure of the script, we get a sense of familarity, even though the script introduces a number of new concepts, such as WMI, collections, and the For Each...Next statement.

Just the Steps

To use For Each...Next

1.
On a new line in a script, type For Each and then a variable name.

2.
On the next line, enter a command you want repeated.

3.
On the line following the command you want repeated, type Next.


Header Information

The Header information section of your script contains commands that are rapidly becoming old hat:

Option Explicit
'On Error Resume Next
Dim colDrives
Dim drive

This script begins by using Option Explicit, which says that each variable must be specifically listed (declared) by using the Dim command. On Error Resume Next is a rudimentary error handling technique that says "when an error occurs, skip the line that caused the problem and go on to the next line in the script."

Defining Constants

The Const DriveType = 3 line is a new concept. In this line, you define a constant. This line says that the word DriveType is equal to the number 3. Why do you do this? You want to use the number 3 later in the script when you build the WMI query. Rather than hard-coding the number 3 into your query (hard-coding a value into a script is called creating a literal), you replace it with the constant DriveType. Just like a variable, the constant can be called anything you want. But because WMI uses a number to refer to the type of drive, you call the constant DriveType. The most important thing to remember about a constant is that the value never changesit is constant.

Constants vs. Variables

Why did we use a constant instead of a variable in the CollectionOfDrives.vbs script? This is a good question, and the answer is that you could have used a variable in this instance. It would look something like this:

Dim colDrives 'holder for what comes back from the WMI query
Dim drive 'holder for name of each logical drive in colDrives
Dim DriveType
DriveType = 3 'Local drives. From the SDK

In this particular case, using a variable instead of a constant wouldn't have made any difference. However, variables have a dark secret that will come back to haunt you one day (guaranteed). Their value can change during script execution, whereas the value of a constant is set before execution and cannot change. This is illustrated in the following rather silly script. First is the normal Header information section: Option Explicit, On Error Resume Next, and a few Dim statements to declare the variables. Next, in the Reference section, you assign values to each variable and echo out the total. So far so good. However, you then reassign the FirstValue to be equal to the total, and echo out the total. Because the variable total is assigned to FirstValue + SecondValue before the FirstValue is reassigned to the total, the script produces illogical results. If you added Total = FirstValue + SecondValue right before the second echo, the script would work as expected.

Option Explicit
On Error Resume Next

Dim total
Dim FirstValue
Dim SecondValue

FirstValue = 1
SecondValue = 3
Total = FirstValue + SecondValue

WScript.Echo " the total of " & FirstValue & " and " & _
  SecondValue & " Is " & (total)
FirstValue = Total
WScript.Echo " the total of " & FirstValue & " and " & _
  SecondValue & " Is " & (Total)

Shared Benefits of Constants and Variables

You gain several advantages by using either a constant or a variable:

  • The script is easier to read. When you read the WMI query, notice that you're filtering by DriveType. This makes more sense than filtering out number 3 drive types.

  • The script is easier to revise. To change the script to filter out CD-ROMs, simply change the constant to the number 5.

Important

The ease of modifying the value of the constant in the reference section points out the advantage of calling our constant DriveType instead of something like LocalDisk, or FixedDisk. If we did this, then we would need to revise the constant name, and every place in the script that referenced that constant, or else the script would be misleading. Can you imagine the confusion a constant called LocalDisk would have if you set the value to 4, which refers to network disks? The script would still run fine, because as William Shakespeare said, "A constant by any other name is still a constant."


  • Reusing the value in the script later on is easier. This script does not reuse the constant DriveType. However, you'll do this in longer scripts, and using constants is a good habit to get into.

  • The script is easier to document. You can easily add a comment or a series of comments such as the following:

    Const DriveType = 3 'used by WMI for fixed disks
    'other drive types are 2 for removable,
    '4 for Network, 5 for CD


After the constant is defined, you list a couple of variables used by the script. In this case, you declared two. The first one is colDrives. Now, why did you call this colDrives? Because the WMI query returns a collection of drives. Let's look at collections and see what they do for you. But before we do, let's stop and see how we are doing.

Quick Check

Q.

Name one advantage of using For Each...Next.

A.

Using this construct provides the ability to iterate through a collection without knowing the number of members in advance.

Q.

What is the difference between a variable and a constant?

A.

A variable can change value, whereas a constant retains a constant value.

Q.

List three reasons for using constants.

A.

Using constants makes the script easier to read and easier to revise. Reuse later in the script is also easy.


Collections

When you have the possibility of seeing a group of related items, thinking of them as a collection is useful. A collection is a familiar concept. For instance, my wife has a collection of key chains. Although each of the key chains is different (some have city names, some have college names, and others have product names), they are also similar enough to be in her collection called key chains. That is, they all have a ring on which keys are hungwithout that common feature, they would not be key chains. In a similar fashion, when you run your script, the script will return all the permanently fixed hard disk drives on the server. These drives might be IDE or SCSI, but they will all be hard disk drives.

What's so great about having a collection of hard disks? Consider the alternative. If you couldn't return a collection of hard drives from a server, you'd need to know which drives are actually installed on the machine. You'd have to connect to the server and list information for each drivefor example, you'd need to connect to drives A, C, D, E, F, and so on. In addition, to keep the script from failing when a drive did not exist, you'd need error handling (such as On Error Resume Next), or you'd have to test for the presence of each drive prior to querying information about it. Although that approach would work, it would be kludgy, to say the least. It would also defeat the purpose of using automation to retrieve information related to drives.

There is only one bad thing about collections: You cannot simply perform a WScript.Echo of the information returned from a query, because each drive in the collection could have different properties. For example, if I wanted drive.size, which size would be returned from the echo command? To retrieve drive.size, we need to singularize a drive from the collection, so that we are working with only one drive from the collection at a time. To do this, you have to do something like a For Each...Next loop and go through the loop as many times as there are items in the collection. If you had five drives in your collection, guess what? We, in our current script, make five passes through the loop and echo each of the drives out. Walking through the loop multiple times, once for each member of the collection, is called iteration and is a task routinely performed in administrative scripting.

If you have only one drive, guess what? It's still returned as a collection, and you have to iterate through the collection using For Each...Next to get out your single drive. Fortunately, by the end of this chapter, you'll have so much experience doing this, it will seem like a piece of cake (or a piece of celery, if you're on a diet like I am).


Reference Information

In the Reference information section of the script is a new concept mentioned earlierWMI. We're using it here to look at our drives, but you'll learn more about WMI later in this chapter. To connect to WMI, you have to use a string that looks like GetObject("winmgmts:"). Then you simply run a query that selects the desired drives. Remember that in the Reference information section of our script, you say that colDrives is equal to all the information on the right side of the equal sign. You are creating an alias for the long winmgmts connection string that we call colDrives. You can see this in the following code:

Set colDrives =_
GetObject("winmgmts:").ExecQuery _
  ("select DeviceID from Win32_LogicalDisk where DriveType =" & _
  DriveType)

Worker Information

The Worker information section is really small in this script. In addition, the Output information section is sandwiched inside the For Each...Next loop. Let's look at the code:

For Each drive In colDrives
  WScript.Echo drive.DeviceID
Next

Because you sent a fine-tuned query to WMI in the Reference information section of the script, and the purpose of the script was simply to list drives, the Worker information section has little work to do. All it really needs to do is to walk through the collection of drives returned from WMI. You use For Each and then the variable drive that you created to hold each of the drives returned from colDrives. Once you have the drive in your hands, you look for the Device ID of the drive. But, interestingly enough, you use this in the Output information section of the script, which is the WScript.Echo part. After you echo the DeviceID, you use the Next command to do it again for the next drive in the collection.


Previous Page
Next Page