Hey, Scripting Guy! Event 7 *Solutions* from Expert Commentators (Beginner and Advanced; the discus throw)

  

(Note: These solutions were written for Event 7.) 

Beginner Event 7: The discus throw

In the discus throw event, you will be asked to go farther than ever before. To meet this challenge, you must track your progress by writing the information to a log file. You will also be required to read from that log file to determine progress.

Guest commentator: David Zazzo

David is an infrastructure and messaging consultant with Microsoft Consulting Services. He also maintains an eponymous blog on MSDN.

Image of guest commentator David Zazzo 

VBScript solution

The challenge is to take an existing VBScript and extend it to create and write outuput to a log file. Log files are invaluable for troubleshooting. This is particularly important if you have a regularly scheduled task or long-running job that you do not want to continuously monitor.

The script provided performs some basic tasksopen a new instance of Microsoft Office Word, open a document, find misspelled words, replace them with the correct spelling, and get out. This should not be too difficult to document.

The initial version had zero logging capabilities, so I started out by adding four subroutines: OpenLogFile, CloseLogFile, WriteOutput, and WriteCriticalError. These four routines are explained here:

·         OpenLogFile: Creates a new instance of FileSystemObject, and then uses the OpenTextFile method to open or create a log file at the path specified in the script.

·         CloseLogFile: If the log file is open, close it to ensure that any buffers are written out to the file and that the file is closed properly and its resources released.

·         WriteOutput: This routine was created to make my life a little easier. I wanted to write out to the console as well as the log file, and generally, the information would be the same, so this one subroutine does both. It writes out to the console, and if the log file is open and debug logging enabled, it writes it there, too.

·         WriteCriticalError: Similar to above, but this is an error handling routine. Write an error string to WriteOutput, close the log file (using CloseLogFile), and exit the script returning the error number (using Err.Number).

Additionally, I added some other logic to the script.  I added a command-line parameter to enable logging (/debug), instructed Word to save the document and politely exit (the previous version left Word open and running), and provided a summary of the script’s configuration to the user.

The completed BeginnerEvent7Solution.vbs script is seen here.

BeginnerEvent7Solution.vbs

Option Explicit
‘==========================================================================

‘ NAME: Zazzo-ReplaceWordInWord.vbs

‘ AUTHOR: ed wilson , MrEd
‘ DATE  : 5/8/2009

‘ COMMENT: http://www.microsoft.com/technet/scriptcenter/resources/qanda/aug06/hey0808.mspx
‘ HSG-05-14-2009

‘ 5/15/2009 — dzazzo — added helper functions and added
                                                         command-line argument to enable or disable logging
‘==========================================================================

Dim strLogText
Dim fileSystemObject
Dim objLogFile
Dim colArguments
Dim blnDebugMode
Dim textToFind
Dim textToReplace
Dim strFileToOpen
Dim intErrNumber
Dim strErrDescription
Dim objWord
Dim objDoc
Dim objSelection

Const fsoOpenForAppending = 8
Const wdReplaceAll = 2

‘blnDebugMode = True
strFileToOpen = “C:UsersdzazzoDocumentsSummer 09 Scripting GamesZazzotest.doc”
textToFind = “mispelled”
textToReplace = “misspelled”

‘***** Subroutine Declaration *****
Sub OpenLogFile
          Set fileSystemObject = CreateObject( “Scripting.FileSystemObject” )
          If IsObject( fileSystemObject ) Then
                   Set objLogFile = fileSystemObject.OpenTextFile( “c:UsersdzazzoDocumentsSummer 09 Scripting GamesZazzoZazzo-ReplaceWordinWord.log”,fsoOpenForAppending,True )
                   objLogFile.WriteLine VbCrLf & “************************************************************” & VbCrLf & “Zazzo-ReplaceWordinWord started at ” & Now & VbCrLf & “************************************************************”
                   WriteOutput “Debug logging enabled.”
          End If
End Sub

Sub CloseLogFile
          If IsObject( objLogFile ) Then
                   objLogFile.Close
                   Set objLogFile = Nothing
          End If
End Sub

