Hey, Scripting Guy! Question

Hey, Scripting Guy! I have an HTA in which I do a search and create a table with the results. The search takes quite some time, 30 seconds or so. The results are displayed within a span and show up only once the search is complete. I would like a message ("Searching...") to appear in the span as soon as the search starts, then be replaced by the table once the search is done. I found out that if I put up a message box the span updates the way I want it to. But I don’t like having to use a message box for this. How can I do this without the message box?

-- MF

SpacerHey, Scripting Guy! AnswerScript Center

Hey, MF. You know, for the most part the Scripting Guy who writes this column generally has a laser-focus on his job (or would have, if anyone could tell him exactly what that job is). Nevertheless, every now and then he likes to temporarily leave the wacky world of system administration to take a peek at an even wackier place called the real world. For example, a few weeks ago the Michigan Lawsuit Abuse Watch announced the winner of their annual Wacky Warning Label Contest. The winner for the year 2007? A warning label attached to a small tractor that recommends that users: Avoid Death.

Now, granted, that is pretty funny; obviously the tractor manufacturers are afraid someone will use their machine, die, and then win a lawsuit because nobody told them that should try to avoid death while riding a tractor. (Just think about the number of times you’ve died because someone forgot to tell you that you shouldn’t die while doing whatever it was you were doing.) And yet, cute as that might be, there’s no doubt that this is pretty good advice; in fact, it makes us wonder why stickers like that aren’t mandatory on pretty much everything. After all, Avoiding Death is useful advice for everyone, not just small tractor drivers. (Or drivers of small tractors, if you prefer.)

Of course, the only problem with that advice is that Avoiding Death is pretty much a full-time job; you can’t afford to let your guard down, not even for a second. (And yes, that includes people who don’t have to drive on the same roads or park in the same parking garage as the Scripting Editor.) To be honest, that doesn’t leave much time for doing anything except avoiding death. That’s why we decided to keep today’s solution as simple as possible; with luck, you’ll be able to keep one eye on the lookout for death, and keep the other on the following HTA code:

<SCRIPT Language = "VBScript">
    Sub Subroutine1
        DataArea.InnerHTML = "Searching ...."
        document.body.style.cursor = "wait"
        idTimer = window.setTimeout("Subroutine2", 2000, "VBScript")
    End Sub

    Sub Subroutine2
        window.clearTimeout(idTimer)

        strComputer = "."
        Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
        Set colEvents = objWMIService.ExecQuery("Select * From Win32_NTLogEvent")

        For Each objProcess in colEvents
            i = i + 1
        Next

        DataArea.InnerHTML = "Total events: " & i
        document.body.style.cursor = "arrow"
    End Sub
</SCRIPT>

<body>
    <input type="button" value="Count Events" onClick="Subroutine1"><p>
    <span id=DataArea>This is a span named DataArea.</span>
</body>

Before we launch into our explanation we should note that there are probably other ways to accomplish this task; heck, there are probably better ways to accomplish this task. But this one works and – with the possible exception of avoiding death – that’s all we really care about, right?

Note. Fortunately, no, Microsoft doesn’t care only about things that work. How else can you explain the fact that the Scripting Guys are still hanging around after all these years?

We know that a number of you have tried to do something similar to this, and you’ve all encountered the same problem. You call a subroutine that’s supposed to change the value of a span, or maybe change the cursor to an hourglass (a bit of bonus code we tossed into today’s column). After that’s done, the subroutine is supposed to retrieve some data, a process that’s expected to take awhile. Once that’s done, that data replaces the message in the span, and the cursor changes back to an arrow. Simple and straightforward.

Except for one thing: it doesn’t work. The initial message never gets displayed and the cursor never changes to an hourglass. (Well, at least in practical terms these events never take place.) As far as the user is concerned, your HTA simply ignores those lines of code.

And you’re right: that’s not very nice, is it?

So why does this happen? Well, to tell you the truth, we aren’t 100 percent sure. But that’s not what’s important. What’s important is that we managed to find a way to work around the problem. And without anyone getting hurt.

Well, OK, Peter banged up his knee a little and Dean got a headache. But other than that, no one got hurt.

