Skip to content

4. Error Handling

In programming, there is one absolute rule: a program must never "crash" unexpectedly. All errors that may occur during program execution must be handled, and meaningful error messages must be generated.

If we return to the tax example discussed earlier, what happens if the user enters any value for the number of children? Let’s look at this example:

1
2
3
C:\>cscript impots1.vbs o xyzt 200000

C:\impots1.vbs(33, 3) Microsoft VBScript runtime error: Incompatible type: 'cint'

This is what is known as a wild crash. The "crash" occurred on the statement children=cint(wscript.arguments(1)) because arguments(1) contained the string "xyzt".

Before using a variant whose exact nature is unknown, you must verify its exact subtype. There are several ways to do this:

  • test the actual type of the data contained in a variant using the vartype or typename functions
  • use a regular expression to verify that the variant’s content matches a specific pattern
  • allow the error to occur, then intercept it and handle it

We will examine these different methods.

4.1. Determining the exact type of a data item

Remember that the vartype or varname functions allow you to determine the exact type of a piece of data. This isn’t always very helpful. For example, when we read data typed on the keyboard, the vartype and typename functions will tell us that it is a string of characters, since that is how all data typed on the keyboard is treated. This does not tell us whether this string can, for example, be considered a valid number. We therefore use other functions to access this type of information:

isNumeric(expression)
returns true if expression can be used as a number
isDate(expression)
returns true if expression can be used as a date
isEmpty(var)
returns true if the variable var has not been initialized
isNull(var)
returns true if the variable var contains invalid data
isArray(var)
returns true if var is an array
isObject(var)
returns true if var is an object

The following example prompts the user to enter data via the keyboard until it is recognized as a number:

Program
' Read input until it is recognized as a number

Option Explicit

Dim end, number

' loop until the entered data is correct
' the loop is controlled by a Boolean variable 'finished', set to false at the start (= not finished)

finished = false
Do While Not finished
    ' ask for the number
    wscript.stdout.write "Enter a number: "
    ' read the number
    number = wscript.stdin.readLine
    ' the type is necessarily string when reading
    wscript.echo "Type of the read data: " & typename(number) & "," & vartype(number)
    ' we check the actual type of the read data
    If isNumeric(number) Then
        done = true
    Else
        wscript.echo "Error, you did not enter a number. Please try again..."
    End If
Loop

' confirmation
wscript.echo "Thank you for the number " & number

' and end
wscript.quit 0
Results
1
2
3
4
5
6
Enter a number: a
Data type read: String,8
Error, you did not enter a number. Please try again...
Enter a number: -12
Type of data read: String,8
Thanks for the number -12

The isNumeric function does not tell us whether an expression is an integer or not. To get this information, we need to perform additional checks. The following example checks for an integer greater than 0:

Program
' Read data until it is recognized as an integer greater than 0

Option Explicit

Dim fini, number

' loop until the entered data is correct
' the loop is controlled by a Boolean variable 'finished', set to false at the start (= it's not finished)

finished = false
Do While Not finished
    ' ask for the number
    wscript.stdout.write "Enter an integer >0: "
    ' read it
    number = wscript.stdin.readLine
    ' check if the read data is a real number
    If isNumeric(number) Then
        ' Is it a positive integer (number equal to its integer part)?
        If (number - Int(number)) = 0 And number > 0 Then
            result=true
        End If
    End If
    ' Possible error message
    If Not finished Then wscript.echo "Error: You did not enter an integer greater than 0. Please try again..."
Loop

' confirmation
wscript.echo "Thank you for the integer >0: " & number

' and end
wscript.quit 0
Results
1
2
3
4
5
6
7
8
Enter an integer >0: a
Error, you did not enter an integer >0. Please try again...
Enter an integer >0: -1
Error: You did not enter an integer greater than 0. Please try again...
Enter an integer >0: 10.6
Error: You did not enter an integer greater than 0. Please try again...
Enter an integer >0: 12
Thank you for the integer >0: 12

Comments:

  • int(number) returns the integer part of a number. A number equal to its integer part is an integer.
  • It is interesting to note that we had to use the test If (number - int(number)) = 0 And number > 0 because the test If number = int(number) And number > 0 did not yield the expected results. It did not detect positive integers. We leave it to the reader to discover why.
  • The test If (number - int(number)) = 0 is not entirely reliable. Let’s look at the following example:
