Fixing Windows XP Annoyances
A script is simply a list of commands that are placed one after another and stored in a text file. Script commands are like building blocks: the more commands and programming techniques you learn, the broader your palette will be for making useful scripts. Some of the simpler building blocks will be used in this section of the chapter to illustrate the way scripts are built. Advanced users may prefer to skip to subsequent sections, which cover more advanced topics.
To run a script, just double-click on the script file icon; you'll probably never need to run the Scripting Host program (wscript.exe) directly.
|
When the Scripting Host runs the script, the commands are executed in order, one-by-one. You can leave Notepad open to make changes and additions while you test the script (big screens are especially handy for this sort of thing).
You can quickly open an existing script file for editing by right-clicking on it and selecting Edit. This will, by default, open Notepad, although you might want to associate the Edit action for .vbs files with a more powerful text editor (see Section 4.3.3).
The process of putting a script together essentially involves typing commands and then running the scripts to test them. In the following topics, we'll cover the background concepts necessary to complete many tasks with scripts:
-
Using variables to store and manipulate information
-
Asking for and displaying information with the InputBox and MsgBox commands
-
Creating interactive scripts with conditional statements
-
Using loops to repeat a series of commands
-
Making building blocks with subroutines and functions
-
Extending scripts with object references
9.1.1 Using Variables to Store and Manipulate Information
The use of variables is essential when some interaction is required by a script. A variable can be assigned a value, which is subsequently used or simply recalled later in the script. For example, the following two commands:
MyName = "joe user" MyShoeSize = 12
set two different variables to two different values. The first variable, MyName, is assigned to a text string, while the second, MyShoeSize, is set to a numeric value. You can also assign variables in terms of other variables:
MyIQ = MyShoeSize + 7
which, when placed after the two preceding lines, would result in the variable MyIQ having a value of 19 (12 plus 7). When a variable name appears on the left side of an equals sign, its value is being manipulated. When it appears on the right side of an equals sign or within some other command, its value is simply being read. You can carry out more complex mathematical operations using various combinations of parentheses and the standard operators (+, -, *, /, and ^ for addition, subtraction, multiplication, division, and exponentiation, respectively).
9.1.2 Giving Your Scripts an Interface with the InputBox and MsgBox Commands
Some scripts are ideally suited to run in the background and perform a sequence of tasks, and then simply exit when those tasks are complete. Others require some sort of user interaction, either in the form of asking the user for input or informing the user when something has gone wrong. For example, this command:
MyName = InputBox("Please enter your name.")
will display a prompt on the screen when the script is run, asking for some text to be typed. When you enter some text and click OK, the script places the text you've typed into the variable MyName and continues on to the next command.
Now, collecting and rearranging information does no good without the ability to spit out a result. The versatile MsgBox function allows you to display a simple message, as follows:
MsgBox "Hello, Hello Again."
Combining the principles we've covered so far, consider the following code:
MyAge = InputBox("Please type your age.") NewAge = MyAge + 5 MsgBox "In 5 years, you will be " & NewAge & "."
The first line does two things: it first asks the user to type something, and then assigns the typed text to the variable MyAge. The second line creates a new variable, NewAge, assigns the user's input to it, and adds five. Note the lack of any error checking in this example: if the user enters something other than a number, this code will cause a WSH error, and the script will end early. The third line then uses the & operator to concatenate (glue together) a text string to the NewAge variable and displays the result in a message box. Notice that plain text is always enclosed in quotation marks, but variables are not. If we were to enclose the NewAge variable in quotation marks, the script would simply print out the text NewAge instead of whatever value is stored in the variable.
The MsgBox statement can also be used like this:
Response = MsgBox("Here's My Message", 17, "Message Title")
which allows it to be used for not only displaying a message, but recording the response as well. The 17 is the sum of a few different values, which specify the options used to customize the message box. Figure 9-1 shows two sample message boxes, each with different buttons and icons.
Figure 9-1. Various options can be combined to produce a variety of message boxes.
To choose the buttons that are displayed by the MsgBox function, specify:
- 0 for OK
- 1 for OK & Cancel
- 2 for Abort, Retry, & Ignore
- 3 for Yes, No, & Cancel
- 4 for Yes & No
- 5 for Retry & Cancel
To choose the icon that is displayed, specify:
- 16 for a red "X" (error)
- 32 for a question mark (query)
- 48 for an exclamation mark (warning)
- 64 for a blue "I" (information)
Additionally, you can add:
- 256 to give the second button the focus (dotted lines)
- 512 to give the third button the focus
- 4096 to make the message box "system modal" (i.e., all applications are suspended until the user responds to the message box)
So, to have a message box with the Yes and No buttons, to have the question mark icon, and to have No be the default, you would specify a value of 4 + 32 + 256 = 292. The two message boxes in Figure 9-1 have values of 17 (that's OK, Cancel, and the "X" icon) and 292, respectively. Note that it's good practice not to add the values together (like I did in the first example with 17), but rather to leave them separated, like this:
Response = MsgBox("Here's My Message", 16 + 1, "Message Title")
This way, it's easier to understand and modify later on.
When the user responds to the message box, the Response variable will be set to:
- 1 if the user clicked OK
- 2 for Cancel
- 3 for Abort
- 4 for Retry
- 5 for Ignore
- 6 for Yes
- 7 for No
The next step is to write code that can perform different functions based on this recorded response. See the subsequent "Creating Interactive Scripts with Conditional Statements" topic for details on using the results from a MsgBox statement to determine what happens next in a script.
9.1.3 Creating Interactive Scripts with Conditional Statements
Conditional statements allow you to redirect the flow depending on a condition you determine, such as the value of a variable. Take, for example, the following script:
Response = MsgBox("Do you want to continue?", 32 + 4, "Next Step") If Response = 7 Then WScript.Quit MsgBox "You asked for it..."
The first statement uses the MsgBox function, described in the previous topic, to ask a question. The value of 32 + 4 specifies Yes and No buttons, as well as the question mark icon. If the user chooses Yes, the value of the Response variable is set to 6; if No is chosen, Response is set to 7.
The next statement uses the vital If...Then structure to test the value of the Response variable. If it's equal to 7 (meaning the user clicked No), then the script exits immediately (using the WScript.Quit statement). Otherwise, script execution continues to the next command.
Here's another example using a slightly more complex version of the If statement:
MyShoeSize = InputBox("Please type your shoe size.") MyIQ = InputBox("Please type your IQ.") If MyShoeSize > MyIQ Then MsgBox "You need to read more." Else MsgBox "You need larger shoes." End If
One of the nice things about VBScript is that most of the commands are in plain English; you should be able to follow the flow of the program by just reading through the commands. Before you run the previous script, try to predict what will happen for different values entered at each of the two InputBox statements.
This script uses the If...Then structure to redirect output depending on the two values entered at runtime (when the script is actually being executed). It should be evident that the first message is displayed if the value of MyShoeSize is larger than the value of MyIQ. In all other cases (including when both values are equal), the second message is displayed. Note also the use of End If, which is required if the If...Then structure spans more than one line, as it does in this example.
The If...Then structure can have as many elements as you need. For example:
Crashes = InputBox("How many times a day does Windows crash?") If Crashes <= 3 Then MsgBox "You lucky sod..." ElseIf Crashes = 4 or Crashes = 5 Then MsgBox "The national average: good for you!" Else MsgBox "Take two aspirin and call me in the morning." End If
accommodates three different ranges of answers to the question posed by the first line of code (thanks to the ElseIf line). Note also the use of or on the fourth line; you could also use and here, or a combination of the two. Use parentheses to group conditions in more complex statements.
9.1.4 Using Loops, Using Loops, Using Loops
Another useful structure is the For...Next loop, allowing you to repeat a series of commands a specified number of times:
SomeNumber = InputBox("How many lumps do you want?") TotalLumps = "" For i = 1 To SomeNumber TotalLumps = TotalLumps & "lump " Next Rem The next line displays the result -- MsgBox TotalLumps
The For...Next loop repeats everything between the two statements by incrementing the value of the variable i until it equals the value of the variable SomeNumber. Each time we go through the loop, another "lump" is added to our variable, TotalLumps. When the loop is finished, the contents of the TotalLumps variable are displayed.
Notice the use of the concatenation operator (&) in the middle of the loop, which adds a new lump to the variable. Those new to programming might be put off by the fact that we have the TotalLumps variable on both sides of the equals sign.[1] This works because the scripting host evaluates everything on the right side of the equals sign (adds it all up) and then assigns it to the variable on the left side.
[1] In traditional algebra, we couldn't have a statement like this; it would be like saying x=x+1, which has no solution. However, this is not an equation; it's a instruction that you want carried out. Besides, you're supposed to have forgotten algebra years ago.
Note also the TotalLumps="" statement before the For...Next loop; this empties the variable before we start adding stuff to it. Otherwise, whatever might be assigned to that variable before the loop would still be kept around something we didn't anticipate nor want. It's good programming practice to prepare for as many different situations as can be imagined.
Also good practice is the use of spaces, indentations, and remarks to make the code easier to read without affecting the execution of the script. The Rem command (shown earlier) is used to include remarks (comments that are ignored when the script is run), allowing you to label any part of the script with pertinent information. In place of the Rem command, you can also use a single apostrophe ('), which has the advantage of being used on the same line as another command.
As you write these scripts, think about the formatting as you would in writing a word processor document; scripts that are easier to read are easier to debug and easier to come back to six months later.
9.1.5 Making Building Blocks with Subroutines and Functions
A subroutine allows you to encapsulate a bit of code inside a single command, making it easy to repeat that command as many different times as you want, just like it was a built-in command in VBScript. Simply include the entire subroutine anywhere in a script, and then type the name of the subroutine elsewhere in the script to execute the subroutine.
A function is essentially the same as a subroutine, except that it has a result, called a return value. Both subroutines and functions accept input variables, listed in parentheses after the respective Sub and Function statements.
|
Consider Example 9-1, which compares the contents of two text files. At the heart of this example are the two structures at the end of the script, although their specific position in the script is not important. WSH separates all subroutines and functions before executing the script; they won't be executed unless they're called, and the variables used therein are unrelated to variables used elsewhere in the main script. Whenever it encounters the name of a subroutine or function in the script body, it executes it as though it were a separate script. Try to follow the execution of the script, command by command.
Example 9-1. Using functions and subroutines
Filename1 = InputBox("Enter the first filename") Filename2 = InputBox("Enter the second filename") If Not FileExists(Filename1) Then MsgBox Filename1 & " does not exist." ElseIf Not FileExists(Filename2) Then MsgBox Filename2 & " does not exist." Else Call RunProgram("command /c fc " & filename1 & _ " " & filename2 & " > c:\temp.txt", True) Call RunProgram("notepad c:\temp.txt", False) End If Function FileExists(Filename) Set FileObject = CreateObject("Scripting.FileSystemObject") FileExists = FileObject.FileExists(Filename) End Function Sub RunProgram(Filename) Set WshShell = WScript.CreateObject("WScript.Shell") ReturnVal = WshShell.Run(Filename, True) End Sub
One of the most important aspects of both subroutines and functions is that they can accept one or more input variables, called parameters or arguments. The parameters that a subroutine can accept are listed in parentheses after the subroutine definition and are separated with commas (if there are more than one). Then, using the Call statement, the values you wish to pass to the subroutine (which are placed in the parameter variables when the script is run) are listed in parentheses.
This way, the same subroutine or function can be called repeatedly, each time with one or more different variables. Functions (such as FileExists in this example) also can return a single variable (usually dependent on the outcome of some operation).
The first structure defines the FileExists function (discussed later in this chapter), which is passed a filename and returns a value of True (-1) if the file exists and False (0) if it does not. The FileExists function is called twice, once for each filename entered when the script is run (Filename1 and Filename2 ). The If...Then structures (see Section 9.1.3 earlier in this chapter) first call the function, then redirect the flow based on the result of the function.
The second structure defines the RunProgam subroutine, also called from the script two times. RunProgram simply runs the program filename passed to it; since it's a subroutine and not a function, there is no return value. In theory, you could use functions exclusively, and simply ignore the return values of those functions that don't use them; the benefit of subroutines, though, is that you don't have to think about handling a return value at all.
In FileExists and RunProgram, Filename is a variable (shown in parentheses) in which passed data is placed so that it can be used inside the subroutine or function. It's considered a local variable; that is, it has no value outside of the subroutine or function.
The most important consequence of this design the separation of the code into subroutines and functions is that it makes it easy to reuse portions of code. Experienced programmers will intentionally separate code into useful subroutines that can be copied and pasted to other scripts. Just think of programming as building something out of Lego
It's worth mentioning that, in the case of subroutines, the Call statement is not strictly necessary. For example, the line:
Call RunProgram("notepad c:\temp.txt", False)
is equivalent to:
RunProgram "notepad c:\temp.txt", False
Note that in removing the Call keyword, I've also had to remove the parentheses around the arguments. Personally, I like using the call command, as it makes references to my custom subroutines more distinct and easier to find, but others might prefer the simpler form.
The solutions in the subsequent topics are presented as either subroutines or functions. I've used subroutines for code that performs an action, such as copying a file or writing information to the Registry. When a result is expected, such as reading information from the Registry or finding the date of a file, a function is used instead.
You should be able to place the subroutines and functions directly into your scripts and call them with a single command. It's up to you to put the pieces together to accomplish whatever tasks you have in mind. Feel free, also, to alter these routines to suit your needs.
9.1.6 Extending Scripts with Object References
There are some operations that can be performed with the Windows Script Host regardless of the language being used. These operations, such as accessing the filesystem, are made possible by extending the language with objects. For the time being, we can consider an object to be simply a context that is referred to when carrying out certain commands.
Admittedly, this can make carrying out some tasks rather difficult and convoluted, but it is necessary given the modular architecture of WSH. For example, many scripts will require a line similar to the following (using VBScript syntax in this case):
Set WshShell = WScript.CreateObject("WScript.Shell")
which creates and initializes the WshShell object. WshShell is not a visible object like a file or other component of Windows, but rather a required reference used to accomplish many tasks with WSH, such as running programs, creating Windows shortcuts, and retrieving system information.
If you're unfamiliar with object references, your best bet is to simply type them as shown and worry about how they actually work when you're more comfortable with the language. The subsequent topics include many solutions that take advantage of objects, such as WScript.Shell, which has many uses, and Scripting.FileSystemObject, used for accessing files, folders, and drives.