PowerShell: A Logging Function

Often you need to record what a script does to a file.  You can easily get something going with Start-Transcript but sometimes you want to control what is written with a bit more granularity.

So here’s a function I tend to re-use in my scripts.  It takes text output and writes it both to a text log file and to the Verbose output stream.

Here’s the function with some explanation about how it works afterwards;

<# .SYNOPSIS This writes verbose or error output while also logging to a text file. .DESCRIPTION This writes verbose or error output while also logging to a text file. As these scripts are aimed at PowerShell 2.0 (default Exchange 2010) the Verbose/Error redirect features are not present. .PARAMETER $Output The string to write to the log file and Error / Verbose streams. .PARAMETER $IsError If this switch is specified the $Output string is written to the Error stream instead of Verbose. .PARAMETER $Heading Makes the passed string a heading (gives it a border) .PARAMETER $Emphasis Puts an emphasis character on either side of the string to output. #>
function Write-Log
{
    [CmdletBinding()]
    param
    (
     [Parameter(
        Mandatory=$true,
        ValueFromPipeline=$true)]
     [String]$Output,
     [switch]$IsError=$False,
     [switch]$Heading=$False,
     [switch]$Emphasis=$False
    )
    $TitleChar="*"
    $FormattedOutput=@()
    if ($Heading)
    {
        $TitleBar=""
        for ($i=0;$i -lt ($Output.Length)+2; $i++)
        {
            $TitleBar+=$TitleChar
        }
        $FormattedOutput=@("",$TitleBar,"$TitleChar$Output$TitleChar",$TitleBar,"")
    }elseif ($Emphasis)
    {
        $FormattedOutput+="$TitleChar$Output$TitleChar",""
    }else
    {
        $FormattedOutput+=$Output
    }
    if (!$IsError)
    {
        $FormattedOutput=$FormattedOutput | %{(Get-Date).ToLongTimeString()+" : " + $_}
        $FormattedOutput | Write-Error
    }else
    {
        $FormattedOutput=$FormattedOutput | %{(Get-Date).ToLongTimeString()+" : ERROR " + $_}
        $FormattedOutput | Write-Verbose
    }
    if (($LogFile -ne $Null) -and ($LogFile -ne ""))
    {
        $FormattedOutput | Out-File -Append $LogFile
    }
}

The first thing to mention is that $LogFile is a global variable which holds the path to where I’m going to write the log file entries.  I could have defined it as a parameter and passed it to the function but it’s something I define at the start of the script somewhere so that someone else can easily modify it without needing to know any PowerShell.

    [CmdletBinding()]
    param
    (
     [Parameter(
        Mandatory=$true,
        ValueFromPipeline=$true)]
     [String]$Output,
     [switch]$IsError=$False,
     [switch]$Heading=$False,
     [switch]$Emphasis=$False
    )

The only thing we really need is the output to write to the screen and the log file so $Output is mandatory.   The additional switches define if the output is an error, whether it should look like a heading or whether I should emphasise it.

    $TitleChar="*"
    $FormattedOutput=@()
    if ($Heading)
    {
        $TitleBar=""
        for ($i=0;$i -lt ($Output.Length)+2; $i++)
        {
            $TitleBar+=$TitleChar
        }
        $FormattedOutput=@("",$TitleBar,"$TitleChar$Output$TitleChar",$TitleBar,"")
    }elseif ($Emphasis)
    {
        $FormattedOutput+="$TitleChar$Output$TitleChar",""
    }else
    {
        $FormattedOutput+=$Output
    }

$TitleChar is the character I’m going to use to build a banner for the header (and to surround emphasised text).

In the case of a header I want to surround the text in a border made up of $TitleChar.  That means I need a line of characters before, then the emphasised line of output and then another line.  So $FormattedOutput is an array of strings instead of just a string (so it can be three strings in the case of a header).

The $Titlebar is a line of $TitleChar;  the length is the size of $Output + 2 (for the ends of the string).

If the output is to be emphasised (instead of plain or a header) then $FormattedOutput just has the one line (wrapped with $TitleChar).

    if (!$IsError)
    {
        $FormattedOutput=$FormattedOutput | %{(Get-Date).ToLongTimeString()+" : " + $_}
        $FormattedOutput | Write-Error
    }else
    {
        $FormattedOutput=$FormattedOutput | %{(Get-Date).ToLongTimeString()+" : ERROR " + $_}
        $FormattedOutput | Write-Verbose
    }
    if (($LogFile -ne $Null) -and ($LogFile -ne ""))
    {
        $FormattedOutput | Out-File -Append $LogFile
    }

In all cases I prepend the current time to the beginning of the string which is always useful in a log.  If the script is outputting information about an error (and -$IsError is specified) then the text is written to the Error stream.  Otherwise it is written to the Verbose stream.

Last if $LogFile has a value then the output is also sent to that file.

In a longer script I use the function whenever I would have used Write-Error or Write-Verbose;  that makes sure the output goes to the stream as before but also gets recorded to a file for logging purposes.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: