Hey, Scripting Guy! Question

Hey, Scripting Guy! I’m using an HTA as a drive-mapping application. I’d like to be clever about this and have the available drive letters appear in a list box. How can I dynamically show all the drive letters available on a computer?

-- JT

SpacerHey, Scripting Guy! AnswerScript Center

Hey, JT. You know, just the other day the Scripting Guy who writes this column was lamenting a truly lost weekend for Seattle-area sports fans. Well, we’re happy to report that things are finally looking up. After all, last night the Mariners’ Jeff Weaver – our top free agent acquisition, a “free” agent making more than $8 million this year – won his seventh game of the season, defeating the Oakland A’s 8-7.

And no, that’s not a misprint: 7 wins, and 8 million dollars. Admittedly, that sounds like a lot of money; more than $1 million for each victory. But look at it this way: if Jeff Weaver hadn’t won those 7 games, the Mariners wouldn’t make the playoffs. But with those seven wins, and with that outlay of $8 million, the Mariners will definitely make the – uh, never mind. We’re going to have to rethink our calculations.

Note. It’s obviously not the place of the Scripting Guy who writes this column to tell the Seattle Mariners how to run their team. (Although ….) On the other hand, it cost $8 million for the Mariners to have Jeff Weaver and not make the playoffs. Suppose next year they cut Jeff Weaver and pay the Scripting Guy who writes this column a measly $1 million to take his place. Will the Mariners make the playoffs with the Scripting Guy as part of their pitching rotation? Probably not. But they would save $7 million.

And yes, making $1 million a year would be a drastic pay cut for the Scripting Guy who writes this column. But he’s willing to do that for the chance to play major league baseball.

Hard as this might be to believe, however, Jeff Weaver winning his seventh game of the year isn’t the only exciting news to hit the Seattle area. In addition to that milestone, the whole town has been thrilled to hear about a script that can dynamically display available drive letters in a list box. Could such a script truly exist? Let’s see for ourselves:

<SCRIPT Language="VBScript">
    Sub Window_onLoad
        strComputer = "."

        Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

        Set colDisks = objWMIService.ExecQuery("Select * from Win32_LogicalDisk")

        Set objDictionary = CreateObject("Scripting.Dictionary")

        For Each objDisk in colDisks
            objDictionary.Add objDisk.DeviceID, objDisk.DeviceID
        Next

        For i = 67 to 90
            strDrive = Chr(i) & ":"
            If objDictionary.Exists(strDrive) Then
            Else
                strDrives = strDrives & strDrive & vbCrLf
            End If
        Next

        arrDrives = Split(strDrives, vbCrlf)

        For Each strDrive In arrDrives
            Set objOption = Document.createElement("OPTION")
            objOption.Text = strDrive
            objOption.Value = strDrive
            AvailableDrives.Add(objOption)
        Next
    End Sub
</SCRIPT>

<body>
    <select size="5" name="AvailableDrives" style="width:100">
    </select>
</body>

Before we launch into the discussion of this script and how it works, we should note that this is not a complete solution for mapping a network drive; all it does it demonstrate how to retrieve the available drive letters and add those drive letters to a list box. Are we going to show you a more complete solution before we call it a day? Maybe, but we aren’t going to tell you right now. After all, if we did that, you’d be tempted to skip directly to that portion of the article, which means you’d miss all the other cool things in today’s column.

Note. Not that there are any cool things in today’s column, mind you. But just in case there are, well, we’d hate for you to miss them.

The HTA itself is pretty simple: it consists solely of a list box named AvailableDrives, a list box that displays five items at a time (that’s what the Size property is for) and is 100 pixels wide. Here’s the HTML tagging for displaying the list box:

<select size="5" name="AvailableDrives" style="width:100">
</select>

And you’re right: this list box doesn’t have any options, does it? (Sort of like the Scripting Guys when it comes to career choices.) But don’t worry; as you might recall, the whole idea of this HTA is to add these options programmatically. And we’re going to do just that, in due time.

Note. What if you aren’t totally sure what we mean when we talk about HTAs, list boxes, and <SELECT> statements? That’s all right; just take a peek at our HTA Developer’s Center for some more information.

OK, so much for displaying a list box onscreen. Now let’s talk about the good stuff: dynamically adding available drive letters to that list box.

