PowerShell Logging – The Basics

PowerShell Logging will be the best thing you learn.  The biggest topic I get asked for help outside the specific activities I am assigned to is PowerShell.    I will be the first to understand everyone has their own style.  I never judge the style or syntax format.  Besides that, PowerShell logging ALWAYS needs to occur.  If you are not logging, your running blind. PowerShell logging is the one the most powerful features you can learn and implement.  

PowerShell does have some native abilities, “Start-Transcript“, and if that is all you want to understand, that is fine.   But, this post will take you through some basic understandings on capturing your outputs in a format that is readable and standard.  With some tricks in creating some great logic around managing your log.

PowerShell Logging – The Setup

The first step into your new logging endeavors is to understand the ability to call a function to simplify your process. 

For this function to become usable we first must set the variable “$logpath” so the function can understand where it is writing to.   My standard is to log to “%SystemDrive%\Windows\Temp”

# Set log path
$logpath = "$env:SystemDrive\Windows\Temp\mynewlog.log"

# Function to write to log file
function Write-Log
{
	param($msg)
	"$(Get-Date -Format G) : $msg" | Out-File -FilePath $logpath -Append -Force
}

Calling the Function

During your process of creating your script you will want to call the PowerShell logging function to say your going to run a process, log the process file call, its arguments, other module calls, and most important capturing any error output. 

Write-Log "Running:"
Write-Log "setup.exe -S -v/l "$env:SystemDrive\Windows\Temp\Setup.log"

PowerShell Logging – Catching the Exception

When starting any process, module, function, or block you should wrap it in “try and catch”.  This will ensure you script never exits unexpectedly and you always capture what is happening.  This is what I have found to be the easiest way to handle most scenarios. 

try {
	Start-Process -FilePath "msiexec.exe" -ArgumentList "/i $PSScriptRoot\msi.msi /qn /norestart /lv $env:SystemDrive\Windows\Temp" -NoNewWindow -PassThru -Wait
}
catch {
	$ErrorMessage = $_
	Write-Log "There was an exception during the process, please review"
	Write-Log "$ErrorMessage"
	Exit 2
}

Write-Log "Process Completed with Success"

PowerShell Logging – Using the Error Variable

A option for logging functions is with the built in “cmdlet” switch “-ErrorVariable”.  This parameter captures the error of the function you are running.  To test use the following example. 

Start-Process -FilePath "msiexec.exe" -ArgumentList "/i $PSScriptRoot\msi.msi /qn /norestart /lv $env:SystemDrive\Windows\Temp" -NoNewWindow -PassThru -Wait -ErrorVariable errvar

if($errvar)
{
	Write-Log "There was an error running the PowerShell function"
	Write-Log "$errvar"
	Exit 2
}

PowerShell Logging – Capturing the Exit Code

When you run “Start-Process” you can capture the exit code when you assign it to a variable with the “-wait” switch included.   This helps when you need to capture non-failure exit codes. For example the “3010” exit  code means there is a “Soft Reboot” required.  This exit code is not a failure.  

$p = Start-Process -FilePath "msiexec.exe" -ArgumentList "/i $PSScriptRoot\msi.msi /qn /norestart /lv $env:SystemDrive\Windows\Temp" -NoNewWindow -PassThru -Wait -ErrorVariable errvar

if($p.ExitCode -ne 0 -and $p.ExitCode -ne 3010)
{
	Write-Log "The process did not exit cleanly"
	$ProcExitCode = $p.ExitCode
	Write-Log "Exit Code: $ProcExitCode"
	Exit $ProcExitCode
}

Bringing It All Together

The below script block brings the above examples together.  Calling the process through a try and catch loop and checking the exit code. 

$setuppath = "msiexec.exe"
$setuplogpath = "$logroot\PS-Deploy-$AppName.log"
$setupargs = "/i `"$sd\install.msi`" /qn /norestart /lv `"$setuplogpath`""
Write-Log "Running: $setuppath $setupargs"
try {
    $p = Start-Process -FilePath "$setuppath" -ArgumentList "$setupargs" -PassThru -NoNewWindow -Wait
    #  Testing the exit code of the process
    If($p.ExitCode -ne 0 -and $p.ExitCode -ne 3010 -and $p.ExitCode -ne $null)
    {   
        # ERROR Handling
        Write-Log "The setup process didn't not exit with a recognized exit code"
        $ExitCode = $p.ExitCode
        Write-Log "Exit Code: $ExitCode"
        Exit 2
    }  
}
catch {
    # ERROR Catch Exceptions
    Write-Log "There was error starting the process for $AppName"
    $ErrorMessage = $_
    Write-Log "Error Message: `n$ErrorMessage"
    Exit 1
}

