[Previous] [Table of Contents] [Next]

User Dialog Boxes and Output

We've looked at numerous samples showing script output in user dialog boxes. Now I'd like to share a few hints and programming techniques for handling script output in user dialog boxes. I'll also discuss input/output streaming in the Command Prompt window (console) and offer a few hints on printing different document types in WSH scripts.

Using Tabs and Line Feeds

When you submit a string to the dialog box methods (such as Echo and Popup) and functions (such as MsgBox) mentioned in previous chapters, the string is formatted automatically to fit in the dialog box. But you can insert tab and line-feed characters into the string to format the output as you wish. VBScript uses the predefined vbCrLf constant to force a line wrap:

WScript.Echo "First line " & vbCrLf & "Second line "

In JScript, you add the escape characters \n (new line) to the string:

WScript.Echo("First line \nsecond line ");

To format columns in a dialog box, you can insert tab stop characters into the text. In VBScript, you do this by using the named constant vbTab in your string. In JScript, you insert the escape characters \t in the string to force a tab. Tab stop positions are fixed within the dialog box.

Displaying Console Input and Output

To display console input and output in the Command Prompt window, you can use the StdIn and StdOut stream properties in WSH 2. To use these stream properties, you must run the script in CScript.exe. (WScript.exe causes a run-time error because Windows applications don't support input/output streams.)

The following sample uses the methods of WSH 2 to read user input from the command line and echo the typed string. This script must be executed in CScript.exe. Because there's no guarantee that the user will launch the script with the right host, the script itself forces execution of CScript.exe.

If (Not IsCScript()) Then
    Set WshShell = WScript.CreateObject("WScript.Shell")
    WshShell.Run "CScript.exe " & WScript.ScriptFullName 
    WScript.Quit            ' Terminate script.
End If

If the user-defined function IsCScript returns the value False, the script executes the Run method to launch a second copy of the script. This copy is executed in CScript.exe. The function IsCScript uses the following code:

Function IsCScript()
    ' Check whether CScript.exe is the host.
    If (InStr(UCase(WScript.FullName), "CSCRIPT") <> 0) Then
        IsCScript = True
    Else
        IsCScript = False
    End If
End function

The function checks whether the host returns a string containing "CSCRIPT". This string is returned from the FullName property of the WScript object.

You can use the following command to write to the output stream:

WScript.StdOut.WriteLine "Please enter something"

The command uses the StdOut property of the WScript object and executes the WriteLine method. The method shows the submitted parameter in the command prompt window (Figure 14-4).

Click to view at full size.

Figure 14-4 User input at the command prompt

To read user input from the console input, you use the ReadLine method of the StdIn property of the WScript object, as shown in the following command:

tmp = WScript.StdIn.ReadLine

The user input returned from the method is assigned to the variable tmp. ReadLine pauses the script until a new line character is detected (that is, until the user presses the Enter key). The entire sample program is shown in Listing 14-7.

Listing 14-7 StdIO.vbs

'************************************************
' File:   StdIO.vbs (WSH 2 sample in VBScript) 
' Author: (c) G. Born
'
' Using the WSH 2 StdIn and StdOut properties
'************************************************
Option Explicit

Dim tmp, WshShell

' Test whether the host is CScript.exe.
If (Not IsCScript()) Then
    Set WshShell = WScript.CreateObject("WScript.Shell")
    WshShell.Run "CScript.exe " & WScript.ScriptFullName 
    WScript.Quit            ' Terminate script.
End If

WScript.StdOut.WriteLine "Please enter something"
tmp = WScript.StdIn.ReadLine

WScript.StdOut.WriteLine "Your input was: " & tmp

WScript.StdOut.WriteLine "Please press Enter"
tmp = WScript.StdIn.ReadLine ' Wait until key is pressed.

Function IsCScript()
    ' Check whether CScript.exe is the host.
    If (InStr(UCase(WScript.FullName), "CSCRIPT") <> 0) Then
        IsCScript = True
    Else
        IsCScript = False
    End If
End Function

'*** End

Writing to a Line and Reading from It

The sample above writes a string to the command line, forces a new line, and reads the user input. In most cases, you should write a string and read the user input on the same line. You can do this using the Write method instead of the WriteLine method:

WScript.StdOut.Write("Enter a number> ")

The statement above writes text to the command line but doesn't advance the cursor to the next line. The user can then enter something on the same line in the Command Prompt window. This user input can be read using the Read method or the ReadLine method.

You can use the Read method to read a given number of characters. The following command retrieves the first character of input after the user presses Enter:

tmp = WScript.StdIn.Read(1)

If the user types the string 1234{Enter}, where {Enter} stands for the Enter key, Read(1) returns the value 1. (The Read method is especially useful when reading fixed-length fields from a data file.) Listing 14-8 shows how to use Write and Read to handle user input and messages on one line.

Listing 14-8 StdIO1.vbs

'************************************************
' File:   StdIO1.vbs (WSH 2 sample in VBScript) 
' Author: (c) G. Born
'
' Using the WSH 2 StdIn and Stdout properties
'************************************************
Option Explicit

Dim tmp, WshShell

' Test whether the host is CScript.exe.
If (Not IsCScript()) Then
    Set WshShell = WScript.CreateObject("WScript.Shell")
    WshShell.Run "CScript.exe " & WScript.ScriptFullName 
    WScript.Quit            ' Terminate script.
End If

' Write a string without a new line.
 WScript.StdOut.Write("Type in a three-digit number and press Enter> ")

' Use the Read method to get the first character of input.
tmp = WScript.StdIn.Read(1)

' Echo the data.
WScript.StdOut.WriteLine("The first character was " & tmp)

' Next line
WScript.StdOut.Write("Type in a three-digit number and press Enter> ")

' Use ReadLine to retrieve the data.
tmp = WScript.StdIn.ReadLine()

' Show input using the Echo method.
WScript.Echo "You entered", tmp
WScript.Echo "Please press the Enter key to quit the script" 

WScript.Echo WScript.StdIn.Read(1)  ' Wait.

Function IsCScript()
    ' Check whether CScript.exe is the host.
    If (InStr(UCase(WScript.FullName), "CSCRIPT") <> 0) Then
        IsCScript = True
    Else
        IsCScript = False
    End If
End Function

'*** End

Piping Program Output

In the Command Prompt window, the piping mechanism allows you to use the output of one program as the input of another program. The following command uses the output of the dir command as input for the more filter:

dir *.* | more

You can use a similar command to pipe some input to a script program. Let's say that the program StdIOFilter.vbs reads the default stream as input. We can type this command to cause the command prompt to execute the dir command:

dir *.* | CScript.exe StdIOFilter.vbs

The dir output will be piped as input for the script program. It is important to use CScript.exe to call the host. StdIOFilter.vbs can read the standard input stream, process the input, and write the result to the standard output. Listing 14-9 reads the input stream, adds a leading and a trailing line, converts all characters to uppercase, and writes the result to the default output stream.

Listing 14-9 StdIOFilter.vbs

'***************************************************
' File:   StdIOFilter.vbs (WSH 2 sample in VBScript) 
' Author: (c) G. Born
'
' Reading input data and sending it
' to the output stream
'***************************************************
Option Explicit

Dim tmp

' Now read the input stream.
tmp = WScript.StdIn.ReadAll()

' Try to write the result to the standard output stream.
WScript.StdOut.WriteLine("*** Output Filter demo ***")
WScript.StdOut.Write(UCase(tmp))
WScript.StdOut.WriteLine("*** End of input data ***")

'*** End

NOTE
To launch StdIOFilter.vbs, you must use the command shown above. You cannot use the IsCScript function to shell a second copy of CScript.exe because WSH can't handle the input/output streams properly.

Using Files for Streaming

Let's take a look at how a script can use the TextStream object to read data from a text file, process the data, and display it. Our sample VBScript program can run under CScript.exe or WScript.exe. It shells a dir command, which creates a directory listing of drive C:\ and redirects the output to a temporary file. Then the script reads the temporary file and removes the header with the volume name. Then it strips the date and time columns from the directory listing. The result (including the two summary lines) is shown in a dialog box.

This simple script demonstrates a few new techniques. First, it creates a temporary file in the system's temporary folder using the methods of the FileSystemObject object:

Set fso = CreateObject("Scripting.FileSystemObject")
tmp = fso.GetTempName

The variable tmp contains a name for a temporary file that is generated by the operating system. Next, you find the path of the folder that the operating system uses to store temporary files. You retrieve the location of this folder using the GetSpecialFolder method:

path = fso.GetSpecialFolder(tmpFolder)

The tmpFolder parameter must be set to the constant value 2.

NOTE
A value of 0 for the tmpFolder parameter returns the path to the Windows folder. The value 1 returns the location of the system folder. In previous chapters, I mentioned that the system folder depends on the operating system. Windows 95 and Windows 98 have the \System folder, and Windows NT and Windows 2000 use \System32. Therefore, you can use GetSpecialFolder with a value of 1 to retrieve the location and name of the system folder independent of the operating system.

After retrieving the path of the temporary folder, you can create the absolute path of the temporary file:

tmp = path & "\" & tmp

The following two statements shell a dir command, which creates a temporary file containing the results of the command:

Set WshShell = CreateObject("WScript.Shell")
WSHShell.Run "%comspec% /c " & cmd & " >" & tmp, 0, True

The variable %comspec% contains the name of the command processor. The second parameter is set to 0 (normal window size), and the third parameter is set to True to force the script to pause until the command terminates.

The following command opens the temporary file and returns a TextStream object associated with the file:

Set oFile = fso.OpenTextFile(tmp)

You can then use the following loop to skip the first four lines:

For i = 1 to 4
    oFile.SkipLine
Next

The next statement reads the rest of the temporary file into the string variable txt:

txt = oFile.ReadAll

After the temporary file is read, you can close and delete it using the following commands:

oFile.Close 
fso.DeleteFile tmp

The variable txt contains the file's content in a single string. If you want to process this content line by line, you can use the Split function:

lines = Split(txt, vbCrLf)

This function splits the string into an array, using the separator given as a second parameter. You can then access the lines using the array elements lines(0), lines(1), and so on.

Next, you can join the array into a single string using the following command:

txt = Join(lines, vbCrLf)

The Join function adds the pattern submitted in the second parameter to each line and stores the result to a string. The entire VBScript sample is shown in Listing 14-10. The script shows two dialog boxes, the first containing the original output of the dir command and the second showing the filtered data (with the time and date stripped off).

Listing 14-10 Pipe.vbs

'************************************************
' File:    Pipe.vbs (WSH 2 sample in VBScript) 
' Author:  (c) G. Born
'
' Exchanging data between MS-DOS
' commands and a script using temporary files
'************************************************
Option Explicit

Const cmd = "dir c:\*.* /ON"   ' Command to be executed
Const tmpFolder = 2        ' System's temp folder

Dim WshShell, fso, oFile, tmp, path, txt, lines, i

' Create a FileSystemObject.
Set fso = CreateObject("Scripting.FileSystemObject")

' Retrieve a temporary filename.
tmp = fso.GetTempName     

' Retrieve the path to the temporary system folder.
path = fso.GetSpecialFolder(tmpFolder)
tmp = path & "\" & tmp     ' Create temporary file path.

' Now we have a temporary file. Create a WshShell object...
Set WshShell = CreateObject("WScript.Shell")
' ...and shell an MS-DOS command that redirects 
' the output to the temporary file.
WSHShell.Run "%comspec% /c " & cmd & " >" & tmp, 0, True

' The MS-DOS command is finished. A temporary file
' now exists containing the output of the dir command.
' Read the file's content.
Set oFile = fso.OpenTextFile(tmp)

' Skip the first lines.
For i = 1 to 4
    oFile.SkipLine
Next
txt = oFile.ReadAll                  ' The rest of the file
oFile.Close 
fso.DeleteFile tmp                   ' Delete temp file.

WScript.Echo txt     ' Show content.
' Here we can try to process the content.
' First, split all lines into an array.
lines = Split(txt, vbCrLf)
' Ignore the last 2 lines.
For i = 1 to UBound(lines) - 3 
    lines(i) = Mid(lines(i), 40) ' Split off the date and time.
Next

' Join the array data into one string and show it.
MsgBox Join(lines, vbCrLf)

'*** End

Logging Script Output

To log the script output to a file, you can use the LogEvent method of the WshShell object (provided in WSH 2). This method appends the data to a log file. In Windows NT and Windows 2000, the entry goes into the system's log file; you can view it using the Event Viewer (Figure 14-5).

Click to view at full size.

Figure 14-5 Viewing a log entry in the Event Viewer

In Windows 95 and Windows 98, the event is appended to the file WSH.log, which is in the user's Windows folder. The entry is written as a record with a date/time stamp, the event type, and the text submitted to the LogEvent method. To call this method, you can use the following code:

Set WshShell = WScript.CreateObject("WScript.Shell")
flag = WshShell.LogEvent(INFORMATION, Text)

The LogEvent method of the WScript.Shell object requires two parameters. The first parameter defines the event type and has a value between 0 and 16. The second parameter contains the text to be appended to the log file. If the method succeeds, the value True is returned; otherwise, False is returned.

Let's look at a small VBScript program that records the date, time, computer name, domain name, and user name to a log file each time a user logs on to the machine. It first collects the information that will go into the log file. The information requested is contained in the properties of the WshNetwork object, so you can create a text variable that contains the necessary data:

Text = Date & "  " & Time & ": Computer: " & _
       WshNetwork.ComputerName & vbTab
Text = Text & "Domain: " & _
       WshNetwork.UserDomain & vbTab

User = WshNetwork.UserName      ' Initialize value.
Do While User = ""              ' Loop until user name is returned.
    WScript.Sleep 200           ' Suspend to lower CPU load.
    User = WshNetwork.UserName  ' Read property.
Loop

Text = Text & "User: " & User & vbTab

Because this sample makes sense only in a Windows 95 or Windows 98 system (Windows NT and Windows 2000 record user logins by default), I used the technique mentioned in Chapter 11 to wait until the UserName property returns a valid name. You can write this string to a log file by using the following code:

WshShell.LogEvent INFORMATION, Text

This call doesn't return any value because we're using a procedure call. So how do you force Windows 95 or Windows 98 to execute this script each time a user logs on? You add a command that consists of the script's path and filename to the value UserLog of the following Registry key:

HKLM\Software\Microsoft\Windows\CurrentVersion\Run

The content of this key is processed each time a user logs on. Listing 14-11 shows a script that adds the command to the Run key during each execution. The user must launch the script only once to force automatic execution. To remove the entry from the Registry, you can use the System Policy Editor, the Registry Editor, or (in Windows 98) the System Configuration Utility.

Listing 14-11 UserLog.vbs

'*******************************************************
' File:    UserLog.vbs (WSH 2 sample in VBScript) 
' Author:  (c) G. Born
'
' Writing the user name using the WSH 2 LogEvent method. 
' The script is added to the Run key, so it's handy
' to establish a user log function in Windows 95 
' and Windows 98.
'*******************************************************
Option Explicit 

Const SUCCESS = 0 
Const ERROR = 1 
Const WARNING = 2 
Const INFORMATION = 4
Const AUDIT_SUCCESS = 8
Const AUDIT_FAILURE = 16 

Dim Text, User
Dim WshNetwork, WshShell         ' Object variable

' Create reference to the WshShell object.
' (needed for Registry access and EventLog method).
Set WshShell = WScript.CreateObject("WScript.Shell")

' Add script to Run key so that it will 
' be executed at each logon.
AddRun                 

' Create a new WshNetwork object to access network properties.
Set WshNetwork = WScript.CreateObject("WScript.Network")

Text = Date & "  " & Time & ": Computer: " & _
       WshNetwork.ComputerName & vbTab
Text = Text & "Domain: " & _
       WshNetwork.UserDomain & vbTab

User = WshNetwork.UserName      ' Initialize value.
Do While User = ""              ' Loop until user name is returned.
    WScript.Sleep 200           ' Suspend to lower CPU load.
    User = WshNetwork.UserName  ' Read property.
Loop

Text = Text & "User: " & User & vbTab

' Write log file entry. Returns True (success) or False (failed).
If WshShell.LogEvent(INFORMATION, Text) Then
    ' Remove for silent use.
    WScript.Echo "Log Event written" & vbCrLf & Text  ' Show result.
Else
    WScript.Echo "LogEvent method failed in UserLog.vbs"
End If

Sub AddRun()
    ' Add the batch file for launching the script to the 
    ' Registry's Run key.
    Dim Command

    Const Root = "HKEY_LOCAL_MACHINE"
    Const key = "\Software\Microsoft\Windows\CurrentVersion\Run\"
    Const valname = "UserLog"
 
    Command = WScript.ScriptFullName

    WshShell.RegWrite Root & Key & valname, Command, "REG_SZ"
End Sub

'*** End

Printing from a WSH Script

WSH has no printing support because printing isn't a simple task in Windows. The only way to print from a script is to force an application to do the job for you. To print a plain text file, you can use Notepad.exe. Notepad supports the /p switch, which allows you to print a text file and then close the editor after printing, as shown here:

Notepad /p C:\Test\Text.txt

The command forces Windows to load Notepad and submit the parameters of the command line. If Notepad finds a filename in the command line, it tries to load that file. If it detects the /p switch, it sends the file to the default printer. After printing, Notepad quits.

The following code snippet, taken from PrintTxt.vbs, uses this technique to print the source code of the script:

Set oWshShell = WScript.CreateObject("WScript.Shell")

' Shell out a command to print using Notepad.
oWshShell.Run "Notepad /p " & WScript.ScriptFullName, 7, True

The first line creates an object instance of the WshShell object, which is required for the Run method. The second statement shells out a command to launch Notepad and print the specified text file. The statement uses the ScriptFullName property to insert the script's filename. The second parameter submitted to the Run method is set to 7 to force Notepad to run in a minimized window. The last parameter is set to True to tell the script to wait until the process launched from the Run method terminates.

This solution works well for plain text files, but you can't use it to change the printer. To invoke the Print dialog box (in Windows 2000) so that the user can select a printer and set printer options, you can use this code snippet:

Set oWshShell = WScript.CreateObject("WScript.Shell")

' Shell out a command to print source with MSHTML.
oWshShell.Run "RunDll32.exe MSHTML.dll,PrintHTML " & _
              WScript.ScriptFullName, 7, True

' Shell out a command to print HTML documents with MSHTML.
oWshShell.Run "RunDll32.exe MSHTML.dll,PrintHTML " & _
              GetPath() & "TestForm.htm", 7, True

The first statement creates the object instance required for the Run method. The next two statements execute the Run method. The first statement prints the script's source code, and the second prints an HTML document. Both statements use RunDll32.exe to call a helper DLL. The library MSHTML.dll exports several functions that you can use from external programs. One function is PrintHTML. (Note the case-sensitive name.) This API function reads a parameter from the command line that contains the document filename. After the function is invoked, the Print dialog box is displayed. The user can select a printer, set the printer options, and print the document.

NOTE
The PrintDocs.vbs sample allows you to print text files, HTML documents, and documents in .rtf format. MSHTML can't print other formats, such as .gif graphics and .pdf files. To print Microsoft Office documents, you can use the Office applications. These applications don't support a /p switch, but they support a COM interface, so you can access the objects and methods instead. All Office applications contain methods for printing a document. I've also written an ActiveX control that provides methods for printing documents using the Windows shell. (For more details, see Advanced Development with Microsoft Windows Script Host 2.0.)