PowerShell 2.0: A Configurable and Flexible Script Logger Module

One thing that has been lamented frequently about PowerShell is that it is very difficult to log, if not impossible, to log all of the various types of streams it has to a single source. The legacy Windows shell CMD, and Unix shells like Bash, Ksh etc only deal with three streams: stdin, stdout and stderr for Input, Ouput and Error respectively. PowerShell has many more: Input, Output, Verbose, Warning, Debug, Progress and Error. Finally, the APIs in v2.0 offer enough hooks to unify the logging but you got to work a bit to make it come together. Well, to be honest, you got me doing the work. The rest is easy ;)

import-module .\scriptlogger.psm1 -force

$logger = New-ScriptLogger

# override error handler
$logger.ErrorHandler = {
    param($record)
    
    $record.tostring() >> scriptlog.txt
}

# override verbose handler
$logger.VerboseHandler = {
    param($record)
    
    $record.message >> scriptlog.txt
}

# run scriptblock with logging
$logger.Invoke(
    {
        $verbosepreference='continue';
        $erroractionpreference = 'continue';
        $debugpreference = 'continue';
        write-verbose "verbose";
        write-error "an error";
        write-warning "a warning"
        Write-debug "debug string"
        "this is output"
        1,2,3
    })
And here is the module; save it as ScriptLogger.psm1. By default, all logging goes to an attached debugger, like sysinternals DbgView. You can override any of the handlers like above and do what you want. Each handler receives one argument: a ErrorRecord, WarningRecord, VerboseRecord, DebugRecord or ProgressRecord. All of these Types are native powershell types and are documented on MSDN.
<#
    Name     : Universal Script Logging Module (ScriptLogger.psm1)
    Version  : 0.1
    Author   : Oisin Grehan (MVP)
    Site     : http://www.nivot.org/
#>
function New-ScriptLogger {
    New-Module -AsCustomObject -ScriptBlock {
        
        $script:ps              = [powershell]::Create()
        $script:ar              = $null
        $script:module          = $ExecutionContext.SessionState.Module
        
        [scriptblock]
        $script:ErrorHandler    = {
            param(
                [Management.Automation.ErrorRecord]
                $record
            )
            [diagnostics.debug]::writeline(
                "Error: " + $record.tostring());
        }
        [scriptblock]
        $script:WarningHandler  = {
            param(
                [Management.Automation.WarningRecord]
                $record
            )        
            [diagnostics.debug]::writeline(
                "Warning: " + $record.message);
        }
        [scriptblock]
        $script:VerboseHandler  = {
            param(
                [Management.Automation.VerboseRecord]
                $record
            )
            [diagnostics.debug]::writeline(
                "Verbose: " + $record.message);
        }
        [scriptblock]
        $script:DebugHandler    = {
            param(
                [Management.Automation.DebugRecord]
                $record
            )
            [diagnostics.debug]::writeline(
                "Debug: " + $record.message);
        }
        [scriptblock]
        $script:ProgressHandler = {
            param(
                [Management.Automation.ProgressRecord]
                $record
            )        
            [diagnostics.debug]::writeline(
                "Progress: " + $record);
        }
        
        $script:Handlers   = @{
            Error = Register-ObjectEvent $ps.Streams.Error DataAdded -Action {
                & $event.MessageData {& $ErrorHandler @args} $event.sender[$eventargs.index]
            } -MessageData $module #-SupportEvent
            
            Warning = Register-ObjectEvent $ps.Streams.Warning DataAdded -Action {
                & $event.MessageData {& $WarningHandler @args} $event.sender[$eventargs.index]
            } -MessageData $module #-SupportEvent
            
            Verbose = Register-ObjectEvent $ps.Streams.Verbose DataAdded -Action {
                & $event.MessageData {& $VerboseHandler @args} $event.sender[$eventargs.index]
            } -MessageData $module #-SupportEvent
            
            Debug = Register-ObjectEvent $ps.Streams.Debug DataAdded -Action {
                & $event.MessageData {& $DebugHandler @args} $event.sender[$eventargs.index]
            } -MessageData $module #-SupportEvent
            
            Progress = Register-ObjectEvent $ps.Streams.Progress DataAdded -Action {
                & $event.MessageData {& $ProgressHandler @args} $event.sender[$eventargs.index]
            } -MessageData $module #-SupportEvent
        }
        
        function Invoke {
            param(
                [validatenotnullorempty()]
                [scriptblock]$script
            )
            
            try {
            
                write-host -foreground green "Running"
                
                $ps.commands.clear()
                $command = new-object management.automation.runspaces.command $script, $true            
                $ps.commands.addcommand($command) > $null                
                $ps.invoke() # returns output                
            
            } catch {
            
                # oops-ee!
                write-host -foreground red "Unhandled terminating error: $_"
                $record = new-object management.automation.errorrecord $(
                    new-object exception $_.tostring()), "TerminatingError", "NotSpecified", $null
                & $ErrorHandler $record
            
            } finally {
            
                write-host -foreground green "Complete"
            
            }
        }
                
        Export-ModuleMember -Function Invoke -Variable ErrorHandler, WarningHandler, VerboseHandler, DebugHandler, ProgressHandler
    }
}

Have fun!

blog comments powered by Disqus

About the author

Irish, PowerShell MVP, .NET/ASP.NET/SharePoint Developer, Budding Architect. Developer. Montrealer. Opinionated. Montreal, Quebec.

Month List

Page List