The Script Center Guide to ActiveX Controls

ActiveX Controls
*

Creating a Progress Bar That Actually Measures Progress


As part of the Antiques Script Show webcast presented during Scripting Week 3, we showed people how they can use an ActiveX progress bar control to display the progress a script is making while it runs.

Well, check that: what we really did was show how you can use a progress bar to display activity as a script runs. Due to time constraints and some technical considerations, all we really did was show people how to display a progress bar; we didn’t talk about how to configure that progress bar so that the script comes to a close right about the same time the progress bar reaches 100%. In fact, we even discouraged people from trying to create a true progress bar, noting that it can be very difficult to estimate completion times for scripts and ensure that the script finishes up at the same time the progress bar does.

Needless to say, we didn’t deter many people: everyone wants a real progress bar, one that measures actual script progress. Therefore, due to overwhelming popular demand today’s ActiveX article shows you one technique for creating a progress bar that actually measures progress. This particular approach is optimized for use with WMI and it’s really designed for scripts that run against just one computer. However, you can probably take the same basic technique and apply it to other scripting technologies and to scripts that run against multiple machines. But you’re on your own there.

Note. Before we begin we should note that the ActiveX control we’re using today is installed along with Microsoft Office; if you don’t have Microsoft Office you won’t be able to use this script. Also, we aren’t going look at the individual properties of the progress bar in detail. For that information, take a peek at the Antiques Script Show.

Top of pageTop of page

Using the Progress Bar Control

To begin with, we need two files in order to make this work: the script itself (a .vbs file) and a .htm file (in our sample script, that happens to be C:\Scripts\Progress.htm). The code for Progress.htm looks like this:

<html>

<head>
    <title>Please wait</title>
</head>

<body bgcolor="buttonface">

<p>
<object classid="clsid:35053A22-8589-11D1-B16A-00C0F0283628" id="ProgressBar1" height="20" width="400">
    <param name="Min" value="0">
    <param name="Max" value="100">
    <param name="Orientation" value="0">
    <param name="Scrolling" value="1">
</object>
</p>

</body>
</html>

And this is what the HTML file will look like after it gets called by our script:

Progress Bar


As you can see, we’ve used pretty standard – and pretty simple – HTML tagging. To begin with, we have an <object> tag (used for inserting the progress bar control) that looks like this:

<object classid="clsid:35053A22-8589-11D1-B16A-00C0F0283628" id="ProgressBar1" height="20" width="400">

Note that we specify four things when inserting the progress bar:

classid. A globally-unique identifier that tells the operating system to insert the Microsoft Office progress bar control. This ID number will be the same on all computers where Microsoft Office (and thus the progress bar) is installed.

id. A user-friendly “nickname” for our instance of the progress bar control. We’ll use the id when referring to the progress bar from within our script. Why do we need to refer to the progress bar from the script? Well, the progress bar has no built-in intelligence of any kind: it only does what we tell it to do. Therefore, if we want to indicate that progress is being made, we have to periodically update the Value of the progress bar control. That’s something we’ll do from the .vbs file.

height. Height of the progress bar control. This has nothing to do with the size of the Internet Explorer window; we’ll set that from inside our script.

width. Width of the progress bar control. This has nothing to do with the size of the Internet Explorer window; we’ll set that from inside our script.

We then include four optional (but, in our case, important) parameters as part of the <object> tag:

<param name="Min" value="0">
<param name="Max" value="100">
<param name="Orientation" value="0">
<param name="Scrolling" value="1">

The Min and Max parameters represent the minimum and maximum values of the progress bar respectively: the Value of our progress bar can never drop below 0 nor rise above 100. Setting the Orientation to 0 gives us a horizontal progress bar, and setting the Scrolling parameter to 1 gives us a nice, smooth progress bar (as opposed to a dotted progress bar). Once again, see the Antiques Script Show for details.

You’re right: we must be having a sale on the Antiques Script Show!

And that’s all there is to the HTML file: we don’t do much more than embed the ActiveX control and configure a few properties. No other HTML elements are added to the page, and no scripts are required: all the scripting takes place from the .vbs file. Short and sweet.

With that in mind, let’s take a look at that .vbs file:

On Error Resume Next

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.ExecQuery("Select * From Win32_DesktopMonitor")
For Each objItem in colItems
    intHorizontal = objItem.ScreenWidth
    intVertical = objItem.ScreenHeight
Next

Set objExplorer = CreateObject("InternetExplorer.Application")
objExplorer.Navigate "file:///C:\Scripts\progress.htm"   
objExplorer.ToolBar = 0
objExplorer.StatusBar = 0
objExplorer.Width = 450
objExplorer.Height = 90 
objExplorer.Left = (intHorizontal - 450) / 2
objExplorer.Top = (intVertical - 90) / 2
objExplorer.Visible = 1             

Do While (objExplorer.Busy)
    Wscript.Sleep 200
Loop 

Set colServices = objWMIService.ExecQuery("Select * from Win32_Service")

intServices = colServices.Count
intIncrement = 100/intServices

For Each objService in colServices
    Wscript.Sleep 200
    objExplorer.Document.Body.All.ProgressBar1.Value = _
        objExplorer.Document.Body.All.ProgressBar1.Value + intIncrement
Next

objExplorer.Quit

We’re going to skip over several sections of this script without discussing them in any detail. For example, the script starts off with this block of code, which we use to determine the horizontal and vertical resolution of the screen:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.ExecQuery("Select * From Win32_DesktopMonitor")
For Each objItem in colItems
    intHorizontal = objItem.ScreenWidth
    intVertical = objItem.ScreenHeight
