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.