Downloads
44580.zip

Rather than reinventing the wheel each time they create a script, scripters often adapt existing code. However, there are many situations in which you might have a script but need to adapt it to run under a specific security context. For example, suppose you need a script to monitor Windows services locally and remotely and you find a script called ServiceMonitor.wsf. This script performs the tasks you want to automate. It uses Windows Management Instrumentation (WMI) to monitor services, restart a monitored service when it stops, and send an email alert if the service doesn't restart. However, you notice that to run ServiceMonitor.wsf on remote machines, users need to enter an ID and password when they launch the script. Because this information is entered on the command line in plaintext, this practice isn't secure. In addition, because the script provides alerts by sending email messages through a specified secured SMTP relay, the security credentials to establish an SMTP connection are hard-coded in the script, which is another insecure practice. Although you could configure the SMTP relay to accept anonymous connections, doing so is undesirable because it introduces other security risks.

Fortunately, there are several ways to work around these insecure practices. Three common solutions for Windows Script Host (WSH) scripts are to prompt users for passwords, encode the script, and run the script as a Windows service. Before I explain how to implement these solutions, though, you need to know a little bit about the ServiceMonitor.wsf script that we'll be adapting.

ServiceMonitor.wsf
Listing 1 shows an excerpt from ServiceMonitor.wsf. You can download the entire script from the Windows Scripting Solutions Web site. Go to http://www.windowsitpro.com/windowsscripting, enter InstantDoc ID 44580 in the InstantDoc ID text box, then click the 44580.zip hotlink.

To launch this script, you use the command

ServiceMonitor.wsf ServiceName
  \[ServiceName\] \[/Machine:value\]
  \[/User:value\] \[/Password:value\]

(Although this command appears on several lines here, you would enter it on one line. The same holds true for the other multiline commands in this article.) In this command, each ServiceName parameter specifies the name of a Windows service to monitor. You can specify one or more services. The /Machine, /User, and /Password parameters are optional. If you don't include them, the script will connect to the WMI system on the local host by using the security credentials of the user running the script. If you want to connect to the WMI system on a remote machine, you use the /Machine parameter's value to specify the name of the remote machine, the /User parameter's value to provide the user ID for the remote connection, and the /Password parameter's value to specify the password for the remote connection.

The overall structure of ServiceMonitor.wsf is similar to the structure of the GenericEventAsyncConsumer .wsf script, which I documented in the Exchange & Outlook Administrator article "Exchange 2000 SP2 WMI Updates," January 2003, InstantDoc ID 27211. Therefore, I won't dig into the details of the script here.

Prompting for Passwords
One way to secure passwords is to have the script prompt users for the required passwords when they launch the script. Windows Server 2003 and Windows XP expose a new COM object called ScriptPW.Password. This object works in conjunction with the standard input stream to read any text input. This object doesn't display the input text on the screen, which is perfect for a password prompt.

To modify ServiceMonitor.wsf to prompt for passwords, follow these steps:

1. Delete the code that callout A in Listing 1 shows. This code isn't needed because the script will prompt users for passwords.

2. Add the tag

  id="objPasswordPrompt" />

after the code that callout B in Listing 1 shows. This tag defines the ScriptPW.Password object so that the script can use it.

3. Replace the code at callout D in Listing 1 with the code that Listing 2 shows. The new code prompts the user for the password for the remote WMI connection, then the password for the SMTP connection. Note that the password for the WMI connection is prompted only when the /User parameter is specified. If no /User parameter is specified, the script uses the security context of the user running the script for the WMI connection.

In the 44580.zip file, you'll find a script called ServiceMonitorWithPrompt.wsf. This script contains the modifications I just provided. To launch this script, you use the command

ServiceMonitorWithPrompt.wsf
 ServiceName \[ServiceName\]
 \[/Machine:value\] \[/User:value\]

Encoding the Script
Although ServiceMonitorWithPrompt.wsf secures the passwords used during the script's execution, the script requires user intervention at start-up. Thus, ServiceMonitorWithPrompt.wsf isn't appropriate for unattended runs. However, hard-coding passwords in plaintext the way ServiceMonitor.wsf stores the SMTP password presents an obvious security risk. Fortunately, the Script Encoder (scr10enc.exe) lets you encode VBScript and JScript files so that you can hard-code passwords with minimal risk. You can download this free utility from the Microsoft Developer Network (MSDN) at http://msdn.microsoft.com/library/default.asp?url=/downloads/list/webdev.asp.

When you use the Script Encoder, the script's extension changes to .vbe and .jse for VBScript and JScript files, respectively. This extension change is important because it informs the WSH scripting engines that the script is encoded.

Unfortunately, the Script Encoder doesn't work for .wsf files because it isn't aware of the XML tags that WSH uses. However, there's a workaround you can use. Follow these steps:

1. In the ServiceMonitor.wsf file, cut the VBScript code in the section and save the code in a .vbs file.

2. Use the Script Encoder to encode the .vbs file.

3. Cut the encoded code from the .vbe file and paste it back in the ServiceMonitor.wsf file's section.

In the 44580.zip file, you'll find a script called ServiceMonitorEncoded.wsf that contains the encoded section. Figure 1 shows an excerpt from this script. Note the VBScript.Encode statement in the