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.