Q: I have a Citrix farm of more than 150 servers, all born from the same image. From time to time, I get a call from our Help desk about a user who can't view an image or PDF file in Microsoft Internet Explorer (IE). When I look at the ActiveX controls on the Citrix server that the user is logged on to, the server shows that the ActiveX control's status is "Damaged" or "Unknown." Figure 1 shows examples of these statuses. I'm uncertain of what's causing the ActiveX controls to become damaged, so I can't attack the problem that way. So, I'd like to do the next best thing: Run a script every morning on all my servers to catch these problems. If a problematic server is found, I can email the network engineer. The engineer can then remove that server from the Citrix pool, reinstall the ActiveX control, and return the server to its assigned pool, which should help reduce these types of calls to the Help desk. Can you help me with such a script?
— Oscar Perez
A: You bet. The key to writing such a script is the MicrosoftIE_Object class. Never heard of it? Don't feel bad. According to the Microsoft Scripting Guys, this class has never been documented.
As the Scripting Guys mention in "Hey, Scripting Guy: How Can I Return a List of Downloaded Controls and Applets for Internet Explorer?" (http://www.microsoft.com/technet/scriptcenter/resources/qanda/dec04/hey1220.mspx), you can use the MicrosoftIE_Object class with the root\cimv2\Applications\MicrosoftIE namespace to get the name and status of all the controls and other programs that have been downloaded for IE on a machine. That's exactly what the script IEDLProgStatus.vbs does.
Listing 1 shows IEDLProgStatus.vbs. To use this script, you need to create an input file that contains the names of the servers you want to check. Place each server's name on a separate line so that it looks like
Save the input file as a text file .
The servers you specify in the input file need to be running Windows Management Instrumentation (WMI). The machine on which you run IEDLProgStatus.vbs must also be running WMI as well as Windows Script Host (WHS). To run the script, you follow the syntax
where server_list is the name of the input file. If the filename includes spaces, you need to enclose the filename in quotes. The script is currently set up to run under CScript because it's written to display on the console. If CScript isn't your default host, you can follow this launch syntax instead
cscript iedlprogstatus.vbs server_list
Now that you know how to run the script, let's look at what will happen when you do. The script begins with the On Error Resume Next statement, which means the script won't stop executing if it encounters a runtime error. You should include this statement in a script only when you know nonfatal errors might occur. In this case, an error could occur if the script can't connect to one of the servers. By specifying On Error Resume Next, the script will simply move on to the next server rather than quitting, which would happen if the statement weren't included.
Next, the script sets the ForReading constant, which will be used to read the input file. However, before the script can read that file, it has to find out the file's name. So, in the code at callout A in Listing 1, the script retrieves the argument you entered on the command line by using the WScript object's Arguments property in WSH. The script then counts the number of command-line arguments to make sure you provided only one. If you didn't specify a filename or you specified a filename that includes spaces but didn't enclose the filename in quotes (in which case the script will read each word in the filename as a separate argument), the script's launch syntax displays and the script quits. The launch syntax will also display if you put /? as the command-line argument.
After IEDLProgStatus.vbs has the filename in hand, it makes sure the file exists. To do so, it first creates an instance of a FileSystemObject object, which it assigns to the oFSO variable. The FileSystemObject object, which is part of WSH's Microsoft Scripting Runtime Library, has many methods and properties that let you work with files and folders. In this case, the script is using the object's FileExists method to check for the existence of the input file. If the file doesn't exist, the script displays an error message, followed by the launch syntax, then quits.
If the file exists, IEDLProgStatus.vbs opens it by using the FileSystemObject object's OpenTextFile method. This method takes the name of the input file and the ForReading constant set earlier as its arguments and returns a TextStream object (oFile), whose methods and properties you can use to read and write to files. Although you could use these methods and properties to read the server names directly from the input file, putting the file's contents into an array is preferable because you can use a For Each...Next statement to iterate through the contents. Thus, the script prepares the server names for placement into an array.
In order to get the server names into an array, the script uses the TextStream object’s ReadLine method to read each server name in the text file, then adds it to a comma-delimited string stored in the strServerList variable. However, because you (or whomever created the input file) might have inadvertently added spaces before or after a server name, the script first uses the Trim function to remove any superfluous spaces before adding the name to the comma-delimited string. Finally, to convert this string into an array called arrServerList, the script uses the Split function. This function takes in two parameters: a string containing the items you want to convert and a string representing the delimiter used to separate each element within the string.
The code at callout B checks the status of all the controls that have been downloaded for IE. For each server name in the array, the code calls the CheckIE subroutine, which callout C shows. This subroutine uses WMI to connect to the root\cimv2\Applications\MicrosoftIE namespace on the servers and retrieve the desired information.
In case you're unfamiliar with WMI, let's take a closer look at that part of the CheckIE subroutine. The code
creates an SWbemLocator object. This object lets you connect to a namespace on a local or remote computer. Next, the subroutine uses the SWbemLocator object's ConnectServer method in the code
to connect to the root\cimv2\Applications\MicrosoftIE namespace on the server specified in strComputer. Because strComputer represents a remote server, the code
Level = 3
sets the DCOM impersonation level to 3 (i.e., Impersonate), which means that WMI will use the security context of the person launching the script to perform the requested operation. (Impersonate is the recommended impersonation level for WMI scripts.)
Assuming that the server is available and the permissions acceptable, WMI returns an SWbemServices object representing the root\cimv2\Applications\MicrosoftIE namespace on the target remote server. The subroutine then uses that object's ExecQuery method in the code
("Select * from MicrosoftIE_Object")
to retrieve all the information from the MicrosoftIE_Object class.
The retrieved information is in a collection, so the subroutine uses a For Each…Next statement to iterate through it. The MicrosoftIE_Object class's ProgramFile and Status properties are used to obtain each control's name and status, respectively. The control information is displayed on screen.
When the script can't connect to the server because the server is unavailable or the permissions unacceptable, a runtime error occurs. Because the On Error Resume Next statement was used, the script doesn't fail. Instead, it moves on to the next server. However, it's important to know when a connection failed, so the code at callout B uses VBScript's Err object to check for runtime errors. If one occurs, an error message is displayed on screen.
As you can see, the key to writing this script is knowing about the MicrosoftIE_Object class and root\cimv2\Applications\MicrosoftIE namespace. Once you're armed with that knowledge, writing the script is a fairly simple task. You just need to access the input file, read its server names into an array, and use WMI to connect to each server and retrieve the control information from the MicrosoftIE_Object class.