Enter an integer >0: 4.0000000000000000000000001

Thank you for the integer >0: 4.0000000000000000000000001

Real numbers are not represented exactly but approximately. And here, the operation number-int(number) returned 0 to the computer's precision.

4.2. Regular expressions

Regular expressions allow us to test the format of a string. This way, we can verify that a string representing a date is in the dd/mm/yy format. To do this, we use a pattern and compare the string to that pattern. Thus, in this example, d, m, and y must be digits. The pattern for a valid date format is then "\d\d/\d\d/\d\d", where the symbol \d denotes a digit. The symbols that can be used in a pattern are as follows (Microsoft documentation):

Character
Description
\
Designates the following character as a special character or literal. For example, "n" corresponds to the character "n". "\n" corresponds to a newline character. The sequence "\\" corresponds to "\", while "\(" corresponds to "(".
^
Matches the start of the input.
$
Matches the end of the input.
*
Matches the preceding character zero or more times. Thus, "zo*" matches "z" or "zoo".
+
Matches the preceding character one or more times. Thus, "zo+" matches "zoo", but not "z".
?
Matches the preceding character zero or one time. For example, "a?ve?" matches "ve" in "lever".
.
Matches any single character, except the newline character.
(pattern)
Searches for the pattern and stores the match. The matching substring can be retrieved from the resulting Matches collection using Item [0]...[n]. To find matches with characters inside parentheses ( ), use "\(" or "\)".
x|y
Matches either x or y. For example, "z|foot" matches "z" or "foot". "(z|f)oo" matches "zoo" or "foo".
{n}
n is a non-negative integer. Matches exactly n occurrences of the character. For example, "o{2}" does not match "o" in "Bob," but matches the first two "o"s in "fooooot".
{n,}
n is a non-negative integer. Matches at least n occurrences of the character. For example, "o{2,}" does not match "o" in "Bob," but matches all "o"s in "fooooot." "o{1,}" is equivalent to "o+" and "o{0,}" is equivalent to "o*."
{n,m}
m and n are non-negative integers. Matches at least n and at most m occurrences of the character. For example, "o{1,3}" matches the first three "o"s in "foooooot" and "o{0,1}" is equivalent to "o?".
[xyz]
Character set. Matches any of the specified characters. For example, "[abc]" matches "a" in "plat".
[^xyz]
Negative character set. Matches any character not listed. For example, "[^abc]" matches "p" in "plat".
[a-z]
Character range. Matches any character in the specified range. For example, "[a-z]" matches any lowercase alphabetical character between "a" and "z".
[^m-z]
Negative character range. Matches any character not in the specified range. For example, "[^m-z]" matches any character not between "m" and "z".
\b
Matches a word boundary, that is, the position between a word and a space. For example, "er\b" matches "er" in "lever," but not "er" in "verb."
\B
Matches a boundary that does not represent a word. "en*t\B" matches "ent" in "bien entendu".
\d
Matches a character representing a digit. Equivalent to [0-9].
\D
Matches a character that is not a digit. Equivalent to [^0-9].
\f
Matches a line break character.
\n
Matches a newline character.
\r
Equivalent to a carriage return character.
\s
Matches any whitespace, including space, tab, page break, etc. Equivalent to "[ \f\n\r\t\v]".
\S
Matches any non-whitespace character. Equivalent to "[^ \f\n\r\t\v]".
\t
Matches a tab character.
\v
Matches a vertical tab character.
\w
Matches any character representing a word and including an underscore. Equivalent to "[A-Za-z0-9_]".
\W
Matches any character that does not represent a word. Equivalent to "[^A-Za-z0-9_]".
\num
Matches num, where num is a positive integer. Refers to stored matches. For example, "(.)\1" matches two consecutive identical characters.
\n
Matches n, where n is an octal escape value. Octal escape values must consist of 1, 2, or 3 digits. For example, "\11" and "\011" both match a tab character. "\0011" is equivalent to "\001" & "1". Octal escape values must not exceed 256. If they do, only the first two digits are taken into account in the expression. Allows ASCII codes to be used in regular expressions.
\xn
Corresponds to n, where n is a hexadecimal escape value. Hexadecimal escape values must consist of exactly two digits. For example, "\x41" corresponds to "A". "\x041" is equivalent to "\x04" & "1". Allows the use of ASCII codes in regular expressions.

An element in a pattern may appear once or multiple times. Let’s look at a few examples involving the \d symbol, which represents a single digit:

pattern
meaning
\d
a digit
\d?
0 or 1 digit
\d*
0 or more digits
\d+
1 or more digits
\d{2}
2 digits
\d{3,}
at least 3 digits
\d{5,7}
between 5 and 7 digits

Now let’s imagine a model capable of describing the expected format for a string:

target string
pattern
a date in dd/mm/yy format
\d{2}/\d{2}/\d{2}
a time in hh:mm:ss format
\d{2}:\d{2}:\d{2}
an unsigned integer
\d+
a sequence of spaces, which may be empty
\s*
an unsigned integer that may be preceded or followed by spaces
\s*\d+\s*
an integer that may be signed and preceded or followed by spaces
\s*[+|-]?\s*\d+\s*
an unsigned real number that may be preceded or followed by spaces
\s*\d+(.\d*)?\s*
a real number that may be signed and preceded or followed by spaces
\s*[+|]?\s*\d+(.\d*)?\s*
a string containing the word "just"
\bjuste\b
  

You can specify where to search for the pattern in the string:

pattern
meaning
^pattern
the pattern starts the string
pattern$
the pattern ends the string
^pattern$
the pattern starts and ends the string
pattern
the pattern is searched for anywhere in the string, starting from the beginning.
search string
pattern
a string ending with an exclamation point
!$
a string ending with a period
\.$
a string beginning with the sequence //
^//
a string consisting of a single word, optionally preceded or followed by spaces
^\s*\w+\s*$
a string consisting of two words, optionally followed or preceded by spaces
^\s*\w+\s*\w+\s*$
a string containing the word secret
\bsecret\b

Sub-patterns of a pattern can be "extracted." Thus, not only can we verify that a string matches a particular pattern, but we can also extract from that string the elements corresponding to the sub-patterns of the pattern that have been enclosed in parentheses. For example, if we are parsing a string containing a date in the format dd/mm/yy and want to extract the dd, mm, and yy components of that date, we would use the pattern (\d\d)/(\d\d)/(\d\d).

Let’s look at this example to see how to work with VBScript.

  • First, we need to create a RegExp (Regular Expression) object
set pattern = new regexp
  • Then we set the pattern to be tested
pattern.pattern="(\d\d)/(\d\d)/(\d\d)"
  • You may want to ignore case (by default, it is case-sensitive). Here, it doesn’t matter.
pattern.IgnoreCase = true
  • You may want to search for the pattern multiple times in the string (by default, this is not done)
pattern.Global=true

A global search only makes sense if the pattern used does not refer to the beginning or end of the string.

  • We then search for all matches of the pattern in the string:
set matches = pattern.execute(string)

The execute method of a RegExp object returns a collection of match objects. This object has a value property that is the string element matching the pattern. If you have set pattern.global=true, there may be multiple matches. This is why the result of the execute method is a collection of matches.

  • The number of matches is given by matches.count. If this number is 0, it means the pattern was not found anywhere. The value of match #i is given by matches(i).value. If the pattern contains subpatterns in parentheses, then the element of matches(i) corresponding to parenthesis j in the pattern is matches(i).submatches(j).

All of this is shown in the following example:

Program
' regular expression

' we want to check that a string contains a date in the dd/mm/yy format

Option Explicit
Dim template

' define the pattern
Set pattern = New Regexp
pattern.pattern="\b(\d\d)/(\d\d)/(\d\d)\b"  ' a date anywhere in the string
pattern.global = true                      ' search for the pattern multiple times in the string

' the user provides the string in which the pattern will be searched
Dim string, matches, i

chain=""
' loop as long as string <> "end"
Do While true
    ' Ask the user to enter text
    wscript.stdout.writeLine "Enter text containing dates in dd/mm/yy format and 'end' to stop: "
    string = wscript.stdin.readLine
    ' End if string = "end"
    If string="end" Then Exit Do
    ' Compare the read string to the date template
    Set matches = template.execute(string)
    ' did we find a match
    If matches.count <> 0 Then
        ' we have at least one match
        For i = 0 To matches.count - 1
            ' display match i
            wscript.echo "I found the date " & matches(i).value
            ' retrieve the sub-elements of match i
            wscript.echo "The elements of match " & i & " are (" & matches(i).submatches(0) & "," _
            & matches(i).submatches(1) & "," & matches(i).submatches(2) & ")"
        Next
    Else
        ' no match
        wscript.echo "I did not find any dates in the dd/mm/yy format in your text"
    End If
Loop

' done
wscript.quit 0
Results
Type text containing dates in dd/mm/yy format and "end" to stop:
Today is 01/01/01 and tomorrow will be 02/01/02
I found the date 01/01/01
The elements of the date 0 are (01,01,01)
I found the date 02/01/02
The components of the date 1 are (02,01,02)

Type a text containing dates in dd/mm/yy format and press Enter to stop:
a date in the incorrect format: 01/01/2002
I did not find any dates in dd/mm/yy format in your text

Type a text containing dates in dd/mm/yy format and press Enter to stop:
a sequence of dates: 10/10/10, 11/11/11, 12/12/12
I found the date 10/10/10
The components of the date 0 are (10,10,10)
I found the date 11/11/11
The elements of date 1 are (11,11,11)
I found the date 12/12/12
The components of the date 2 are (12,12,12)

Type a text containing dates in dd/mm/yy format and press Enter to stop:
end

Using regular expressions, the program that checks whether a keyboard input is indeed a positive integer could be written as follows:

Program
' Read data until it is recognized as a number

Option Explicit

Dim fini, number

' define the pattern for a positive integer (which may be zero)
Dim pattern
Set pattern = New Regexp
pattern.pattern = "^\s*\d+\s*$"

' loop until the entered data is correct
' The loop is controlled by a Boolean variable `fini`, set to false at the start (= not finished)

finished = false
Do While Not finished
    ' we ask for the number
    wscript.stdout.write "Enter an integer >0: "
    ' read the number
    number = wscript.stdin.readLine
    ' check the format of the read data
    Dim matches
    Set matches = template.execute(number)
    ' Has the model been verified?
    If matches.count <> 0 Then
        ' It is an integer, but is it >0?
        number = Int(number)
        If number > 0 Then
            finished=true
        End If
    End If
    ' possible error message
    If Not finished Then wscript.echo "Error: You did not enter an integer greater than 0. Please try again..."
Loop

' confirmation
wscript.echo "Thank you for the integer >0: " & number

' and end
wscript.quit 0
Results

Enter an integer >0: 10.3
Error, you did not enter an integer >0. Please try again...
Enter an integer >0: abcd
Error: You did not enter an integer >0. Please try again...
Enter an integer >0: -4
Error: You did not enter an integer greater than 0. Please try again...
Enter an integer greater than 0: 0
Error: You did not enter an integer greater than 0. Please try again...
Enter an integer >0: 1
Thank you for the integer >0: 1

Finding the regular expression that allows us to verify whether a string matches a certain pattern can sometimes be a real challenge. The following program lets you practice. It asks for a pattern and a string, and then indicates whether or not the string matches the pattern.

Program
' regular expression

' we want to verify that a string matches a pattern

Option Explicit

' define the pattern
Dim pattern
Set pattern = New Regexp
pattern.global = true                      ' We will search for the pattern multiple times in the string

' The user provides the string in which to search for the pattern
Dim string, matches, i

Do While True
    ' the user is prompted to enter a pattern
    wscript.stdout.write "Enter the pattern to test and 'end' to stop: "
    pattern.pattern = wscript.stdin.readLine
    ' Done?
    If pattern = "end" Then Exit Do
        ' Ask the user for the strings to compare against the pattern
        Do While True
            ' Ask the user to enter a pattern
            wscript.stdout.writeLine "Enter the string to test against the pattern [" & pattern.pattern & "] and 'end' to stop: "
            string = wscript.stdin.readLine
            ' Done?
            If string="end" Then Exit Do
            ' Compare the read string to the date pattern
            Set matches = pattern.execute(string)
            ' Did we find a match?
            If matches.count <> 0 Then
                ' we have at least one match
                For i = 0 To matches.count - 1
                    ' display match i
                    wscript.echo "I found match " & matches(i).value
                Next
            Else
                ' no match
                wscript.echo "I didn't find a match"
            End If
    Loop
Loop

' done
wscript.quit 0
Results
Type the pattern to test and press Enter to stop: ^\s*\d+(\,\d+)*\s*$

Enter the string you want to test using the pattern [^\s*\d+(\,\d+)*\s*$] and press Enter to stop:
18
I found the match [18]

Type the string to test with the pattern [^\s*\d+(\,\d+)*\s*$] and press Enter to stop:
145,678
I didn't find a match

Type the string to test with the pattern [^\s*\d+(\,\d+)*\s*$] and press Enter to stop:
145.678
I found the match [  145.678   ]

4.3. Handling runtime errors

Another method for handling runtime errors is to let them occur, be notified of them, and then handle them. Normally, when an error occurs during execution, WSH displays an error message and the program stops. Two statements allow us to change this behavior:

on error resume next

This statement tells the system (WSH) that we will handle errors ourselves. After this statement, any errors are simply ignored by the system.

on error goto 0

This statement returns us to normal error handling.

When the on error resume next statement is active, we must handle any errors that may occur ourselves. The Err object helps us do this. This object has various properties and methods, of which we will focus on the following two:

  • number: an integer representing the number of the last error that occurred. 0 means "no error"
  • description: the error message the system would have displayed if we hadn’t issued the on error resume next statement

Let’s look at the following example:

Program
Results

' unhandled error

Option Explicit
Dim number

number = cdbl("abcd")
wscript.echo "number=" & number
C:\ err5.vbs(6, 1) Microsoft VBScript runtime error: Incompatible type: 'cdbl'

Now let's handle the error:

Program
Results
' error handled

Option Explicit
Dim number

' we handle errors ourselves
On Error Resume Next
number = CDBL("abcd")
' Was there an error?
If Err.Number <> 0 Then
    wscript.echo "Error [" & err.description & "] occurred"
    On Error GoTo 0
    wscript.quit 1
End If
' No error - return to normal operation
On Error GoTo 0
wscript.echo "number=" & number
wscript.quit 0
The [Incompatible Type] error occurred

Let's rewrite the program for entering an integer >0 using this new method:

Program

' Read data until it is recognized as a number

Option Explicit

Dim finished, number

' loop until the entered data is correct
' the loop is controlled by a Boolean variable 'finished', set to false at the start (= not finished)

finished = false
Do While Not finished
  ' ask for the number
  wscript.stdout.write "Enter an integer >0: "
  ' read the number
  number = wscript.stdin.readLine
  ' check the format of the read data
  On Error Resume Next
  number = CDBL(number)
  If err.number = 0 Then
    ' no error, it's a number
    ' return to normal error handling mode
    On Error GoTo 0
    ' Is it an integer greater than 0?
    If (number - Int(number)) = 0 And number > 0 Then
      finished=true
    End If
  End If
  ' return to normal error handling mode
  On Error GoTo 0
  ' possible error message
  If Not finished Then wscript.echo "Error: You did not enter an integer greater than 0. Please try again..."
Loop

' confirmation
wscript.echo "Thank you for the integer >0: " & number

' and end
wscript.quit 0
Results

Enter an integer >0: 4.5
Error, you did not enter an integer >0. Please try again...
Enter an integer >0: 4.5
Error: You did not enter an integer greater than 0. Please try again...
Enter an integer >0: abcd
Error: You did not enter an integer greater than 0. Please try again...
Enter an integer greater than 0: -4
Error: You did not enter an integer greater than 0. Please try again...
Enter an integer >0: 1
Thank you for the integer >0: 1

Comments:

  • This method is sometimes the only one that can be used. In that case, do not forget to return to normal error handling mode as soon as the sequence of instructions likely to generate the error is complete.

4.4. Application to the tax calculation program

We’ll revisit the tax calculation program we wrote earlier to, this time, verify the validity of the arguments passed to the program:

Program

' Calculating a taxpayer's tax
' the program must be called with three parameters: married children salary
' married: character O if married, N if unmarried
' children: number of children
' salary: annual salary without cents

' no data validation is performed, but we
' we do check that there are exactly three

' mandatory variable declaration
Option Explicit
Dim syntax
syntax = _
    "Syntax: pg married children salary" & vbCRLF & _
    "married: character O if married, N if unmarried" & vbCRLF & _
    "children: number of children (integer >=0)" & vbCRLF & _
    "salary: annual salary without cents (integer >=0)"

' Check that there are 3 arguments
  Dim nbArguments
  nbArguments = wscript.arguments.count
  If nbArguments<>3 Then
    ' error message
    wscript.echo syntax & vbCRLF & vbCRLF & "error: incorrect number of arguments"
    ' exit with error code 1
    wscript.quit 1
  End If

' retrieve the arguments and check their validity
' An argument is passed to the program without spaces before or after
' we will use regular expressions to verify the validity of the data
  Dim pattern, matches
  Set pattern = new regexp

  ' the marital status must consist of the characters oOnN
  pattern.pattern="^[oOnN]$"
  Set matches = template.execute(wscript.arguments(0))
  If matches.count=0 Then
    ' error
    wscript.echo syntax & vbCRLF & vbCRLF & "error: incorrect marital status argument"
    ' exit
    wscript.quit 2
  End If
  ' retrieve the value
  Dim marie
  If lcase(wscript.arguments(0)) = "o" Then
    marie = true
  Else
    married=false
  End If

  ' children must be an integer >=0
  pattern.pattern="^\d{1,2}$"
  Set matches = pattern.execute(wscript.arguments(1))
  If matches.count=0 Then
    ' error
    wscript.echo syntax & vbCRLF & vbCRLF & "error: incorrect children argument"
    ' exit
    wscript.quit 3
  End If
  ' retrieve the value
  Dim children
  children = Int(WScript.Arguments(1))

  ' salary must be an integer >=0
  pattern.pattern="^\d{1,9}$"
  Set matches = pattern.execute(wscript.arguments(2))
  If matches.count=0 Then
    ' error
    wscript.echo syntax & vbCRLF & vbCRLF & "error: incorrect salary argument"
    ' exit
    wscript.quit 4
  End If
  ' retrieve the value
  Dim salary
  salary = clng(wscript.arguments(2))

  ' define the data needed to calculate the tax in 3 tables
  Dim limits, coeffn, coeffr
  limits = array(12620, 13190, 15640, 24740, 31810, 39970, 48360, _
    55790,92970,127860,151250,172040,195000,0)
  coeffr=array(0,0.05,0.1,0.15,0.2,0.25,0.3,0.35,0.4,0.45, _
    0.5,0.55,0.6,0.65)
  coeffn=array(0,631,1290.5,2072.5,3309.5,4900,6898.5,9316.5, _
    12106,16754.5,23147.5,30710,39312,49062)

  ' we calculate the number of parts
  Dim nbParts
  If marie=true Then
    nbParts=(children/2)+2
  Else
    nbParts = (children / 2) + 1
  End If
  If children >= 3 Then nbParts = nbParts + 0.5

  ' Calculate the family quotient and taxable income
  Dim income, qf
  income = 0.72 * salary
  qf = income / nbParts

  ' Calculate the tax
  Dim i, tax
  i = 0
  Do While i < ubound(limits) And qf > limits(i)
    i = i + 1
  Loop
  tax = int(income * coeffr(i) - nbParts * coeffn(i))

  ' display the result
  wscript.echo "tax=" & tax

  ' exit without error
  wscript.quit 0
Results
C:\>cscript impots2.vbs

Syntax: pg married children salary
married: character O if married, N if unmarried
children: number of children (integer >=0)
salary: annual salary without cents (integer >=0)

error: incorrect number of arguments

C:\>cscript impots2.vbs a b c

Syntax: pg married children salary
married: character O if married, N if unmarried
children: number of children (integer >=0)
salary: annual salary without cents (integer >=0)

Error: Invalid argument "marie"


C:\>cscript impots2.vbs o b c

Syntax: pg married children salary
married: character O if married, N if unmarried
children: number of children (integer >=0)
salary: annual salary without cents (integer >=0)

error: incorrect children argument

C:\>cscript impots2.vbs o 2 c


Syntax: pg married children salary
married: character O if married, N if unmarried
children: number of children (integer >=0)
salary: annual salary without cents (integer >=0)

error: incorrect salary argument

C:\>cscript impots2.vbs o 2 200000

tax=22504