We’d like our HTA to automatically load those drive letters each time our little application is opened or refreshed. How do we do that? That’s easy: we put the list box-updating code in a subroutine named Window_onLoad:

<SCRIPT Language="VBScript">
    Sub Window_onLoad

If you’re familiar with HTML programming you know that the Window_onLoad subroutine is automatically executed any time a Web page (or HTA) is loaded or refreshed. What if you’re not familiar with HTML programming? That’s fine: now you know that the Window_onLoad subroutine is automatically executed any time a Web page (or HTA) is loaded or refreshed.

Inside the subroutine, we kick things off by connecting to the WMI service on the local computer. Could we run this HTA against a remote computer? Well, kind of. It’s easy enough to get drive letter information off a remote machine; however, mapping drives on that remote computer is a whole ‘nother story, and one we won’t get into today. (For a hint as to how you might go about doing that, take a gander at this Hey, Scripting Guy! column.

After we connect to the WMI service, we then use the ExecQuery method to return information about all the used drives on the computer, both local drives and mapped network drives:

Set colDisks = objWMIService.ExecQuery("Select * from Win32_LogicalDisk")

What’s that? You’re saying that all we’ve done so far is return a collection of the unavailable drive letters (that is, drive letters already in use)? You’re saying that what we need to do is figure out which drive letters are available? Hmmm, that is a bit of a dilemma, isn’t it? Wonder how the Scripting Guys will work their way out of this one?

And no, we’re not going to show you that, either, at least not right now. Before we can do any of that we need to create an instance of the Scripting.Dictionary object. Once we have an instance of the Dictionary object in hand we then execute this block of code:

For Each objDisk in colDisks
    objDictionary.Add objDisk.DeviceID, objDisk.DeviceID
Next

What are we doing there? What we’re doing there is looping through the collection of disk drives and, for each drive in the collection, adding the drive letter (DeviceID) to the Dictionary, using the drive letter as both the Dictionary key and item.

Note. We know, we know. But you can learn everything you’d ever want to know about the Dictionary object – and then some – in everyone’s favorite (well, second favorite) scripting column, Sesame Script.

When all is said and done, we’ll have a Dictionary object containing keys similar to this, with each key representing a used drive letter:

C:

D:

E:

M:

X:

And you’re right: we still don’t have a list of available drive letters, do we? But that’s about to change, thanks to this block of code:

For i = 67 to 90
    strDrive = Chr(i) & ":"
    If objDictionary.Exists(strDrive) Then
    Else
        strDrives = strDrives & strDrive & vbCrLf
    End If
Next

Don’t worry; we were just about to explain how this works. To begin with, we set up a For Next loop that runs from 67 to 90. Why 67 to 90? Well, drive letters are limited to the letters C through Z (the letters A and B being reserved from floppy drives). As it turns out the ASCII value for the letter C is 67, and the ASCII value for the letter Z is 90. Setting up a For Next loop that runs from 67 to 90 gives us a sneaky way to loop through all the letters from C through Z.

Inside the For Next loop, we use the Chr function to convert the ASCII value of the loop variable (i) to an actual character; for example, the first time through the loop, i is equal to 67, which means that the Chr function will return the letter C. After converting the ASCII value we then tack a colon onto the end. That’s what this line of code is for:

strDrive = Chr(i) & ":"

In turn, that means that – the first time through the loop – the variable strDrive is equal to this: C:.

Which, now that you mention it, does look an awful lot like a drive letter, doesn’t it?

That brings us to this line of code:

If objDictionary.Exists(strDrive) Then

Here we’re using the Exists method to determine whether or not that first drive letter appears in the Dictionary object. Let’s assume that it does. That means that the drive letter is already in use, and thus not available for drive mapping. Consequently, we don’t do anything at all. (Well, other than circling around and repeating the process with the next value in the loop.)

Now, suppose that the drive letter can’t be found in the Dictionary. (That is, suppose the Exists method returns False.) That can only mean one thing: this drive letter is not in use, and thus is available for drive mapping. Consequently, we use this line of code to add the drive letter (and a carriage return-linefeed character) to a variable named strDrives:

strDrives = strDrives & strDrive & vbCrLf

Got that? By the time we exit the loop, all the available drive letters will be safely tucked away in the variable strDrives.

Now all we have to do is figure out how to get those drive letters into our list box. To that end, our next step is to use the Split function to create an array named arrDrives; as you can see, we do this by splitting the variable strDrive on the carriage return-linefeed character (vbCrLf):

arrDrives = Split(strDrives, vbCrlf)

In turn, that gives us an array consisting of the following items (on our test computer), items that correspond quite nicely to the available drive letters on the computer:

F:

G:

H:

I:

J:

K:

L:

N:

O:

P:

Q:

R:

S:

T:

U:

V:

W:

Y:

Z:

And now – finally – we can add the available drive letters to the list box (and don’t worry; the script zips by much quicker than the explanation does):

For Each strDrive In arrDrives
    Set objOption = Document.createElement("OPTION")
    objOption.Text = strDrive
    objOption.Value = strDrive
    AvailableDrives.Add(objOption)
Next

All we’ve done here set up a For Each loop to cycle through the collection of available drive letters. For each item in the collection we create an instance of the HTML Option object; each Option object is equivalent to an item in the list box. That’s what this line of code is for:

Set objOption = Document.createElement("OPTION")

We then use these two lines of code to configure the Text and Value properties for the item:

objOption.Text = strDrive
objOption.Value = strDrive

If you haven’t done a lot of work with HTML, the Text is simply the text that appears in the list box. The Value, meanwhile, is the data reported to a subroutine when a given option is chosen. Text and Value for an option do not have to be identical. We made them identical here simply because it makes sense: you want the drive letter to show up in the list box, and you probably want the exact same drive letter to be used in your subroutine.

After configuring the Option object we add the new item to the list box like so:

AvailableDrives.Add(objOption)

As you can see, all we do is refer to the list box (AvailableDrives) and call the Add method. Along the way, we pass Add a single parameter: the object reference to our Option object. And then we loop around and repeat the process with the next item in the array. When that’s finished, all the available drive letters will show up in a list box, a list box that should look something like this:

Spacer

Now, what about that more-complete solution, the one that actually maps a drive for us? Well, without any further explanation, here’s one way to do that. To use this HTA, select a drive letter, then click the Map Drive button. When the dialog box appears, type in the UNC path to the network drive (e.g., \\atl-fs-01\public). Click OK and two things will happen:

The drive will be mapped

The list box will refresh itself (because a previously-available drive letter is no longer available)

Here’s the code:

<SCRIPT Language="VBScript">
    Sub Window_onLoad
        Set objDictionary = CreateObject("Scripting.Dictionary")

        strComputer = "."

        Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

        Set colDisks = objWMIService.ExecQuery("Select * from Win32_LogicalDisk")

        For Each objDisk in colDisks
            objDictionary.Add objDisk.DeviceID, objDisk.DeviceID
        Next

        For i = 67 to 90
            strDrive = Chr(i) & ":"
            If objDictionary.Exists(strDrive) Then
            Else
                strDrives = strDrives & strDrive & vbCrLf
            End If
        Next

        arrDrives = Split(strDrives, vbCrlf)

        For Each strDrive In arrDrives
            Set objOption = Document.createElement("OPTION")
            objOption.Text = strDrive
            objOption.Value = strDrive
            AvailableDrives.Add(objOption)
        Next
    End Sub

    Sub MapDrive
        If AvailableDrives.Value = "" Then
            Msgbox "Please select a drive letter."
            Exit Sub
        End If
        strDrive = AvailableDrives.Value
        strPath = InputBox("Please enter the network path:")
        If strPath = "" Then
            Exit Sub
        End If
        Set objNetwork = CreateObject("Wscript.Network")
        objNetwork.MapNetworkDrive strDrive, strPath
        Location.Reload(True)
     End Sub
</SCRIPT>

<body>
    <select size="5" name="AvailableDrives" style="width:100px"></select><p>
    <input type="button" value="Map Drive" onClick="MapDrive">
</body>

As for the Mariners, well, if anyone from the Seattle front office is reading this column (and, to be honest, we’d really prefer that you were reading Baseball Digest instead) just send email to scripter@microsoft.com (in English, if possible) and make us an offer. We can’t promise to accept that offer; why that would mean giving up a thrilling and rewarding career as a technical writer just for the chance to fly around the country playing baseball. But we’ll definitely take it under consideration.