Sub WriteOutput( strLogText )
          If blnDebugMode Then
                   If IsObject( objLogFile ) Then
                             objLogFile.WriteLine Now & “:” & vbTab & strLogText
                   End If
          End If
          WScript.Echo strLogText
End Sub

Sub WriteCriticalError( intErrNumber, strErrDescription )
          WriteOutput “[!] ERROR: ” & intErrNumber & “: ” & strErrDescription
          CloseLogFile
          WScript.Quit intErrNumber
End Sub
         
‘***** Sub Declaration *****

‘***** Set up *****
On Error Resume Next
WriteOutput “Initializing…”
Err.Clear

Set colArguments = WScript.Arguments
If colArguments.Count > 0 Then
          If colArguments.Unnamed.Exists( “/debug” ) Then
                   blnDebugMode = True
          End If
End If

If blnDebugMode Then
          WScript.Echo “Enabling debug logging…”
          OpenLogFile
End If

WriteOutput “Running ReplaceWordinWord with the following settings:” & VbCrLf & _
          vbTab & “Target File:” & VbCrLf & vbTab & strFileToOpen & VbCrLf & _
          vbTab & “Word to Find:” & vbTab & textToFind & VbCrLf & _
          vbTab & “Replace With:” & vbTab & textToReplace & VbCrLf
         
‘***** End Set up *****

Err.Clear
WriteOutput “Starting Microsoft Word…”
Set objWord = CreateObject( “Word.Application” )

If Not IsObject( objWord ) Then
          WriteCriticalError Err.Number, Err.Description
End If

WriteOutput “Word.Application successfully created.”

WriteOutput “Making Word visible…”
objWord.Visible = True


WriteOutput “Opening: ” & strFileToOpen
Set objDoc = objWord.Documents.Open(strFileToOpen)
If Err.Number <> 0 Then
          WriteCriticalError Err.Number, Err.Description
End If

Set objSelection = objWord.Selection
objSelection.Find.Text = textToFind
objSelection.Find.Forward = True
objSelection.Find.MatchWholeWord = True
objSelection.Find.Replacement.Text = textToReplace

WriteOutput “Finding and replacing text…”
objSelection.Find.Execute ,,,,,,,,,,wdReplaceAll
If Err.Number <> 0 Then
          WriteCriticalError Err.Number, Err.Description
End If
         
‘***** Clean up *****
WriteOutput “Saving document…”
objWord.ActiveDocument.Save
If Err.Number <> 0 Then
          WriteCriticalError Err.Number, Err.Description
End If

WriteOutput “Closing Microsoft Word…”
objWord.ActiveDocument.Close
objWord.ActiveWindow.Close
objWord.Application.Quit
Set objWord = Nothing

WriteOutput “Finished!”
CloseLogFile

 

When the script is launched, the is displayed:

Image of what is displayed when script is run 

Guest commentator: Ben Pearce


 Image of guest commentator Ben Pearce

Ben is a premier field engineer at Microsoft in the United Kingdom. He maintains the Benp’s guide to stuff blog on TechNet.  

Windows PowerShell solution

Here are additional details about my solution to this event. There are really two interesting parts to this script: the objects and error checking.

Objects

In Windows PowerShell, objects are your best friend. After you have something represented as an object, you can stick it in an array. And after you have something in an array, you can use the pipeline to get at all of Windows PowerShell’s funky stuff. Ben, what do you mean by “funky stuff”? After we use objects and arrays, we can easily sort, filter, format, and export data. For that reason, I try to use objects as often as possible.

Let me explain the CreateLogObject function. The purpose of this function is to return an object that comprises two properties: the TimeStamp property and the Message property. This object can then be added to the array stored in the $results variable.

Inside the CreateLogObject function, we must create a new .NET object. To do this, we use the New-Object cmdlet and tell it to create a new system.object. We then use the Select-Object cmdlet to add properties to the object. This a bit weird, but Select-Object can add new properties to an object as well as remove properties! This is seen here:

$Log = new-object system.object | select-object Timestamp, message

After we get the object, it is relatively easy to populate the properties with values. We just assign values to the properties as shown here:

