VBScript
Fileformat
VBScript files are regular
text files with a .vbs extension. This means they can be created and
edited with anything from Notepad to Visual Studio, or even edlin if
you remember it. But, if you can get your hands on an editor that knows
VBScript you will usually get color coding and code completion which
will greatly help in writing and maintaining your scripts.
Interpreter
Since the script files are text files they cannot do anything on their
own so they must be fed to an interpreter which reads the script code
and tries to execute instructions in it.
There are two interpreters to choose from. Which one you want to use depends on how you want output to be displayed. Any output that isn't explicitly sent to a command window or a popup dialogue is sent to the preferred output of the interpreter you choose to feed the script file.
cscript.exe
Sends output by default to a command window. (screenshot)
wscript.exe
Sends output by default to a popup dialogue. (screenshot)
Now, lets write some scripts.
Hello World!
Here is our first script:
Type the above in a Notepad window and save as HelloWorld.vbs. Then open a cmd window, navigate to where you saved the script and execute it with: cscript HelloWorld.vbs. (screenshot)
This little script will print Hello World! and then exit. Actually, what it does is tell the interpreter (Windows Script Host) to send the string "Hello World!" to the Echo method of the WScript object which will take care of displaying the string. (WScript is the object that let us communicate with Windows Script Host itself)
Calculations
Now that we know how to output a text string lets build on that. (Try
these examples in the same way you did with Hello World! so you know
what they output)
Adding two numbers:
Displaying the addition as a text string:
Combining (concatenating) two strings (into one string):
Concatenating strings and values:
As you can see when you run the last example everything between double quotes is considered a string. If you concatenate a string and a value the result will be a string where the value has been converted to a string (after first having been calculated of course). 5+5 will be calculated to 10 which will become "10" when it is combined with the other two strings.
This is all nice but not very useful. The idea behind scripts (and programming in general) is to automate repetitious actions, reuse information and expand on the views on data that you already have. The additions above (5+5) is immediately calculated and left is the sum (10) which itself isn't available any longer than the WScript.Echo statement. What if we wanted to reuse the sum or the added numbers? In come Variables.
Variables
A variable is an entity in which you can store data. When you want to
use that data you refer to the variables name. Like this:
result = 5 + 5
WScript.Echo "The result is: " & result
First we create (define) the variable and name it "result". Then we calculate 5+5 and store the sum in the newly created variable. Finally we use the variable. This way we can refer to the result whenever we want without having to do the calculation all over again when we want to display it.
Consider this scenario:
We want to find out what percentage of disabled accounts we have in an
Active Directory domain. First we would get the total number of
accounts and store it in a variable (totAccounts) and then the number
of disabled accounts (disAccounts). When we want to display the data we
could then simply do:
The variables in VBScript are so called Variants. In some languages you have to decide what type of data a variable is supposed to hold when you define it and after that you can only put that kind of data in it. In VBScript Variants will hold whatever you put in them, regardless of what they held previously.
Here are a few other examples of using variables:
result = "The result is: " & 5 + 5
WScript.Echo result
result = 5 + 5
text = "The result is: "
output = text & result
WScript.Echo output
Option Explicit
Here is a recommendation: Always use Option Explicit!
If you place an Option Explicit statement at the beginning of your scripts you set a rule that says you have to define your variables before you use them (without it you actually don't have to define the variables with Dim before you use them).
Consider this script:
resul = 5+5
WScript.Echo "Result: " & result
On the second line I missed a 't' when I tried to assign the variable 'result' the sum of 5+5. This way the script has two variables. One named 'result' which is empty and the other named 'resul' which contain the value 10.
Normally this isn't a big issue in a small script but when you get more creative you will eventually end up writing larger scripts and trying to find one misspelled variable in a script of 100 or more lines can get really troublesome. Not to mention the ammount of time you will waste which could be spent being creative instead.
The solution, as I wrote above, is to add the statement Option Explicit to the start of all your scripts. If you add it to the above script and run it again you will see the interpreter complain: "Variable is undefined: 'resul'".
Personally ALL of my production scripts ALWAYS use Option Explicit no matter the size (a small script for finding a user account, a larger one that gathers statistics about user home folders or an ASP web site).
Variable naming schemes
Why should it matter how you name variables? Technically, it doesn't
matter at all (almost). But, if you always name them in the same way it
will be easier to maintain them and the one who has to take over your
job when you get promoted will have a much easier job.
There are as many naming schemes as there are developers/script writers but beside personal styles you will most likely only see a handful repeated in public scripts. Here I will use Hungarian Notation (or my take on it).
In Hungarian Notation a variable is named with a prefix hinting at its content. Using this scheme helps keep track of what a variable should contain. A few examples:
intAge - an Integer
strUserName - a String
arrUsers - an Array
objGroup - an Object
colFiles - a Collection
This way it is pretty obvious that objGroup doesn't contain the name of the Group but the Group itself. If I wanted the name of the Group I would use a variable named strGroup (or possibly the Name property of the objGroup object, objGroup.Name, if this was an ADSI script).
I wrote earlier that "Technically, it doesn't matter at all (almost)" what you name your variables. The one situation where it does matter is if you name a variable the same as one of the built in VBScript statements or the Windows Script Host (WSH) reserved names. Some examples:
Dim WScript
This obviously doesn't work since the interpreter will get confused about what you actually want it to do.
Ok, enough info. Lets dive into letting the script decide what to do based on what it has to work with.
Controlling program flow (Conditional statements)
A simple script will run from start to finish and then exit. The
scripts we've written so far have done just that which mean they cannot
do more than simple things. What would we have to write if we for
example needed to take different action based on the result of a
calculation? In come conditional statements.
If
Take a look at the following script:
Dim intResult
intResult = 5 + 5
If intResult > 10 Then
WScript.Echo "Greater than 10"
End If
First it calculates 5+5 and then it compares the result of that calculation with the value 10. If our calculation is greater than 10 the script outputs "Greater than 10" otherwise it exits. In this partitcular case obviously nothing will be output.
If we build on this and tries to handle the other two situations that could arise from the calculation (the result is lower than 10 or equal to 10):
Dim intResult
intResult = 5 + 5
If intResult > 10 Then
WScript.Echo "Greater than 10"
Else
WScript.Echo "Lower than or equal to 10"
End If
Dim intResult
intResult = 5 + 5
If intResult > 10 Then
WScript.Echo "Greater than 10"
ElseIf intResult < 10 Then
WScript.Echo "Lower than 10"
Else
WScript.Echo "Equal to 10"
End If
With an If statement we get the power to take different action based on a specified criteria. Granted, testing 5+5 doesn't pose much of a challenge but if we were counting the number of characters in a filename we might want to warn if the filename itself contained 50 characters or more (since most APIs in Windows are only able to handle a filename, including its path, of 255 characters).
For
What if we wanted to repeat an action several times, like counting down
from 10? Of course we could write a script with 10 WScript.Echo lines
outputing 10, 9, 8... etc but this soon become unmanageable. And if we
get a list of users that are members of a group that we want to
display, how do we, when we write the script, know how many users to
display? Lets take a look at the For statement:
Dim intLoop
For intLoop = 1 To 10
WScript.Echo "Counting: " & intLoop
Next
This script will loop 10 times, each time outputing "Counting: " and the current value of the intLoop variable. Each time the interpreter reaches the Next statement it will increment intLoop by 1 (which is the default behaviour). If we wanted to count DOWN from 10 to 0 instead we could rewrite line three like this:
Step decides how we want to increase or decrease the counter for each iteration.
While
Another way of doing this is to use a While loop:
Dim intLoop
intLoop = 10
While intLoop >= 0
WScript.Echo "Counting: " & intLoop
intLoop = intLoop - 1
Wend
Here we don't get any help but have to decrease the tested variable ourselves. The While statement first checks to see if intLoop is greater than or equal to 0. If it is everything between While and Wend is carried out. When the interpreter reaches Wend it will return back up to the While statement again and do another test. Imagine what would happen if we forgot to decrement intLoop at the bottom of the loop... the script would keep running forever (or until we kill it).
Before we continue I just want to mention the For Each statement which is very handy when handling lists of things (specifically Arrays and Collections). Using For Each it is easy to for example manipulate every member of a group, like this:
WScript.Echo objMember.Name
Next
This last code segment is a snippet from one of my ADSI scripts and is only meant to hint the power of the For Each statement. objGroup.Members return a collection (a soft of list) of everything that is a member of the group. Each time around this loop the objMember variable will contain the next group member and the script will thus output the names of all of the members.
Ok, now that we are this far ahead I won't be providing fully functional code snippets since they would be too long and all you would be doing would be copying and pasting code into your editor.
Subprocedures and Functions
The last thing we're going to look at is a way to break out some of the
logic in a script into a reusable fragment. Two examples will help in
illustrating the usefulness of Subprocedures and Functions.
First, lets look at a situation where you want your script to output some things to the screen and at the same time write the same or something entirely different to a log file (for example more verboose info to help find errors). Look at the following fragment:
fLogFile.WriteLine("Info: Somethings happening...")
' Do something else...
fLogFile.WriteLine("Info: File copying starting...")
' Copy files...
fLogFile.WriteLine("Info: Files copied!")
Subprocedure
Now, what if you had over 50 of these lines in your script that wrote
log messages and you suddenly decided that you didn't want them to
start with "Info: " but instead with "Log: ". You'd have to change more
than 50 lines, either manually or if you have a good editor by doing a
search and replace. Either way it would take time. Lets break out the
logging functionality into a subprocedure instead:
WriteLog "Somethings happening..."
' Do something else...
WriteLog "File copying starting..."
' Copy files...
WriteLog "Files copied!"
Sub WriteLog(strMessage)
fLogFile.WriteLine("Log: " & strMessage)
End Sub
The Sub ... End Sub section defines how the subprocedure works and how it is supposed to be accessed. This particular one is named WriteLog and it takes one argument (the message we want to log) named strMessage. When we use it we write its name and after that the string we want it to log (this string could be a concatenation of several strings/values/variables).
Now we only have one place where we need to change the prefix from "Info: " to "Log: " or to something else when we feel like it.
Another step we could take if we wanted is to add different prefixes depending on what is happening in the script. If we for example noticed that the files we were supposed to copy didn't exist, or that we couldn't copy them we might want to change the prefix from "Log: " to "Error: " and in those situations where everthings going according to plan we might want to return to the previous prefix of "Info: ". Lets add a status indicator:
WriteLog 1, "Somethings happening..."
' Do something else...
WriteLog 1, "File copying starting..."
' Oups, this didn't work...
WriteLog 3, "Files copied!"
Sub WriteLog(intStatus, strMessage)
Dim strStatus
If intStatus = 1 Then
strStatus = "Info: "
Else If intStatus = 3 Then
strStatus = "Error: "
End If
fLogFile.WriteLine(strStatus & strMessage)
End Sub
Here the subprocedure takes two arguments (the status value and the message) and depending on what status value we send to it it will know how to prefix the log message.
As you see it is possible to write scripts that are easy to maintain if you give some extra thought into what migth change in the future. I intentionally left out the status value of 2 since we might need a "Warning: " prefix someday.
Function
Our second example makes use of a function and shows how you can hide a
lot of functionality inside it to help make the rest of the script a
little easier to read. Imagine you're working with user accounts in
your Active Directory domain and you have to check if this account is a
member of a certain group in order to decide what to do:
For Each objGroup In colGroups
If objGroup.Name = "Sales" Then
' Do something (like connect a network disk)...
End If
Next
(An easier way would actually be to get the group we wanted to test and ask it if the user was a member with objGroup.IsMember(objUser.AdsPath), but that wouldn't help in explaining the merits of functions.)
The above is at least 6 lines of code and if we wanted to do the same test on several places in our script it would grow pretty quickly. Lets move this code to a function in the same way we did with the logging subprocedure above:
colGroups = objUser.Groups
For Each objGroup In colGroups
If objGroup.Name = strGroup Then
IsMember = True
End If
Next
IsMember = False
End Function
Here the function uses the For Each loop to iterate through all groups in the collection colGroups. If it finds one group with the same name as the one we're looking for it will return True otherwise it will end with returning False.
When you want a function to return something you assign the return value to the name of the function in the same way you would assign it to a variable, only this variable is automatically created inside this is a function.
With the above function each place in the script where we want to check group membership only need this:
' Do something (like connect a network disk)...
End If
...and we have also moved most of the code to one place in the script so if we realize that we've written something wrong we only have to fix the problem in the IsMember function.
(The IsMember function returns the boolean value True if the user is a member of the tested group, otherwise it returns False and since the If statement tests if something is true or not this works - remember that earlier we tested if 5+5 was equal to 10 and since it is '5+5=10' returns True)
The difference between subprocedures and functions is that:
- Functions require parantheses
- Functions return a return value
- Subprocedures must never have parantheses
- Subprocedures never return anything
Most modern languages today only have Functions since they can do exactly the same thing as Subprocedures can.
Conclusion
Ok, this was a very brief introduction to VBScript. If you want more
info I suggest you visit the references section where you will find
links to more information.