Next

Why do we care about that? Well, we want to center our progress bar on screen, and we can’t do that unless we first know the size of the screen. Like we said, we won’t discuss this technique in any detail; you can find a reasonably good discussion – with pictures! – in the Scripting Week 3 webcast Script It Up a Notch.

Next we have a block of code that creates an instance of the InternetExplorer.Application object and then uses the Navigate method to open the file C:\Scripts\Progress.htm. After opening the file we configure several properties of the Internet Explorer window (including hiding the tool bar and status bar). Finally, we set the Visible property to 1 in order to make our Internet Explorer window appear on screen:

Set objExplorer = CreateObject("InternetExplorer.Application")
objExplorer.Navigate "file:///C:\Scripts\progress.htm"   
objExplorer.ToolBar = 0
objExplorer.StatusBar = 0
objExplorer.Width = 450
objExplorer.Height = 90 
objExplorer.Left = (intHorizontal - 450) / 2
objExplorer.Top = (intVertical - 90) / 2 
objExplorer.Visible = 1

So are we going to talk about this code today? No, sorry. But, again, you can find out more by watching the Script It Up a Notch webcast.

And no, we don’t get extra money any time someone watches a Scripting Week 3 webcast. Though we’re kicking ourselves for not having thought of that.

We will, however, briefly mention this block of code (according to our contract we have to talk about something any time we write an article):

Do While (objExplorer.Busy)
    Wscript.Sleep 200
Loop

Our goal here is to display a progress bar that reaches 100% right about the same time the script finishes. If we’re going to keep the progress bar and the script in synch, it’s important that the script waits until our Internet Explorer window is fully displayed on screen; if the script starts running – and carrying out tasks – before our progress bar is displayed we won’t get the effect we’re striving for. Therefore, we set up a simple Do While loop that checks to see if the Busy property of Internet Explorer is True; if it is, that means Internet Explorer is still busy loading up. If Internet Explorer is busy, we pause for 200 milliseconds, then check again. As soon as objExplorer.Busy equals False we break out of the loop and continue with the rest of the script.

The first thing we do once we’ve broken free of the loop is use this line of code to return a collection of all the services installed on the computer (we chose to use services, but you can apply this technique to any WMI class):

Set colServices = objWMIService.ExecQuery("Select * from Win32_Service")

Next we have these two lines of code:

intServices = colServices.Count
intIncrement = 100/intServices

In the first line, we get the value of the Count property and assign it to a variable named intServices. Every WMI collection has a Count property that tells us how many items are in the collection; in this case, Count will tell us how many services are installed on the computer.

Note. We don’t recommend using the Count property for collections that contains hundreds and hundreds of items (for example, all the events in the event logs). For more information on why we don’t recommend that, take a look at the answer to one of our weekly scripting puzzles.

In the second line, we take the value 100 and divide it by the number of installed services. Why? Well, our progress bar has a maximum value of 100: when the progress bar reaches 100, the script should be done. What we’re going to do in a moment is set up a For Each loop that walks through the collection of services installed on the computer. For ease of use, let’s say there are 50 services, which means we’ll go through the For Each loop 50 times (once for each service).

So why 100 divided by the number of services? Well, we’re going to go through our For Each loop x number of times. Each time we finish a pass through the loop, we need to advance our progress bar a certain amount. If we divide 100 (the maximum value of the progress bar) by 50 (the number of installed services) we get 2. That means that each time we complete a loop we’ll advance the value of the progress bar by 2. If we go through the loop 50 times, the value of the progress bar will be 100 (50 times 2). And guess what? After we go through the loop 50 times the script is essentially done. Furthermore, after we go through the loop 50 times the progress bar will be set at 100. In other words, the progress bar reaches 100% right about the same time the script finishes.

Clever, huh?

Here’s what our For Each loop looks like:

For Each objService in colServices
    Wscript.Sleep 200
    objExplorer.Document.Body.All.ProgressBar1.Value = _
        objExplorer.Document.Body.All.ProgressBar1.Value + intIncrement
Next

Admittedly, we took a little shortcut in this sample script: instead of doing something with each service (like writing information about it to a text file) we simply pause the script for 200 milliseconds. In a real script, you’ll probably want to delete the line of code that reads Wscript.Sleep 200 and replace it with something a bit more interesting. But, again, you’re on your own there.

At the end of the loop we then run this line of code:

objExplorer.Document.Body.All.ProgressBar1.Value = _
   objExplorer.Document.Body.All.ProgressBar1.Value + intIncrement

What are we doing here? Well, objExplorer.Document.Body.All.ProgressBar1 is simply the object path to our progress bar control. All we’re doing is setting the Value of the progress bar to the current value plus the increment we calculated a minute ago. (Remember all that 100 divided by the number of services stuff?) That’s how we indicate progress. When the script starts, the progress bar will be set at 0. After one time through the loop, the progress bar will be set to 2: the current value (0) plus the increment (2). After a second pass through the loop, the progress bar will be set to 4: the current value (2) plus the increment (2). Etc. etc. etc.

It might not make sense, but if you try the script for yourself you’ll understand what we’re talking about.

And that’s all we do. After we’re finished with the For Each loop our script is finished as well. Therefore we simply use this line of code to terminate our instance of Internet Explorer, and pat ourselves on the back for a job well done:

objExplorer.Quit

Top of pageTop of page