$Log.Timestamp = (get-date).datetime

$Log.Message = $Message

We can use the CreateLogObject function to create an entry in the log by passing it the message. We then append it to the end of the array. This is shown here:

$LogRecord = CreateLogObject “Error opening document in Word $ErrMsg  Exiting Script!!”

$results += $LogRecord

After all that is done, look how easy it is to output to console, CSF, and HTML respectively:

$results

$results | Export-csv -Path .WordDebug.csv –NoTypeInformation

$results | ConvertTo-HTML > WordDebug.html 


Error Checking

The other interesting part of the script is error checking. First, we set the value of ErrorActionPreference to “SilentlyContinue.” This means that any errors are added to the automatic $Error array but are suppressed. We then clear $Error by calling the clear method as seen here:

$Error.clear()

When $Error has been cleared, we execute some code. Following that, we check if $Error is still empty. If it is empty, the code completed without an error, and if it is greater than 0, we have an error. This is seen here:

if ($error.count -gt 0)

If we have an error, we can get the error message by querying the message property of the exception. This is seen here:

$ErrMsg = $error[0].exception.message

The completed BeginnerEvent7Solution.ps1 script is seen here.

BeginnerEvent7Solution.ps1

#Useage
#.ReplaceWordInWord.ps1 (Runs script with no debug output)
#.ReplaceWordInWord.ps1 -debug (Displays debug on screen)
#.ReplaceWordInWord.ps1 -debug -csv (Creates CSV file with debug output)
#.ReplaceWordInWord.ps1 -debug -html (Creates html file with debug ouput)

#Use Param to specify debug parameters
param([switch]$debug, [switch]$csv, [switch]$html)

function CreateLogObject($Message)
{
    #This function creates a new custom object and then returns it
    #It takes the message as a parameter
    $Log = new-object system.object | select-object Timestamp, message
    $Log.Timestamp = (get-date).datetime
    $Log.Message = $Message
    return $Log
}

#Suppress error messages as we will deal with these in an error handler
$erroractionpreference = “SilentlyContinue”
$error.clear()

#Test harness that copies the document from the original
del “C:storeMSTech DocsPowershellScripting Games 09test.doc”
copy “C:storeMSTech DocsPowershellScripting Games 09test – Original.doc” -destination “C:storeMSTech DocsPowershellScripting Games 09test.doc”

#create an empty Array
$results = @()

#Create a Word Object
$objWord = New-Object -ComObject word.application
$objWord.Visible = $True
$objDoc = $objWord.Documents.Open(“C:storeMSTech DocsPowershellScripting Games 09test.doc”)
if ($error.count -gt 0)
{
   
        #If error > 0 Could not open word
        $ErrMsg = $Error[0].exception.message
        #Call CreateLogObject function
        $LogRecord = CreateLogObject “Error opening document in Word $ErrMsg  Exiting Script!!”
        #Add Object to Results array
        $results += $LogRecord
         #If Debug flag has been specified then output the results
        if($debug)
        {
            if($csv)
            {
                #Pipe results into Export-CSV
                $results | Export-csv -Path .WordDebug.csv -NoTypeInformation
                #Open file in default csv file handler
                Invoke-Item .WordDebug.csv
            }
            elseif($html)
            {
                #Pipe results into Convert-HTML
                $results | ConvertTo-HTML > WordDebug.html
                #Open file in default HTML file handler
                Invoke-Item .WordDebug.html
            }
            else
            {
                #Simply output results to console
                $results
            }
        } #End $Debug if statement
} #End problem opening Word If Statement
else
{
        #We succesfuly opend the Word Document      
        #Create custom message objct
        $LogRecord =  CreateLogObject “Document Succesfully Opened”
        #Add success record to array
        $results += $LogRecord
        $objSelection = $objWord.Selection

        $FindText = “mispelled”
        $ReplaceText = “spelled incorrectly”

        $ReplaceAll = 2
        $FindContinue = 1
        $MatchCase = $False
        $MatchWholeWord = $True
        $MatchWildcards = $False
        $MatchSoundsLike = $False
        $MatchAllWordForms = $False