PowerShell Logging – SCCM Task Sequence

If your responsibilities have you running a lot of processes in a SCCM task sequence environment the following code block will be most helpful.  This will allow you to log to the same directory the SCCM task environment is logging to. The below script block outlines loading the COM object to test if you are in such an environment and gather the directory where logging is occurring. 

When the Microsoft Deployment Toolkit (MDT) is integrated, and utilizing the “Copy Logs on Error” function, these logs will be copied to the log monitoring directory.  

# Loading the COM object for the TS Environment, this is to read / write to TS Variables
$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment

# Setting up logpath if in OSD Process will log to _SMSTSLogPath variable else Windows\Temp
If($tsenv)
{
    # In a SCCM Task Sequence Environment
    $AppName = $script:MyInvocation.MyCommand.Name
    $AppName = $AppName -replace ".ps1",""
    $AppName = $AppName -replace "Invoke-","SMSTS-Script-"
    $logname = "$AppName.log"
    # Getting the log path variable "_SMSTSLogPath"
    $TSLogPath = $tsenv.Value("_SMSTSLogPath")
    $logroot = "$TSLogPath"
    $logpath = "$logroot\$logname"

}
else {
    # Running in a normal Operating System Environment
    $logroot = "$env:SystemDrive\Windows\Temp"
    $AppName = $script:MyInvocation.MyCommand.Name
    $AppName = $AppName -replace ".ps1",""
    $AppName = $AppName -replace "Invoke-","PS-Script-"
    $logname = "$AppName.log"
    $logpath = "$logroot\$logname"
}

PowerShell Logging with a Process

When developing a process that runs on a schedule you will want to manage the file.  The below script block shows how to parameter the log location and size of the file; with applying a header to a new log file.

# Set the parameters for monitoring process

Param (
    $LogPath = "$PSScriptRoot\LogFile.log",
    $logsize = 2
)
$desc = "Description"
$version = "1704.28.1828" 

# Define log function
function Write-Log ([string]$msg)
{
   "$(Get-Date -Format G): $msg" | Out-File -FilePath $LogPath -Append -Force
}

# Log header
$logheader = "###########################
##`t$desc
##`tVersion: $version
##`t $(Get-Date -Format G)
##########################"

# Test if the log exists
If (Test-Path -Path $LogPath)    
{
    # Get the size of the log file
    $size = (Get-Item -Path $LogPath).Length / (1MB)
	
    # Test the size and remove if greater than or equal
	If ($size -ge $logsize)
	{
	    $logheader | Out-File $LogPath -Force
	    Write-Log "Starting new monitoring process....."
	}
	else
	{
	    Write-Log "Starting new monitoring process....."
	}
}
Else
{
	$logheader | Out-File $LogPath -Force
	Write-Log "Starting new monitoring process....."
}

PowerShell Logging to the Event Viewer

When developing workflows, services, or schedule tasks you can use the system’s event viewer to capture your necessary output.  Utilizing the system’s Event Viewer with PowerShell Logging will allow you to monitor the process with a tool like System Center Operations Manager, Microsoft Operations Manager Suite, or any other log analytic system. 

# Test if Event Log Source exists
$eventsource = [System.Diagnostics.EventLog]::SourceExists("PROCESS NAME")

# If it does not exist create it
If(-not($eventsource))
{
    New-EventLog -LogName Application -Source "PROCESS NAME"
}

# Writing New Monitoring Process Entry
Write-EventLog -LogName Application -Source "PROCESS NAME" -EntryType Information -EventId 1000 -Category "" -Message "The PROCESS NAME Script Started.."

# Try / Catch Loop
Try
{
    Set-ADUser -Identity $wUser -Server $DC -Replace @{ logonhours = [Byte[]]$LogonHoursEnabled }
    Write-EventLog -LogName Application -Source "PROCESS NAME" -EntryType Information -EventId 1000 -Category "" -Message "The PROCESS NAME Script Completed.."
}
Catch
{
    $CurrentErrorMessage = $_
    Write-EventLog -LogName Application -Source "PROCESS NAME" -EntryType Error -EventId 1004 -Category "" -Message "Failed to write logon hours for $wUser on the following DC: `n$DC`n`nReview:`nUser exists`nDC is reachable`nPermissons are set`n`nException Message:`n$CurrentErrorMessage"			        
}

With All That Being Said

When developing your next PowerShell script or process; I hope this post has outlined some necessary tools or ideas that will ultimately lead to more PowerShell logging.  Just having this in tool box has saved me countless of hours sifting through command windows and debugging the script.  

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.