Simple Logging Framework for VBScript Files
If you use VB Script files to automate recurring tasks, adding support for severity-based logging offers cheap insurance against future problems.
I often use VB Script files to automate simple, recurring tasks.
I know there are "better" alternatives, but for a quick and dirty solution no other option satisfies all of the following criteria:
- Runs in all versions of Windows with nothing to install (unlike Python)
- No onerous security restrictions (unlike Powershell)
- More capable than a simple Windows batch file (
.bat
) - Hasn't been updated in years (decades?) so it's rock solid dependable
- Integrates well with Windows Task Scheduler to automate recurring tasks
It's greatest strength–simplicity–is also its biggest weakness.
It does not enjoy the same kind of third-party framework support as other alternatives, such as Python or Powershell. As a result, you'll need to build such support from scratch. Today, I'm going to demonstrate how to do that when it comes to logging.
Let's build a simple logging framework for our VB Scripts.
The Logging.vbs File
I like to keep my logging functions in a separate file from my main script.
In the next section, I will show you how to call this file. But for now, let's jump straight to the code. To begin, copy and paste the code below into a file named Logging.vbs
.
The Code
How It Works
This VBScript code defines a function and a subroutine to write log messages to a file.
The GetScriptPath()
function is used to get the path of the current VBScript file. It does this by creating a Wscript.Shell
object and calling the Wscript.ScriptFullName
property to get the full path of the current script, and then using the Scripting.FileSystemObject
to get the parent folder path.
The WriteLog
subroutine is used to write log messages to a log file. It takes two arguments: LogLevel
and Msg
. LogLevel
determines the severity of the log message (e.g. critical, informational, or verbose). Msg
is the message to be logged.
The subroutine uses theFORAPPENDING
constant when opening the log file to specify that new log entries should be appended to the end of the file. It then creates a variable fpLog
which is set to the full path of the log file, by appending the script path obtained from GetScriptPath()
with the log file name defined in a variable LogBase
.
The subroutine then uses the Scripting.FileSystemObject
to check whether the log file exists, and opens it in append mode if it does, or creates a new file if it does not. It then appends a timestamp to the log message, and checks the log level passed to the subroutine. Based on the log level, it appends the correct prefix to the message (e.g. "ERROR" for critical log messages, "Info" for informational messages, "Debug" for verbose messages).
Finally, it checks whether the log level is less than or equal to a global log level (g_LogLevel
). If it is, the log message is written to the log file using the WriteLine
method, and the log file is closed. If not, the log message is not written to the file.
The Main Script File
This is the file with all of the actual business logic.
There are a few things that must be included in your main script file.
- Global constant
g_LogBase
: the base name of your log file - Global constant
g_LogLevel
: the logging level you want (usually 3 in development and 1 or 2 in production) - Subroutine
IncludeRelative
: used to "import" external VBScript files
Here's a minimal example:
How It Works
This VBScript file is an example of how to use the logging functions defined in the "Logging.vbs" file shown earlier. It defines constants to set the log file's base name and the logging level.
The IncludeRelative
subroutine is used to load the "Logging.vbs" file, which contains the logging functions used in this script. It uses the ExecuteGlobal
function to execute the code from "Logging.vbs" in the context of this script.
The code between the comment lines "--== YOUR CODE GOES HERE ==--" is an example of how to use the logging functions. It calls WriteLog
function with different logging levels and messages. The first message is written with the TRACE logging level, which is the most verbose logging level. The second message is written with the INFO logging level, which is used to log file copies and moves. The third message is written with the CRITICAL logging level and includes an intentional error to demonstrate how the Err
object's values can be automatically added to the log message.
Overall, this script demonstrates how to use the logging functions defined in "Logging.vbs" to log messages with different logging levels and how to include the logging functions in another script using the IncludeRelative
subroutine.
Sample Output
Here's what the created log file looks like when we run the above script:
Typical Usage
Here's a more typical scenario:
How It Works
This VBScript file defines a script named "Sample" that simulates the creation of a new file and logs the result using the logging functions defined in the "Logging.vbs" file. As with the Minimal.vbs sample, the script begins by setting some constants for the logging functions, including the base name for the log file and the logging level.
As with the minimal sample, the script then includes the contents of the "Logging.vbs" file using a custom function named IncludeRelative
.
The Main subroutine begins by logging a message at the TRACE level indicating that the script is starting. It then initializes some variables and generates a random number between 0 and 1 using the Rnd() function. If this random number is greater than 0.25, the script sets a variable named FileCreated to True, indicating that the simulated file creation was successful. Otherwise, it sets FileCreated to False.
The script then logs either an INFO or a CRITICAL message depending on the value of FileCreated. If FileCreated is True, the script logs an INFO message indicating that the file was successfully created. If FileCreated is False, the script logs a CRITICAL message indicating that the file creation failed, and then exits with an error code of 100 using the Quit() function.
Finally, the Main subroutine logs a message at the TRACE level indicating that the script is complete and exits with a success code of 0.
Sample Output
I ran this VB Script several times, setting g_LogLevel
to different values. I color-coded the output to correspond with the log level at the time the entries were appended:
1 (CRITICAL)
: Pink highlight2 (INFO)
: Yellow highlight3 (TRACE)
: Green highlight
If you look closely, you'll see I used the logging messages to troubleshoot the sample script itself. When I first used the Rnd()
function, I forgot to randomize it. Every call to Rnd()
returned 0.7055475. Once I added the Randomize
call, the next iteration (starting on line 26) returned 0.132992–which finally simulated a file creation failure.
For the final red section, I had to execute the script about 30 times to produce the eight ERROR lines that you see in the screenshot–the Info lines did not print when the logging level was set to Critical.
Conclusion
By their nature, VB scripts tend to be quick and dirty. But that doesn't mean we need to make things unnecessarily hard on ourselves.
I don't write VB scripts often, but when I do, it's usually because I'm trying to automate a recurring task. The thing with these sorts of recurring tasks is that, once you set them up, they often run for months or even years without requiring any maintenance.
Until one day they break.
If you didn't bother to set up logging when you wrote the script, you'll be in for a nasty surprise. Even with a simple script it can take over an hour to figure out how it's supposed to work. And that's before you've had a chance to start thinking about why it suddenly stopped working.
If you took the time to set up logging, here's how the process will go instead:
- Check the log files to see exactly when the task stopped
- Look for any ERROR lines that might indicate the source of the problem
- Change the logging level to 3 to enable TRACE messages
- Execute the script and let the trace messages guide you
- Easily add additional TRACE messages to further pinpoint the problem
- Solve the problem
- Bask in adulation
Acknowledgements
- Portions of this article's body generated with the help of ChatGPT
Cover image created with Microsoft Designer
BUG FIX [2023-06-30]: Changed LogBase
to g_LogBase
in WriteLog()
function to match declaration in Logging.vbs (h/t bc deewhy in the comments below).