So how does our workaround work? Well, to begin with, you might have noticed that we have a very simple HTA; it consists entirely of a button labeled Count Events and a span with the id DataArea. The principle behind the HTA is equally simple: when the user clicks the button, a subroutine named Subroutine1 runs. That kicks off a process in which all the events in the event logs are tallied up, with the final count displayed in the span. Tallying up all the events in all the event logs takes time; therefore, while the counting takes place the message Searching …. is displayed in the span, and the cursor is changed to an hourglass.

Of course, the last two things are what every HTA creator wants his or her HTA to do; it’s just that those HTAs never bother to display the message or change the cursor. Why? Again, we don’t know with 100 percent certainty. But here’s how you can get your HTA to do what you want it to do.

As you can see, there isn’t much to Subroutine1:

Sub Subroutine1
    DataArea.InnerHTML = "Searching ...."
    document.body.style.cursor = "wait"
    idTimer = window.setTimeout("Subroutine2", 2000, "VBScript")
End Sub

In line 1, we change the InnerHTML property of the span to the message Searching …. In line 2, we set the cursor to the system wait cursor. (In most versions of Windows this is an hourglass – in Windows Vista it’s a…well, a swirly sort of thing.) And then we run smack dab into line 3:

idTimer = window.setTimeout("Subroutine2", 2000, "VBScript")

What we’re doing here is using the setTimeout method to create a new timer named idTimer; in HTML, a timer enables us to temporarily pause a script, much the same way that the Wscript.Sleep command does. As you can see, we pass setTimeout three parameters:

  • Subroutine2. This is the name of the subroutine we want to run when the timer expires.
  • 2000. This is the amount of time, in milliseconds, that we want the timer to wait before calling Subroutine2. If it’s been awhile since you’ve had to deal with milliseconds, here’s a hint: there are 1,000 milliseconds in every second. Thus pausing a script for 2,000 milliseconds pauses that script for 2 seconds.
  • VBScript. A required parameter that simply lets the script know that Subroutine2 is written in VBScript.

In other words, when we click the button Subroutine1 is executed. When this subroutine runs it displays a message in the span, changes the cursor, and then creates a timer named idTimer. The sole function of idTimer is to wait 2 seconds and then call the subroutine Subroutine2. Why do that? Because the little trick of calling one subroutine and changing the span and cursor, pausing the script for a couple seconds, then calling a second subroutine to retrieve the data seems to do the trick: the span updates, the cursor changes, and life is as it should be.

So what goes on in Subroutine2? Well, to begin with, we use the clearTimeout method to ensure that our timer (and thus our subroutine) runs only once:

window.clearTimeout(idTimer)

After that we use some standard WMI code to retrieve a collection of all the events from the event logs. We then set up a For Each loop to run through each event in the collection, methodically counting up the events one-by-one:

For Each objProcess in colEvents
    i = i + 1
Next

Note. Good point: there are better ways to count the number of events in all the event logs. (For more information, see the Microsoft Windows 2000 Scripting Guide.) We chose this simply because we need some code that would take a minute or so to complete. If we counted up something else – like the number of processes running on the computer – everything would happen so quickly we might never know for sure if the message was displayed and the cursor changed.

After we’ve counted all the events in the event logs we then display that data in the span (this is where you would display your table, MF) and change the cursor back to an arrow:

DataArea.InnerHTML = "Total events: " & i
document.body.style.cursor = "arrow"

That’s all you have to do.

We hope that helps, MF. In the meantime, we encourage you to take a look at some of the other entries in the Wacky Warning Label Contest. The Scripting Guy who writes this column actually preferred the contest runner-up: an iron-on T-shirt transfer that states: "Do not iron while wearing shirt." The Scripting Guy who writes this column likes this label because baseball lore has long held that Atlanta Braves pitcher John Smoltz once scalded himself trying to iron a shirt while wearing it. Smoltz has always denied that that was how he got burned, but, then again, who would actually admit to something like that?

We’ll see everyone tomorrow. Have a nice day, and Avoid Death.

Note. For the record, the answer is: no, never. But that’s mainly because the Scripting Guy who writes this column never irons any of his shirts, whether he’s wearing them or not.