Extreme Makeover: Wrap Your Scripts Up in a GUI Interface
HTML Applications (HTAs) provide a way for you to wrap your scripts up in a graphical user interface, an interface replete with check boxes, radio buttons, drop-down lists and all those other Windows elements you’ve grown to love. In this article, the first of a multi-part series, we’ll introduce the basics behind creating your very own HTA.
Creating Your Own HTAs: The First of a Multi-Part Tutorial
You might have noticed that you can’t buy MS-DOS any more, and for good reason: after people got a look at Windows, well, typing in long, cryptic commands from the C:\ prompt just didn’t seem like nearly as much fun. The command prompt still has its place, but the success of Windows (and the Macintosh, and all the new Windows-like Unix shells) suggests that most people prefer a graphical user interface.
Because of that you might expect the Microsoft scripting technologies - such as Windows Script Host (WSH) and VBScript - to have some of the coolest and fanciest graphical user widgets around. Oddly enough, though, that isn’t the case. Outside of a message box here and an input box there, neither WSH nor VBScript offers much in the way of GUI elements. Using WSH and/or VBScript, can you create a form that enables you to select computer names from a list box? No. Can you create a form that allows people to select options from a series of check boxes? No. Can you - sorry; no. If you want a graphical user interface, you’re simply going to have to give up scripting and turn to full-fledged, full-time programming.
Or are you? Let’s think about this for a second: why do people like scripting in the first place? Well, scripting enables you to perform useful tasks quickly and easily, without having to compile anything and without having to use anything more powerful (or expensive) than Notepad. Suppose we told you that you could create a useful utility, like the following, quickly and easily, without having to compile anything and without having to use anything more powerful (or expensive) than Notepad:
Would that satisfy your craving for a graphical user interface? If so, then welcome to the world of HTAs.
What is an HTA?
The term HTA is actually an acronym: it’s short for HTML Application. For our purposes an HTA is nothing more than a way to provide a graphical user interface for your scripts. As we’ve already noted, neither WSH nor VBScript provide much in the way of graphical user interface elements: no check boxes, no list boxes, no nothing. Internet Explorer, however, makes use of all of these elements - and more. Because an HTA leverages Internet Explorer, you can take advantage of all these graphical user interface elements when writing system administration scripts. (And, yes, we’re going to show you how to do just that.)
How closely related are HTML files and HTAs? Well, take any HTML file and change the file extension from .htm (or .html) to .hta. Congratulations: you’ve just created your first HTA.
So Why Don’t I Just Use an HTML File?
There’s a very simple answer to that: security. There are a lot of security restrictions placed on Internet Explorer, and for good reason: if you visit a Web site you’d probably prefer that this Web site not use a client-side script that starts reconfiguring your settings or rooting around through your file system. Consequently many system administration scripts - including those that use WMI or ADSI - either will fail when run from Internet Explorer or, at best, will present you with a dialog box similar to this one:
Any time you run your script from an HTML file you’ll be presented with a dialog box like this. That might be OK, but it’s definitely not the best possible user experience.
HTAs, by contrast, are not bound by the same security restrictions as Internet Explorer; that’s because HTAs run in a different process than Internet Explorer. (HTAs run in the Mshta.exe process rather than the Iexplore.exe process.) Unlike HTML pages, HTAs can run client-side scripts and they have access to the file system. Among other things, this means that HTAs can run your system administration scripts, including those that use WMI and ADSI. Your scripts will run just fine, and you won’t receive any warnings about items that might be unsafe.
Of course, this doesn’t mean that HTAs somehow bypass Windows security. For example, if you don’t have the right to change a user’s password then you can’t use a script to change a user’s password. Placing that script in an HTA won’t make a difference: you still won’t be able to change the user’s password. And HTAs do have some security restrictions of their own. We don’t want to get too bogged down in the details today, so let’s leave it at this:
Although HTAs use Internet Explorer and the Internet Explorer object model, they run in a different process than Internet Explorer. Consequently, they can run scripts and perform other tasks that aren’t allowed in Internet Explorer.
If you’d like more information about HTAs and security, take a look at the HTML Applications SDK on MSDN.
I’m Sold: How Do a Create One of These HTA Things?
Boy, is this a coincidence or what: this article just happens to be the first in a series of tutorials that will teach you - that’s right - how to create your own HTA! (What do you suppose the odds were of that happening?) This month we’ll introduce the basics of HTA construction. In subsequent articles we’ll begin delving into more advanced topics. Should be fun, huh?
Note. What do you mean, “A series of monthly articles takes too long; I want to master HTAs right now!” Well, that might be a bit of a problem. The truth is, there aren’t very many good resources out there for the beginning HTA writer. However, you might check out the HTA Helpomatic tool, which includes a number of code snippets that you can paste into your HTA. And as you get a little better at HTA creation, check out The ABCs of HTAs.
Speaking of basics, we should probably mention right off the bat that HTAs can be created using Notepad or any other text editor. However, because of the heavy-duty use of HTML you might consider creating your HTAs in a Web page editor such as Microsoft FrontPage. Regardless, you create a plain text file and then save it with a .HTA file extension. A very rudimentary - but complete - HTA might look something like this:
<head> <title>HTA Test</title> <HTA:APPLICATION APPLICATIONNAME="HTA Test" SCROLL="yes" SINGLEINSTANCE="yes" WINDOWSTATE="maximize" > </head> <script language="VBScript"> Sub TestSub Msgbox "Testing 1-2-3." End Sub </script> <body> <input type="button" value="Run Script" name="run_button" onClick="TestSub"><p> </body>
Don’t worry; it’s nowhere near as complicated as it might first appear. In fact, it’s possible to break an HTA down into three basic parts, each of which we’ll explain in some detail:
The <HTA:Application> tag
The </script> tag
The <body> tag
The <HTA:Application> Tag
You can actually create an HTA without using the <HTA:Application> tag; your HTA will run just fine and there will be no change in functionality or performance.
So if you don’t have to use the <HTA:Application> tag then why would you ever want to use it? (Hmmm, when you put it that way you sound remarkably like one of the teenage Scripting Sons.…) Well, the <HTA:Application> tag provides you with the ability to control the appearance of your HTA window; this is done by configuring the various tag properties. Here’s the <HTA:Application> tag used in our sample script:
<head> <title>HTA Test</title> <HTA:APPLICATION APPLICATIONNAME="HTA Test" SCROLL="yes" SINGLEINSTANCE="yes" WINDOWSTATE="maximize" > </head>
Note. The <title> tag is actually separate from the <HTA:Application> tag; it simply refers to text that appears in the title bar of the HTA window. We show it here simply because the title is usually found inside the <head> tag, the same place we find <HTA:Application>.
What does this tag do? Well, the ApplicationName property sets the name of the HTA as shown in Windows Task Manager. If you don’t specify an application name, Task Manager displays the path to the HTA file. Setting the Scroll property to Yes causes scroll bars to automatically appear in the HTA if returned data is bigger than the window size; if set to No then only the data that actually fits in the window will be displayed. The SingleInstance property ensures that only one instance of your HTA will run at a time; clicking the HTA file icon will not start a second instance of your little application.
And let’s not forget WindowState: setting the WindowState to maximize causes the HTA to display in a full-sized window each time the HTA starts up.
Some of the more useful HTA Application properties are shown in the following table. For a complete list of properties see the HTML Applications SDK on MSDN.
Sets the name of the HTA. This name is used to enforce the SingleInstance property, and is also the name that appears in Windows Task Manager.
Sets the type of border used for the HTA window. Values include thick (for a resizable window) and thin (for a non-resizable window).
Yes/No value specifying whether the HTA displays a caption in the title bar. The default value is Yes.
Yes/No value specifying whether the HTA displays a Maximize button in the title bar. The default value is Yes.
Yes/No value specifying whether the HTA displays a Minimize button in the title bar. The default value is Yes.
Yes/No value specifying whether the HTA will scroll if returned data is larger than the window size. The default value is Yes.
Yes/No value specifying whether the HTA is shown in the Windows taskbar. The default value is Yes.
Yes/No value specifying whether more than one instance of this HTA can be active at a time. For this property to take effect, you must also specify an ApplicationName. The default value is Yes.
Yes/No value specifying whether the HTA displays the System menu in the title bar. The default value is Yes.
Sets the initial size of the HTA window. Values are Normal, Minimize, and Maximize. By default the WindowState is set to Normal.
The following illustration maps some of these properties to an actual HTA window:
The <script> Tag
Although there are exceptions, many VBScript scripts (that is, .vbs files) do their thing and then get out of there; probably 99% of the scripts found in the Script Center Script Repository do just that. What do we mean when we say these scripts “do their thing and then get out of there?” Well, when you start most VBScript scripts the script host executes line 1, then - without missing a beat - executes line 2, then line 3, and so on. After the last line in the script has been executed, the script ends, the script host process terminates, and the party’s over.
By contrast, HTAs usually don’t work that way; that’s because HTAs are “event driven” applications. In general, when you start an HTA nothing happens; instead the HTA just sits there. In fact, the HTA will sit there forever until an event occurs that the HTA has been programmed to respond to. Typically that means someone clicked a button, selected an item from a drop-down list, or did something similar.
Note. Could you create an HTA that automatically does something the moment you start it? Sure: just include your code inside a subroutine named Window_onLoad. As the name implies, that subroutine automatically runs any time the HTA window is loaded or refreshed. For example, this code automatically calls the TestSub subroutine each time you start your HTA:
In other words, instead of being one big, long, continuous script designed to run from start to finish, an HTA is often composed of separate and distinct smaller scripts: there’s one script that runs any time a user clicks Button A, another script that runs any time a user clicks Button B, and so on. Does this have implications for the scripts we want to use in our HTA? You bet it does.
In fact, there are at least two implications. To begin with, all these separate and distinct scripts must be housed inside a single <script> tag; this, by the way, is standard HTML practice. Second, each “script” is actually a subroutine, one that needs to have a distinct name. (You can name a subroutine anything you want, as long as there are no spaces or other illegal characters in the name. For more information, see the Procedures section of the Microsoft Windows 2000 Scripting Guide.) From now on we’ll use the term subroutine to refer to these blocks of code; that should help differentiate the code we want to run from the <script> tag.
Here’s an example of a <script> tag that includes a single subroutine: TestSub. In a little bit we’ll explain how to add additional subroutines to this tag.
<script language="VBScript"> Sub TestSub Msgbox "Test" End Sub </script>
There’s nothing too complicated here: just remember that you need to place all your code in subroutines and all your subroutines must be inside the <script> tag.
OK, We Give Up. So if HTAs don’t run and then automatically terminate, how do you exit an HTA? Well, the easiest way is probably just to click the window’s Close button. Of course, it’s possible to create an HTA that doesn’t have a Close button; for an example, see this article in the ABCs of HTAs. In a case like that you can use a subroutine like this to exit an HTA:
The <body> Tag
If you’ve ever put together a Web page you know that the body of the document is where most of your “stuff” goes: your text, your tables, your buttons, your check boxes, your horizontal rules, etc. The same thing is true with an HTA. When you build an HTA you typically put the <HTA:Application> tag at the top followed by the <script> tag. Last - but definitely not least - comes the <body> tag. Inside the body tag will be the elements that you want to appear in your HTA’s user interface. In our sample HTA we have a very simple user interface; it consists of a single button labeled Run Script. As you can see, the HTML tagging for the button is included between the <body> and </body> tags:
<body> <input type="button" value="Run Script" name="run_button" onClick="TestSub"><p> </body>
To be honest, the <body> tag is kind of boring; what’s interesting are the elements that go inside that tag. In this first article we’ll focus on two of these elements: buttons and spans. In subsequent articles we’ll cover additional things like check boxes, radio buttons, and list boxes.
My Very First HTA
We know what you’re thinking, “Yeah, yeah, Scripting Guys: now when do we get to the good stuff?” Well, how about right now? Here’s a sample HTA that will tell you the version of the operating system installed on the local computer. To try this HTA out, copy the code, paste it into Notepad, and the save the thing with a .hta file extension (for example, MyFirstHTA.hta). Then just double-click the file icon in My Computer or Windows Explorer and get ready for some excitement:
<head> <title>Operating System Version</title> <HTA:APPLICATION APPLICATIONNAME="Operating System Version" SCROLL="yes" SINGLEINSTANCE="yes" > </head> <script language="VBScript"> Sub GetOSVersion strComputer = "." Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") Set colOperatingSystems = objWMIService.ExecQuery _ ("Select * from Win32_OperatingSystem") For Each objOperatingSystem in colOperatingSystems Msgbox objOperatingSystem.Caption & " " & _ objOperatingSystem.Version Next End Sub </script> <body> <input type="button" value="Operating System" name="run_button" onClick="GetOSVersion"> </body>
So what happens when you start the HTA and click the Operating System button? You should see a message box similar to this:
Yes, we know; but you have to start somewhere, right? We’ll add a few additional features as time goes on. And don’t worry if you don’t understand what we did here. In just a moment, we’ll start walking you through all the relevant lines of code. But first, a quick note about HTA aesthetics.
Making Our HTA Prettier
We admit it: at the moment, our HTA has an appearance that only a mother could love. Can we make it a little prettier, but without having to work too terribly hard at that? Sure we can; after all, the body of the HTA is - at heart - just another HTML page. Therefore we can try making our HTA a little prettier by beefing up the <body> tag. For example, how about a nice cyan background:
That’s right: eat your heart out, Paris Hilton. We can change the background color of our HTA just by adding a bgcolor attribute to the <body> tag. For example, this sets the background color to that breathtaking shade of cyan shown above:
There are plenty of other colors you can use besides cyan; for a list, take a look at the Internet Explorer documentation.
If you have a picture you find suitably attractive you can alternatively set the background attribute to the name of that picture:
<body background = "keyboard.jpg">
Depending on what Keyboard.jpg looks like you’ll end up with something like this:
Cool, huh? Keep in mind that depending on the color of your background you might also need to change the color of your fonts, something we’ll deal with later. And as long as we’re on the subject you might want to check out the gradient background explained in the ABCs of HTAs. That’s way cool, and has the advantage of not requiring a .JPG or other picture file.
Adding a Button
One feature almost every HTA ends up having is a good old-fashioned button. Your HTA might have fancy check boxes and list boxes and other items for configuring options and setting preferences, but at some point you’ll need to do something to tell the HTA to get to work. More often than not that will involve clicking a button.
Adding a button to an HTA is no different than adding a button to an HTML page. For example, this code adds a button labeled Operating System to our HTA:
<input type="button" value="Operating System" name="run_button" onClick="GetOSVersion">
As you can see, we use the <input> tag along with a number of attributes. Let’s take a quick look at those attributes and what they do:
This specifies the type of <input> element to be placed on the page; as we’ll see next month the <input> tag can take on forms other than a button.
“Value” is a misleading term; what we’re really talking about here is the label that appears on the button. In other words, in this example we’re creating a button that has the label Operating System.
As you might expect, this is a unique name given to the button. Why give your button a name? Well, you don’t have to; you can leave this attribute out if you wish. However, if your buttons do have names then they can be configured and modified using scripts. For example, you can enable or disable buttons programmatically.
We’re getting a bit ahead of ourselves here, but this code disables a button named Run_Button:
This is the name of the subroutine we want to run any time the button is clicked. This is optional; you can create buttons that don’t actually do anything when clicked. (And, no, buttons that don’t actually do anything are not known as Scripting Guys Buttons. Though we suppose they could be….)
Incidentally, these are not the only attributes that can be configured for a button. For more information see the HTML and DHTML Reference on MSDN.
Try It Yourself. At the moment we have an HTA with a single button; click that button and the HTA will report back the version of Windows installed on the local machine. That’s probably not going to revolutionize the software industry, but it’s still a useful little utility. Now, suppose we also want to retrieve the version of the latest service pack installed on the computer. Furthermore, suppose we want this to be a separate script; that is, we want to be able to get the operating system version or the service pack version. How can we do that?
Well, we could create a second HTA, one that had a single button and performed a single task (retrieving the service pack version). Alternatively, we could simply add a second button to our existing HTA, a button with the name service_pack and the label (value) Service Pack, which runs a subroutine named GetServicePack any time the button is clicked. (And, yes, we know that this subroutine doesn’t exist. But we’ll take care of that soon enough.) So here’s your assignment: see if you can add a second button to your HTA, and end up with something that looks like this:
And, no, we’re not going to tell you the answer, at least not here. (But if you’re having problems figuring this out, click here.) However, we will give you a hint: try copying and modifying the code for the existing button.
You’re right: we did sort of give it away, didn’t we? Dang….
Adding a Subroutine
Early in this article we showed you a very simplistic subroutine; now let’s take a look at a more practical example, the subroutine we used in our Operating System HTA. Note that the code inside the subroutine is standard WMI code; with a few exceptions involving Windows Script Host (something we’ll discuss in a moment) almost any code that runs in a standalone .vbs script can run in an HTA. You might want to (or have to) make some changes to ensure that the output displays properly, but the basic code functions - connecting to the WMI service, running the ExecQuery method, looping through a collection of returned data - can be left as-is.
Here’s the sample subroutine. Note that - like all subroutines - it’s housed between the <script> and </script> tags:
<script language="VBScript"> Sub GetOSVersion strComputer = "." Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") Set colOperatingSystems = objWMIService.ExecQuery _ ("Select * from Win32_OperatingSystem") For Each objOperatingSystem in colOperatingSystems Msgbox objOperatingSystem.Caption & " " & _ objOperatingSystem.Version Next End Sub </script>
What if we wanted to have a second subroutine, one that runs when we click a different button? That’s no problem; you can include as many subroutines as you want inside the <script> tag. Just make sure that you give each subroutine a unique name, and be sure and use the End Sub statement to mark the end of each subroutine. For example, here’s a very simple subroutine (named MySecondRoutine) that we’ve added to the <script> tag:
<script language="VBScript"> Sub GetOSVersion strComputer = "." Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") Set colOperatingSystems = objWMIService.ExecQuery _ ("Select * from Win32_OperatingSystem") For Each objOperatingSystem in colOperatingSystems Msgbox objOperatingSystem.Caption & " " & _ objOperatingSystem.Version Next End Sub Sub MySecondRoutine Msgbox "This is my second subroutine." End Sub </script>
Try It Yourself. If you did the previous Try It Yourself exercise you have a second button in your HTA interface, one that - in theory - retrieves the version of the latest service pack installed on the computer. Why do we say “in theory?” Because, at the moment, we have a button, but that button isn’t tied to a script that can retrieve the service pack version. So let’s see what we can do about that. Here’s a script that reports back the version of the latest service pack installed on the computer:
strComputer = "." Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") Set colOperatingSystems = objWMIService.ExecQuery _ ("Select * from Win32_OperatingSystem") For Each objOperatingSystem in colOperatingSystems Msgbox objOperatingSystem.ServicePackMajorVersion _ & "." & objOperatingSystem.ServicePackMinorVersion Next
See if you can take this code and add a subroutine named GetServicePack to your HTA. After you’ve added the subroutine, start the HTA, click the Service Pack button and verify that your new subroutine works. If you have difficulty with this, click here.
Why Can’t I Use Wscript.Echo?
You might have noticed that when it came time to report back the operating system version we used the VBScript Msgbox function rather than the more common Wscript.Echo. Why didn’t we use Wscript.Echo? Here’s why:
As it turns out the various Wscript methods - Wscript.Echo, Wscript.Sleep, Wscript.Quit, etc. - are designed solely to run under the Windows Script Host environment. When we’re working in an HTA we’re not running under WSH; instead we’re running under the MSHTA process. Because of that the Wscript methods are not available to us (nor can we create them). Consequently we need to find workarounds for each method, and Msgbox is a perfectly adequate replacement for Wscript.Echo. (We’ll talk about workarounds for other methods - such as Wscript.Sleep - when we get to them.)
The moral of the story: Don’t bother with Wscript.Echo; it won’t work.
Using a <span> Tag
So far our HTA works great: you click a button and you get back a message box with information about the operating system or the latest service pack installed on a computer. Very cool. Now, suppose we want to add the ability to retrieve a list of all the processes running on a computer. That’s easy enough, except for one thing: right now the computer being used to type this tutorial has 49 processes running on it. That’s a problem; who wants to respond to 49 message boxes, one for each process?
We’re not silly enough to say “nobody,” but we’ll go out on a limb and say that most people would rather not click OK 49 times. (47 or 48 maybe, but not 49.) So let’s see. We need to display data somewhere other than in a message box, and we have an HTA that has plenty of white space just sitting there doing nothing. Hmmm, that gives us an idea….
Did someone say, “Why don’t we just display the data in the HTA itself?” Good idea; in fact, way better than the idea we came up with. This is one of the real benefits of using an HTA: not only can you display data in your application itself but - as we’ll see - you can also display formatted data in that HTA. But how exactly do we do that?
Actually there are several different ways. The one we find the easiest to code involves the use of the <span> tag. The <span> tag enables you to name an area within your HTA and then programmatically manipulate all the HTML elements within that tag. What does that mean? Well, save the following code as a .HTA file, run the thing, and then click the Operating System button:
<head> <title>HTA Test</title> <HTA:APPLICATION APPLICATIONNAME="HTA Test" SCROLL="yes" SINGLEINSTANCE="yes" > </head> <script language="VBScript"> Sub GetOSVersion strComputer = "." Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") Set colOperatingSystems = objWMIService.ExecQuery _ ("Select * from Win32_OperatingSystem") For Each objOperatingSystem in colOperatingSystems DataArea.InnerHTML = objOperatingSystem.Caption & " " & _ objOperatingSystem.Version Next End Sub </script> <body> <input type="button" value="Operating System" name="run_button" onClick="GetOSVersion"> <p> <span id = "DataArea"></span> </body>
Your screen should look something like this:
See? No message box; instead our data is written to the HTA itself.
To accomplish this astonishing feat we didn’t have to sell our souls, we only had to do two things. First we modified the <body> area to include a <span> (and specified an id of DataArea). Our revised <body> area looks like this:
<body> <input type="button" value="Operating System" name="run_button" onClick="GetOSVersion"> <p> <span id = "DataArea"></span> </body>
We still have our button, but now we’ve added two more items to the body. First, we added a <p> tag; this is just a paragraph tag, something we added to put a little bit of space between our button and the data to be displayed. Second we added a <span> tag, setting the id to DataArea; that’s what we do here:
<span id = "DataArea"></span>
Notice that our <span> is empty; there’s nothing between the starting and closing tags. If we wanted to, we could put something there to start with. For example, this revised tag has some instructions to the user:
<span id = "DataArea">Click the button to return information about the operating system.</span>
When we click the button this instructional message will disappear and be replaced by the information returned from the GetOSVersion subroutine.
So how exactly do we accomplish all this? If you recall, in the first incarnation of our HTA we retrieved the operating system version and then displayed that information using the Msgbox function. The code for doing that looked like this:
Msgbox objOperatingSystem.Caption & " " & _ objOperatingSystem.Version
Now take a look at our revised code:
DataArea.InnerHTML = objOperatingSystem.Caption & " " & _ objOperatingSystem.Version
What we’ve done here is replace Msgbox with this:
InnerHTML is a property of the <span> tag; it simply refers to everything contained inside the <span> and </span> tags, including text, HTML formatting, and other HTML tags. All we do here is set the InnerHTML property of the span named DataArea to the operating system version. What if, for some unknown reason, we wanted to set the value of InnerHTML to “No information available.”? Then we’d use code like this:
DataArea.InnerHTML = "No information available."
Each time we change the value of the InnerHTML property the old value is deleted and the new value takes its place.
Try It Yourself. If you’ve been doing the Try It Yourself exercises then you should have an HTA that includes a button named Service Pack; when that button is clicked it runs a subroutine named GetServicePack. At the moment, GetServicePack displays the returned information in a message box. See if you can modify the body of the HTA and the GetServicePack subroutine to write the returned information to the HTA itself (like, say, maybe in a <span>….). If you have problems figuring out how to do this, click here.
Did Someone Say Formatting?
Ah, yes. Those of you used to writing scripts that run in a command window might have forgotten that fonts can actually come in different sizes, styles, and colors. We won’t spend a lot of time talking about formatting today; suffice to say that any formatting that can be applied using HTML tags can be included within the InnerHTML property. For example, suppose you were creating a regular Web page and wanted some of the text displayed in red Arial. How would you do that? Well, you’d likely do something like this:
<font color='red' face='Arial'>No information available.</font>
As you can see, we enclosed the text itself inside the <font> and </font> tags. As part of the <font> tag, we assigned values to the color and face attributes. That’s it; it’s that easy.
So how would we specify this same formatting when assigning a value to the InnerHTML property? Why we’d just use the exact same syntax:
DataArea.InnerHTML = "<font color='red' face='Arial'>No information available.</font>"
Still not satisfied? Would you like this text to be boldface as well? Then just add the <b> and </b> tags:
DataArea.InnerHTML = "<b><font color='red' face='Arial'>No information available.</font></b>"
If you want to change the size or make the text blink (um, not recommended) or do any of the other things you can do with text then just add the appropriate HTML tags. For more information about the formats that can be applied to HTML elements take a look at the HTML and DHTML Reference on MSDN.
Displaying Multi-Line Data
As we Scripting Guys are wont to do, we seem to have wandered off-track a little: didn’t this whole <span> discussion come up because we wanted a way to display the names of all the running processes in our HTA? As a matter of fact, it did. So maybe we should take a look at that.
This isn’t particularly hard, but it is a little bit tricky. Why? Well, suppose we use a subroutine similar to this:
Sub GetProcesses strComputer = "." Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") Set colProcesses = objWMIService.ExecQuery("Select * from Win32_Process") For Each objProcess in colProcesses DataArea.InnerHTML = objProcess.Name Next End Sub
OK, that looks pretty good, doesn’t it? Yes, it does; unfortunately, it doesn’t work. If you run this subroutine you’ll have only one process displayed in your HTA. What happened to the other 48 processes?
What happened is that we goofed when we set up the For Each loop that cycles through the collection of processes. Take a close look at what happens inside that loop:
For Each objProcess in colProcesses DataArea.InnerHTML = objProcess.Name Next
Each time we encounter a new process we set the InnerHTML to the name of that process. Remember what we said about InnerHTML: each time we assign InnerHTML a new value the old value - that is, whatever used to be between the <span> and </span> tags - disappears. That’s exactly what’s happening here. We write the name of the first process to InnerHTML, then loop around, get the name of the second process, and write that value to InnerHTML. That’s fine, except that writing a new value erases the previous value. This keeps going until we write the name of the last process in the collection, which - because we don’t overwrite it - ends up being the only item displayed.
No, not good at all. But that’s OK; this is easy enough to fix. The trick here is not to write to the InnerHTML property each time we get the name of a process. Instead what we want to do is save up the names of all those processes and - as soon as we have the entire collection - write the whole kit-and-kaboodle to the InnerHTML property. Let’s show you a revised subroutine, then explain what we did different. (Notice, if you’re cutting and pasting as we go, the name of this new subroutine. You’ll need to change your onClick process to match. It would also make sense to change the name of the button to something like “Processes.”)
Sub GetProcesses strComputer = "." Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") Set colProcesses = objWMIService.ExecQuery("Select * from Win32_Process") For Each objProcess in colProcesses strHTML = strHTML & objProcess.Name & "<br>" Next DataArea.InnerHTML = strHTML End Sub
There are two major differences here. To begin with, we’ve radically revised our For Each loop:
For Each objProcess in colProcesses strHTML = strHTML & objProcess.Name & "<br>" Next
Notice that we no longer set the value of the InnerHTML property. Instead each time we loop through the collection of processes we assign a variable named strHTML the following:
The existing value of strHTML
The name of the process
The HTML tag <br>
Why? Well, suppose the first time we run through the loop we encounter a process named winword.exe; consequently strHTML will equal this:
How did we get that? This is just the value of strHTML (which, the first time through the loop, is equal to nothing) plus the name of the process (winword.exe) plus the <br> tag. We now loop through a second time and encounter a process named outlook.exe. Our variable strHTML now equals this:
Again, we simply took the existing value of strHTML - winword.exe<br> - and tacked on the name of the next process - outlook.exe - and another <br> tag. We keep repeating this process until we have looped through the entire collection.
Only then do we actually assign a value to the InnerHTML property. And this time around we assign InnerHTML the value of the variable strHTML, which just happens to contain a list of all the process names. Here’s the code that takes care of that:
DataArea.InnerHTML = strHTML
When we run the HTA and click the Processes button we get back something like this:
That’s more like it.
Why All the <br> Tags?
Good question. Remember that we’re dealing with HTML here and in HTML the <br> tag represents a carriage return-linefeed: it’s like being in Notepad and pressing the ENTER key. If we want an item (such as a process name) to start on the next line we need to include a <br> tag; it’s as simple as that. Suppose we left out the <br> tag, and had a subroutine similar to this:
Sub GetProcesses strComputer = "." Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") Set colProcesses = objWMIService.ExecQuery("Select * from Win32_Process") For Each objProcess in colProcesses strHTML = strHTML & objProcess.Name Next DataArea.InnerHTML = strHTML End Sub
Here’s what we’d get when we ran the subroutine:
Instead of having a nice list of processes - one process per line - we have a huge single line of processes, all glommed together (there isn’t even a space to separate the individual process names). That’s because we need to manually enter line breaks, which is why we need all those <br> tags.
Yeah, that was a pretty dramatic demonstration, wasn’t it?
Try It Yourself. Thus far we’ve simply echoed one property value - Name - for each process running on a computer. See if you can modify the subroutine so that it echoes an additional property value - ProcessID - on the same line. Your finished HTA should look something like this:
If you run into problems, click here.
Displaying Data in a Table
Our preceding HTA (from the last Try It Yourself exercise) displays the value of two different properties. And, to be honest, our output looks fine; with the two dashes it’s not too hard to figure out where one property value ends and another begins. Suppose, however, that we were displaying 4 or 5 values on a single line? Output like this is a bit harder to decipher:
How can we make this output easier to deal with? Well, one way is to display the information as a table. We don’t have time to talk about tables this month; we’ll have to save that for another time. Just to whet your appetite a little, however, here’s a variation on our last Try It Yourself exercise. This subroutine displays both the process name and the process ID; the difference is that it displays this information in a two-column table:
Sub GetProcesses strComputer = "." Set objWMIService = GetObject("winmgmts:\\" & strComputer & _ "\root\cimv2") Set colProcesses = objWMIService.ExecQuery("Select * From Win32_Process") strHTML = "<table border='1' style='border-collapse: collapse' " & _ "bordercolor='#111111' width='100%' id='Table1' >" For Each objProcess in colProcesses strHTML = strHTML & "<tr>" strHTML = strHTML & "<td width='50%'>" & objProcess.Name & "</td>" strHTML = strHTML & "<td width='50%'>" & objProcess.ProcessID & _ "</td>" Next strHTML = strHTML & "</table>" DataArea.InnerHTML = strHTML End Sub
And here’s what the actual HTA looks like after the subroutine runs:
In Our Next Exciting Episode
In this first lesson we’ve simply introduced the basics of creating an HTA; nevertheless you can actually create a useful little utility based solely on what we’ve covered here. Next month we’ll expand our horizons a bit, and introduce new elements such as check boxes, radio buttons, and drop-down lists. You won’t want to miss it!