Powershell logging error

Error Handling is a very important concept in every programming language including PowerShell which gives us several possibilities to manage errors in code. In this…

Error Handling is a very important concept in every programming language including PowerShell which gives us several possibilities to manage errors in code.

In this article, I will try to cover as many as possible error handling concepts with examples from my practice and one of the important concepts is writing errors in external Error Log text file.

Why Should We Bother Handling Errors In PowerShell

It is no fun to run any code or application full of errors and bugs as the matter a fact it is quite annoying so in order for users to have a pleasant experience handling the errors is one of the essentials in programming.

PowerShell is no exception to that way of thinking and programming ethics although it can take some additional coding and effort but trust me it is always worth it.

I would be more than happy to share my experience with error handling and even more satisfied if I can hear your tips from the field so we can all enrich our knowledge on this very important subject.

Here is a general approach to error handling and logging errors into the external text file. I have an example in the next subheading that further explain each step so everything is much more understandable.

  1. Identify which Commands need Error Handling in your Function, CmdLet or Script.
  2. Set the ErrorAction parameter to value Stop for the command that needs Error Handling. This will stop executing the command when an error occurs and it will show the error message. Basically, we are making non-terminating errors into terminating in order to be able to handle the error in the catch block.
  3. Command that has ErrorAction parameter set to value Stop is wrapped in Try { } block. So when Error occurs handling of the code will jump from the Try block to Catch block.
  4. In Catch { } block that immediately follows after Try {} block, the error is handled by writing to the error log file on the disk.
  5. We use our own Write-ErrorLog CmdLet inside Catch{ } block to write the error in a text file on the disk. (See the explanation and code here)
  6. We have an additional switch error parameter to decide whether we want to write an error in the log file or not. This is totally optional.
  7. Use the Finally { } block if needed.
  8. Test the whole setup by intentionally breaking the code while in the development phase.
  9. Since some of the CmdLets calls are Scheduled we have routine to check external Error Log text file at least once a week and investigate errors that are caught. This step is part of improving the overall quality of the written code.

Example Of PowerShell Error Handling

To show you Error Handling and implement previously defined steps I will use my own Get-CPUInfo CmdLet which is in the Common module of the Efficiency Booster PowerShell Project. Efficiency Booster PowerShell Project is a library of CmdLets that help us IT experts in day to day IT tasks.

In order to follow me along, I highly encourage you to download the zip file with the source code used in this example.

Here is the location of Get-CPUInfo script:
…[My] DocumentsWindowsPowerShellModules3common

Get-CPUInfo CmdLet script location

Let’s use steps defined in the previous subheading to this example.

Step 1. I have identified the command that needs Error Handling in Get-CPUInfo CmdLet and that is a call to Get-CimInstance CmdLet.

Get-CimInstance @params 

Step 2. So I have set up the ErrorAction parameter to the value ‘Stop‘ for Get-CimInstance CmdLet in order to force non-terminating errors into terminating and then to be able to handle such errors.

INFO: I use parameter splatting when running CmdLet. If you want to know more about parameter splating please read this article.

            $params = @{ 'ComputerName'=$computer;
                         'Class'='Win32_Processor';
                         'ErrorAction'='Stop'}
            $CPUInfos = Get-CimInstance @params | 
                            Select-Object   @{label="ServerName"; Expression={$_.SystemName}}, 
                                            @{label="CPU"; Expression={$_.Name}}, 
                                            @{label="CPUid"; Expression={$_.DeviceID}}, 
                                            NumberOfCores, 
                                            AddressWidth

Step 3. Wrap up the call to Get-CimInstance CmdLet into the Try Block in order to be able to handle the error in a catch block that follows.

         try {
            Write-Verbose "Start processing: $computer - $env - $logicalname"
            Write-Verbose "Start Win32_Processor processing..."
            $CPUInfos = $null
            $params = @{ 'ComputerName'=$computer;
                         'Class'='Win32_Processor';
                         'ErrorAction'='Stop'}
            $CPUInfos = Get-CimInstance @params | 
                            Select-Object   @{label="ServerName"; Expression={$_.SystemName}}, 
                                            @{label="CPU"; Expression={$_.Name}}, 
                                            @{label="CPUid"; Expression={$_.DeviceID}}, 
                                            NumberOfCores, 
                                            AddressWidth           
            Write-Verbose "Finish Win32_Processor processing..."                    
            foreach ($CPUInfo in $CPUInfos) {
                Write-Verbose "Start processing CPU: $CPUInfo"
                $properties = @{ 'Environment'=$env;
                                 'Logical name'=$logicalname;
                                 'Server name'=$CPUInfo.ServerName;
            	                 'CPU'=$CPUInfo.CPU;
            	                 'CPU ID'=$CPUInfo.CPUid;
            	                 'Number of CPU cores'=$CPUInfo.NumberOfCores; 
                                 '64 or 32 bits'=$CPUInfo.AddressWidth;
                                 'IP'=$ip;
                                 'Collected'=(Get-Date -UFormat %Y.%m.%d' '%H:%M:%S)}
                $obj = New-Object -TypeName PSObject -Property $properties
                $obj.PSObject.TypeNames.Insert(0,'Report.CPUInfo')
                Write-Output $obj
                Write-Verbose "Finish processing CPU: $CPUInfo"
            }
 Write-Verbose "Finish processing: $computer - $env - $logicalname"                       
        }

Step 4. When the error occurs in the try block it is handled in the Catch Block.
It is important to notice following in the catch block of code:

  • Get-CPUInfo CmdLet switch parameter $errorlog has been used to decide whether to log the errors in an external text file or not. This is completely optional.
  • Certain Error properties are collected using an automatic variable $_ ($PSItem is another name for the same variable). If you want to know more about which properties we collect please read here.
  • Collected data about the error that will be handled has been passed to another CmdLet Write-ErrorLog that will write the data in an external text log file. Please read here about Write-ErrorLog CmdLet.
catch {
            Write-Warning "Computer failed: $computer - $env - $logicalname CPU failed: $CPUInfos"
            Write-Warning "Error message: $_"
            if ( $errorlog ) {
                $errormsg = $_.ToString()
                $exception = $_.Exception
                $stacktrace = $_.ScriptStackTrace
                $failingline = $_.InvocationInfo.Line
                $positionmsg = $_.InvocationInfo.PositionMessage
                $pscommandpath = $_.InvocationInfo.PSCommandPath
                $failinglinenumber = $_.InvocationInfo.ScriptLineNumber
                $scriptname = $_.InvocationInfo.ScriptName
                Write-Verbose "Start writing to Error log."
                Write-ErrorLog -hostname $computer -env $env -logicalname $logicalname -errormsg $errormsg -exception $exception -scriptname $scriptname -failinglinenumber $failinglinenumber -failingline $failingline -pscommandpath $pscommandpath -positionmsg $pscommandpath -stacktrace $stacktrace
                Write-Verbose "Finish writing to Error log."
            }
        }

Step 5. I have already mentioned that Write-ErrorLog CmdLet has been used to write the error data into an external text log file. Read more about this CmdLet here.

Step 6. I did not need Finally { } block for this example.

Step 7. In the development phase, I was intentionally breaking Get-CPUInfo CmdLet to test my error handling code.

Step 8. If Get-CPUInfo CmdLet is part of the scheduled code I would look regularly Error log file and work on correcting all the bugs in the code produced by Get-CPUInfo CmdLet.

Chain Of Events When PowerShell Error Occurs

Let’s talk a little bit about what is happening when an error occurs in PowerShell and which events are triggered one after another.

  • Call to CmdLet is failing and error has just occurred.
  • Since we have ErrorAction parameter set to value Stop our non-terminating error has forced into terminating error so the execution of code stops.
  • The error that is failing in the CmdLet is written in the $Error automatic variable by the PowerShell.
  • We have forced the error to be terminating in order to be able to handle with try catch finally block of error handling.
  • Since our CmdLet call is wrapped in Try block and error is terminating PowerShell can trigger error handling looking for a Catch block.
  • We can have several Catch blocks for one try block. If we have a Catch block that handles the actual error number that block is executed.
  • Otherwise, PowerShell will look Catch block that handles all error numbers.
  • Optionally in the Catch block, we can have code that will write the error in the external text Error log file.
  • We can use the automatic variable $Error or $_ object to read Error data and write them in the external file.
  • If there is no Catch block PowerShell will look for Catch block in parent call if we have nested calls.
  • If there are no further Catch block or no Catch block at all that will handle error then PowerShell looks for Finally block to execute which is used to clean up resources as needed.
  • After executing Finally block error message will be sent to the error stream for further processing.

In further sections of this article, you can read in more detail about the many terms mentioned (ErrorAction, $Error, Try Catch Finally, Terminating, Non-Terminating, etc ) in this bulleted list in order to better understand them.

How To Write PowerShell Errors Into The External Log File

Here I will explain the code of Write-ErrorLog CmdLet that writes error data that occurs in CmdLets and handle the error data into an external text file.

Error data are written in the file named Error_Log.txt in folder PSlogs.

Location and name of external error text log file
Example of error logged in an external text file

Write-ErrorLog CmdLet is part of the Efficiency Booster PowerShell Project and if you want to download the source code of this CmdLet please click here.

Here is the location of Write-ErrorLog script which is part of the Utils module:
…[My] DocumentsWindowsPowerShellModules2utils

Write-ErrorLog CmdLet script location

Write-ErrorLog CmdLet Code

Here is the code of the whole Write-ErrorLog CmdLet.

<#
.SYNOPSIS
Writes errors that occur in powershell scripts into error log file.
.DESCRIPTION
Writes errors that occur in powershell scripts into error log file.
Error log file and error log folder will be created if doesn't exist.
Error log file name is Error_Log.txt and it has been saved into ..DocumentsPSlogs

.PARAMETER hostname
Name of the computer that is failing.

.PARAMETER env
Environment where computer is located. For example: Production, Acceptance, Test, Course etc.

.PARAMETER logicalname
Type of the server that is failing. For example: Application, Web, Integration, FTP, Scan, etc. 

.PARAMETER errormsg
Error message.

.PARAMETER exception
Error number.

.PARAMETER scriptname
Name of the powershell script that is failing.

.PARAMETER failinglinenumber
Line number in the script that is failing.

.PARAMETER failingline
Content of failing line.

.PARAMETER pscommandpath
Path to the powershell command.

.PARAMETER positionmsg
Error message position.

.PARAMETER stacktrace
Stack trace of the error.

.EXAMPLE
Write-ErrorLog -hostname "Server1" -env "PROD" -logicalname "APP1" -errormsg "Error Message" -exception "HResult 0789343" -scriptname "Test.ps1" -failinglinenumber "25" -failingline "Get-Service" -pscommandpath "Command pathc." -positionmsg "Position message" -stacktrace "Stack trace" 

.EXAMPLE
Help Write-ErrorLog -Full

.LINK 
Out-File
#>
Function Write-ErrorLog {
[CmdletBinding()]
param (

    [Parameter(Mandatory=$false,
                HelpMessage="Error from computer.")] 
    [string]$hostname,

    [Parameter(Mandatory=$false,
                HelpMessage="Environment that failed. (Test, Production, Course, Acceptance...)")] 
    [string]$env,

    [Parameter(Mandatory=$false,
                HelpMessage="Type of server that failed. (Application, Web, Integration...)")] 
    [string]$logicalname,
    
    [Parameter(Mandatory=$false,
                HelpMessage="Error message.")] 
    [string]$errormsg,
    
    [Parameter( Mandatory=$false,
                HelpMessage="Exception.")]
    [string]$exception,
    
    [Parameter(Mandatory=$false, 
                HelpMessage="Name of the script that is failing.")]
    [string]$scriptname,
     
    [Parameter(Mandatory=$false,
                HelpMessage="Script fails at line number.")]
    [string]$failinglinenumber,

    [Parameter(Mandatory=$false,
                HelpMessage="Failing line looks like.")]
    [string]$failingline,
    
    [Parameter(Mandatory=$false,
                HelpMessage="Powershell command path.")]
    [string]$pscommandpath,    

    [Parameter(Mandatory=$false,
                HelpMessage="Position message.")]
    [string]$positionmsg, 

    [Parameter(Mandatory=$false,
                HelpMessage="Stack trace.")]
    [string]$stacktrace
)
BEGIN { 
        
        $errorlogfile = "$homeDocumentsPSlogsError_Log.txt"
        $errorlogfolder = "$homeDocumentsPSlogs"
        
        if  ( !( Test-Path -Path $errorlogfolder -PathType "Container" ) ) {
            
            Write-Verbose "Create error log folder in: $errorlogfolder"
            New-Item -Path $errorlogfolder -ItemType "Container" -ErrorAction Stop
        
            if ( !( Test-Path -Path $errorlogfile -PathType "Leaf" ) ) {
                Write-Verbose "Create error log file in folder $errorlogfolder with name Error_Log.txt"
                New-Item -Path $errorlogfile -ItemType "File" -ErrorAction Stop
            }
        }
}
PROCESS {  

            Write-Verbose "Start writing to Error log file. $errorlogfile"
            $timestamp = Get-Date 
            #IMPORTANT: Read just first value from collection not the whole collection.
            "   " | Out-File $errorlogfile -Append
            "************************************************************************************************************" | Out-File $errorlogfile -Append
            "Error happend at time: $timestamp on a computer: $hostname - $env - $logicalname" | Out-File $errorlogfile -Append
            "Error message: $errormsg" | Out-File $errorlogfile -Append
            "Error exception: $exception" | Out-File $errorlogfile -Append
            "Failing script: $scriptname" | Out-File $errorlogfile -Append
            "Failing at line number: $failinglinenumber" | Out-File $errorlogfile -Append
            "Failing at line: $failingline" | Out-File $errorlogfile -Append
            "Powershell command path: $pscommandpath" | Out-File $errorlogfile -Append
            "Position message: $positionmsg" | Out-File $errorlogfile -Append
            "Stack trace: $stacktrace" | Out-File $errorlogfile -Append
            "------------------------------------------------------------------------------------------------------------" | Out-File $errorlogfile -Append                   
            
            Write-Verbose "Finish writing to Error log file. $errorlogfile"
}        
END { 

}
}
#region Execution examples
#Write-ErrorLog -hostname "Server1" -env "PROD" -logicalname "APP1" -errormsg "Error Message" -exception "HResult 0789343" -scriptname "Test.ps1" -failinglinenumber "25" -failingline "Get-Service" -pscommandpath "Command pathc." -positionmsg "Position message" -stacktrace "Stack trace" -Verbose
#endregion

Write-ErrorLog CmdLet Explained

Let’s make our hand’s a little bit “dirty” and dive into PowerShell code.

In the BEGIN block we:

  • Check if folder PSlogs exist in the (My) Documents folder of the current user.
    • If the PSlogs folder doesn’t exist then create the folder.
  • Check if file Error_Log.txt exists in the folder PSlogs.
    • If Error_Log.txt doesn’t exist then create the file.
  • Now we can move on to PROCESS block code.
BEGIN { 
        
        $errorlogfile = "$homeDocumentsPSlogsError_Log.txt"
        $errorlogfolder = "$homeDocumentsPSlogs"
        
        if  ( !( Test-Path -Path $errorlogfolder -PathType "Container" ) ) {
            
            Write-Verbose "Create error log folder in: $errorlogfolder"
            New-Item -Path $errorlogfolder -ItemType "Container" -ErrorAction Stop
        
            if ( !( Test-Path -Path $errorlogfile -PathType "Leaf" ) ) {
                Write-Verbose "Create error log file in folder $errorlogfolder with name Error_Log.txt"
                New-Item -Path $errorlogfile -ItemType "File" -ErrorAction Stop
            }
        }
}

In the PROCESS block:

  • We format the line of text that we want to write into the log file.
  • Then we pipe formatted text to Out-File CmdLet with the Append parameter to write that line of text in the file.
  • We repeat the process of formatting the line of text and appending of that line to the Error log file.
PROCESS {  

            Write-Verbose "Start writing to Error log file. $errorlogfile"
            $timestamp = Get-Date 
            #IMPORTANT: Read just first value from collection not the whole collection.
            "   " | Out-File $errorlogfile -Append
            "************************************************************************************************************" | Out-File $errorlogfile -Append
            "Error happend at time: $timestamp on a computer: $hostname - $env - $logicalname" | Out-File $errorlogfile -Append
            "Error message: $errormsg" | Out-File $errorlogfile -Append
            "Error exception: $exception" | Out-File $errorlogfile -Append
            "Failing script: $scriptname" | Out-File $errorlogfile -Append
            "Failing at line number: $failinglinenumber" | Out-File $errorlogfile -Append
            "Failing at line: $failingline" | Out-File $errorlogfile -Append
            "Powershell command path: $pscommandpath" | Out-File $errorlogfile -Append
            "Position message: $positionmsg" | Out-File $errorlogfile -Append
            "Stack trace: $stacktrace" | Out-File $errorlogfile -Append
            "------------------------------------------------------------------------------------------------------------" | Out-File $errorlogfile -Append                   
            
            Write-Verbose "Finish writing to Error log file. $errorlogfile"
} 

$PSItem or $_

$PSItem contains the current object in the pipeline object and we use it to read Error properties in this case.

Just a quick explanation of each Error property from the $_ ($PSItem) automatic variable that we collect and write in Logfile:

  • $_.ToString() – This is Error Message.
  • $_.Exception – This is Error Exception.
  • $_.InvocationInfo.ScriptName – This the PowerShell script name where Error occurred.
  • $_.InvocationInfo.ScriptLineNumber – This is line number within the PowerShell script where Error occurred.
  • $_.InvocationInfo.Line – This is the line of code within PowerShell script where Error occurred.
  • $_.InvocationInfo.PSCommandPath – This is the path to the PowerShell script file on the disk.
  • $_.InvocationInfo.PositionMessage – This is a formatted message indicating where the CmdLet appeared in the line.
  • $_.ScriptStackTrace – This is the Trace of the Stack.

As you can see on the screenshot below we collect really useful information about the Error that we handle in the Logfile. The pieces of information are presented in very neatly fashion so we can immediately see:

  • which error occurred,
  • what were the message and exception,
  • where the error occurred (script name, script location, line number and line of the code in the script)
  • even the call stack is shown if needed.

Here are the final result and an example of one formatted error logged in Error_Log.txt file.

************************************************************************************************************
Error happend at time: 09/11/2019 18:20:41 on a computer: APP01 -  - 
Error message: The WinRM client cannot process the request. If the authentication scheme is different from Kerberos, or if the client computer is not joined to a domain, then HTTPS transport must be used or the destination machine must be added to the TrustedHosts configuration setting. Use winrm.cmd to configure TrustedHosts. Note that computers in the TrustedHosts list might not be authenticated. You can get more information about that by running the following command: winrm help config.
Error exception: Microsoft.Management.Infrastructure.CimException: The WinRM client cannot process the request. If the authentication scheme is different from Kerberos, or if the client computer is not joined to a domain, then HTTPS transport must be used or the destination machine must be added to the TrustedHosts configuration setting. Use winrm.cmd to configure TrustedHosts. Note that computers in the TrustedHosts list might not be authenticated. You can get more information about that by running the following command: winrm help config.
   at Microsoft.Management.Infrastructure.Internal.Operations.CimAsyncObserverProxyBase`1.ProcessNativeCallback(OperationCallbackProcessingContext callbackProcessingContext, T currentItem, Boolean moreResults, MiResult operationResult, String errorMessage, InstanceHandle errorDetailsHandle)
Failing script: C:UsersdekibDocumentsWindowsPowerShellModules3commonGetCPUInfo.ps1
Failing at line number: 214
Failing at line:             $CPUInfos = Get-CimInstance @params | 

Powershell command path: C:UsersdekibDocumentsWindowsPowerShellModules3commonGetCPUInfo.ps1
Position message: C:UsersdekibDocumentsWindowsPowerShellModules3commonGetCPUInfo.ps1
Stack trace: at Get-CPUInfo, C:UsersdekibDocumentsWindowsPowerShellModules3commonGetCPUInfo.ps1: line 214
at , : line 1
------------------------------------------------------------------------------------------------------------

TIP: If your scripts are scheduled in Task Manager the best practice is to have a routine of regularly checking the Error Log file and investigate the errors that occurred since the last check. I am doing this once a week.

Errors In PowerShell

There are two types of Errors in PowerShell:

  • Terminating
  • Non-Terminating

Terminating Errors

Here are the important features of Terminating errors:

  • terminates execution of command or script
  • triggers Catch block and can be error handled by the Catch block.

Examples are syntax errors, non-existent CmdLets, or other fatal errors

Non-Terminating Errors

Here are important features of Non-Terminating Error:

  • A non-fatal error.
  • Allows execution to continue despite the failure that just occurred.
  • It doesn’t trigger the Catch block and cannot be Error Handled in the Catch block by default.

Examples are permission problems, file not found, etc.

How To Force Non-Terminating Errors Into Terminating

Use the ErrorAction parameter with value Stop to force non-terminating error into terminating as in the following example. The reason why we want to make non-termination error into terminating one is to be able to catch the error when occurs.

$AuthorizedUser = Get-Content .DocumentsWindowsPowerShellProfile.ps1 -ErrorAction Stop 

Basically, the workflow is as follows.

  • When an error occurs,
  • a non-terminating error has changed into terminating one since we have Stop value on ErrrorAction parameter,
  • then since terminating error has occurred try block will send the error handling to catch block where error can be processed and
  • optionally written to the external log,
  • optionally error handling can continue in the final block.

NOTE: ErrorAction parameter overrides temporarily ErrorActionPreference variable while the call to CmdLet has been processed.

How To Treat All Errors As Terminating

We use the ErrorActionPreference variable to treat all errors as terminating by setting to the value Stop in the:

  • script
  • or session

Write the following line of code at the begging of the script to treat all errors as terminating.

$ErrorActionPreference = Stop

Type in Windows PowerShell Console the same command to setup terminating errors for the session.

ErrorAction Common Parameter

ErrorAction parameter belongs to the set of common parameters that we can use with any CmdLet. If we set CmdLetBinding on Advanced Functions than PowerShell automatically makes common parameters available for that command.

This is a parameter that I always use with CmdLets that need error handling since the ErrorAction Parameter determines how the CmdLet responds to a non-terminating error. It has several values that we will talk about in a minute but the value that I like to use is Stop and it is used to make non-terminating errors into terminating errors as written in previous sections.

The ErrorAction parameter overrides the value of the $ErrorActionPreference variable when applied to the specific command.

Here are valid values:

  • Continue (Default)
    • This is the default setting. Display error then continues execution.
  • Stop
    • Display error, and stop the execution.
  • Inquire
    • Displays error message and the user is asked to continue with execution.
  • SilentlyContinue
    • No error message is displayed and execution is continued. However, the error message is added to the $Error automatic variable.
  • Ignore
    • The same as SilentlyContinue, No error message is displayed and execution is continued. However, Ignore does not add an error message to the $Error automatic variable.
  • Suspend
    • This one is for workflows. A workflow job is suspended to investigate what happened, then the workflow can be resumed.

$ErrorActionPreference Preference Variable Explained

$ErrorActionPreference preference variable determines how Windows PowerShell responds to a non-terminating error (an error that does not stop the cmdlet processing) in a script, cmdlet or at the command line

If we want to override the value of the ErrorActionPreference preference variable for the specific command we use the ErrorAction common parameter as explained here.

The valid values for $ErrorActionPreference preference variable are:

  • Continue (Default)
    • This is the default setting. Display error then continues execution.
  • Stop
    • Display error message, and stop the execution.
  • Inquire
    • Displays error message and the user is asked to continue with execution.
  • SilentlyContinue
    • No error message is displayed and execution is continued. However, the error message is added to the $Error automatic variable.
  • Suspend
    • This one is for workflows. A workflow job is suspended to investigate what happened, then the workflow can be resumed.

Error Handling With Try/Catch/Finally Blocks

Try, Catch, and Finally, blocks are used to handle terminating errors in the scripts, functions, and CmdLets. A non-terminating error does not trigger Try block and Windows PowerShell will not look for Catch block to handle the error. So we need to force a non-terminating error to become terminating error using ErrorAction parameter with value Stop whenever we call some CmdLet or Advanced function.

Try block is used as part of the code that PowerShell will monitor for errors. The workflow in the Try block is as follows:

  • Try block is the section of code that will be monitored for errors by Windows PowerShell.
  • When the error occurs within Try block the error is saved to $Error automatic variable first.
  • Windows PowerShell searches for a Catch block to handle the error if the error is terminating. If the Catch block has not been found in current scope Windows PowerShell will search for catch block in parent scopes for nested calls.
  • Then the Finally block is run if exists.
  • If there is no Catch block than the error is not handled and the error is written to the error stream.

One Try block can have several Catch Blocks that will handle different error types.

Catch block usually handles the error.

The Finally block is optional and can be only one. Usually, it is used to clean up and free the resources.

The syntax for Try, Catch, and Finally block:

try { } 
catch [],[]
   { } 
catch { } 
finally { }

Getting Error Information With $Error Automatic Variable

$Error automatic variable is an array of error objects (both terminating and the non-terminating) that occurred in the current PowerShell session. The most recent error that occurred is written with index 0 in the array as $Error[0]. If we have just opened the Windows PowerShell session the $Error variable is an empty array and ready to be used.

Check the number of Errors in $Error variable with the following code:

$Error.Count

To prevent the error from being written in $Error automatic variable set ErrorAction parameter to value Ignore.

$Error variable is a rich object that has many useful properties worth reading and helpful for further understanding of the error that just occurred.

Let’s see some useful properties and in section Write-ErrorLog CmdLet Explained I have explained to you some useful examples of properties that are interesting to be written in an external log file.

$error[0] | Get-Member
Methods and Properties of the Error object
$Error.CategoryInfo | Get-Member
Methods and Properties of CategoryInfo object
$Error[0].Exception
The exception of the error that occurred.
$Error.InvocationInfo | Get-Member
Methods and Properties of InvocationInfo object

Write-Error CmdLet

Write-Error CmdLet writes an object to the error stream.

Please read this article from Microsoft PowerShell documentation regarding this CmdLet.

Handling Errors from non-PowerShell processes

We can run applications from PowerShell script like for example, PsExec.exe or robocopy.exe and they are external processes for PowerShell. Since it is an external process, errors from it will not be caught by our try/catch blocks in PowerShell script. So how we will know whether our external process was successful or not.

Well, we can use the $LastExitCode PowerShell automatic variable.

PowerShell will write the exit code to the $LastExitCode automatic variable when the external process exits. Check the external tool’s documentation for exit code values but usually, 0 means success and 1 or greater values mean a failure.

Useful PowerShell Error Handling Articles

Here are some useful articles and resources:

  • Windows PowerShell Error Reporting
  • About Try Catch Finally
  • About CommonParameters
  • About Automatic Variables
  • About Preference Variables
  • Write-Error
  • About Throw
  • About Break
  • About Continue

Содержание

  1. Используем лог-файлы для скриптов PowerShell
  2. How To Log PowerShell Errors And Much More
  3. Share this post:
  4. Why Should We Bother Handling Errors In PowerShell
  5. How To Handle And Log PowerShell Errors
  6. Example Of PowerShell Error Handling
  7. Chain Of Events When PowerShell Error Occurs
  8. How To Write PowerShell Errors Into The External Log File
  9. Write-ErrorLog CmdLet Code
  10. Write-ErrorLog CmdLet Explained
  11. $PSItem or $_
  12. How To Treat All Errors As Terminating
  13. ErrorAction Common Parameter
  14. $ErrorActionPreference Preference Variable Explained
  15. Error Handling With Try/Catch/Finally Blocks
  16. Getting Error Information With $Error Automatic Variable
  17. Write-Error CmdLet
  18. Handling Errors from non-PowerShell processes
  19. Useful PowerShell Error Handling Articles
  20. Share this post:
  21. Recent Posts
  22. About Dejan Mladenović
  23. LEGAL INFORMATION
  24. About Dejan Mladenović

Используем лог-файлы для скриптов PowerShell

Вы можете использовать простые текстовые лог файлы для контроля запуска и отслеживания всех действий, которые выполняются при запуске PowerShell скриптов. Это удобно при отладке ошибок, для аудита выполненных скриптом действий. В этой статье мы рассмотрим несколько способов ведения текстовых лог-файлов для ваших PowerShell скриптов.

В самом простом случае, если вам нужно записать вывод информационного сообщения или результатов определенной PowerShell команды в текстовый лог файл, вы можете использовать один из следующих форматов перенаправления вывода в txt файл:

Write-Output «Файлы созданы успешно на $env:computername» >> C:PSLogsTestLog.txt

Add-Content -Path C:PSLogsTestLog.txt -Value «Файлы созданы успешно на $env:computername»

«Файлы созданы успешно на $env:computername» | Out-File -FilePath C:PSLogsTestLog.txt –Append

Во всех случаях команды добавляют в указанный текстовый файл новую строку c указанным вами текстом.

Главный недостаток такого метода – по такому лог файлу нельзя определить, когда была внесена та или иная запись в лог (произошло событие). Вы можете добавить в лог текущую метку даты, времени (timestamp). Это поможет легко идентифицировать время запуска скрипта и конкретного события в нем.

Для удобства можно создать в PowerShell скрипте отдельную функцию, которая будет сохранять переданные ей данные в лог файл и добавлять время записи информации.

Можно сделать функцию:

$Logfile = «C:PSLogsproc_$env:computername.log»
function WriteLog
<
Param ([string]$LogString)

Теперь, чтобы записать что-то в лог файл, вам нужно вызвать функцию WriteLog.

WriteLog «Скрипт запущен”
WriteLog «Выполняю вычисления….»
Start-Sleep 20
WriteLog «Скрипт выполнен успешно»

Теперь в лог файле содержится время, когда была произведена запись.

В PowerShell также есть встроенная возможность сохранять в текстовый лог файл все команды и результаты, которые выводятся в консоль PS.

Чтобы начать запись текущей PowerShell сессии, используется командлет Start-Transcript.

После запуска этой команды появляется сообщение, в котором указано, в какой файл сохраняются результаты всех команд. По умолчанию лог файл пишется в профиль текущего пользователя:

Start-Transcript -Append C:PSLogsPSScriptLog.txt

Параметр –Append указывает, что нужно дописывать новые сессию в конец лог файла (не перезатирать его).

Выполните несколько PowerShell команд, которые выводят результаты в консоль. Например, выведем список тяжелых запущенных процессов, запущенных служб и состояние репликации в AD:

Get-Process| where-object <$_.WorkingSet -GT 300000*1024>|select processname,@> |sort «Used RAM(MB)» –Descending
Get-Service | Where-Object <$_.status -eq ‘running’>
Get-ADReplicationFailure -Target DC01

Завершите запись сессии для текущей сессии:

Теперь откройте текстовый файл с логом.

Как вы видите, в текстовом логе отображаются вся история PowerShell команд, которые запускались в скрипте и весь вывод, который выводился в консоль.

Вы можете использовать Start-Transcript/Stop-Transcript в своих PowerShell скриптах чтобы нативно логировать все действия и результаты.

Источник

How To Log PowerShell Errors And Much More

Error Handling is a very important concept in every programming language including PowerShell which gives us several possibilities to manage errors in code.

In this article, I will try to cover as many as possible error handling concepts with examples from my practice and one of the important concepts is writing errors in external Error Log text file.

Table of Contents

Why Should We Bother Handling Errors In PowerShell

It is no fun to run any code or application full of errors and bugs as the matter a fact it is quite annoying so in order for users to have a pleasant experience handling the errors is one of the essentials in programming.

PowerShell is no exception to that way of thinking and programming ethics although it can take some additional coding and effort but trust me it is always worth it.

I would be more than happy to share my experience with error handling and even more satisfied if I can hear your tips from the field so we can all enrich our knowledge on this very important subject.

How To Handle And Log PowerShell Errors

Here is a general approach to error handling and logging errors into the external text file. I have an example in the next subheading that further explain each step so everything is much more understandable.

  1. Identify which Commands need Error Handling in your Function, CmdLet or Script.
  2. Set the ErrorAction parameter to value Stop for the command that needs Error Handling. This will stop executing the command when an error occurs and it will show the error message. Basically, we are making non-terminating errors into terminating in order to be able to handle the error in the catch block.
  3. Command that has ErrorAction parameter set to value Stop is wrapped in Try block. So when Error occurs handling of the code will jump from the Try block to Catch block.
  4. In Catch < >block that immediately follows after Try <> block, the error is handled by writing to the error log file on the disk.
  5. We use our own Write-ErrorLogCmdLet inside Catch < >block to write the error in a text file on the disk. (See the explanation and code here)
  6. We have an additional switch error parameter to decide whether we want to write an error in the log file or not. This is totally optional.
  7. Use the Finally block if needed.
  8. Test the whole setup by intentionally breaking the code while in the development phase.
  9. Since some of the CmdLets calls are Scheduled we have routine to check external Error Log text file at least once a week and investigate errors that are caught. This step is part of improving the overall quality of the written code.

Example Of PowerShell Error Handling

To show you Error Handling and implement previously defined steps I will use my own Get-CPUInfo CmdLet which is in the Common module of the Efficiency Booster PowerShell Project. Efficiency Booster PowerShell Project is a library of CmdLets that help us IT experts in day to day IT tasks.

In order to follow me along, I highly encourage you to download the zip file with the source code used in this example.

Here is the location of Get-CPUInfo script:
…[My] DocumentsWindowsPowerShellModules3common

Let’s use steps defined in the previous subheading to this example.

Step 1. I have identified the command that needs Error Handling in Get-CPUInfo CmdLet and that is a call to Get-CimInstance CmdLet.

Step 2. So I have set up the ErrorAction parameter to the value ‘Stop‘ for Get-CimInstance CmdLet in order to force non-terminating errors into terminating and then to be able to handle such errors.

INFO: I use parameter splatting when running CmdLet. If you want to know more about parameter splating please read this article.

Step 3. Wrap up the call to Get-CimInstance CmdLet into the Try Block in order to be able to handle the error in a catch block that follows.

Step 4. When the error occurs in the try block it is handled in the Catch Block.
It is important to notice following in the catch block of code:

  • Get-CPUInfo CmdLet switch parameter $errorlog has been used to decide whether to log the errors in an external text file or not. This is completely optional.
  • Certain Error properties are collected using an automatic variable $_ ($PSItem is another name for the same variable). If you want to know more about which properties we collect please read here.
  • Collected data about the error that will be handled has been passed to another CmdLet Write-ErrorLog that will write the data in an external text log file. Please read here about Write-ErrorLog CmdLet.

Step 5. I have already mentioned that Write-ErrorLog CmdLet has been used to write the error data into an external text log file. Read more about this CmdLet here.

Step 6. I did not need Finally block for this example.

Step 7. In the development phase, I was intentionally breaking Get-CPUInfo CmdLet to test my error handling code.

Step 8. If Get-CPUInfo CmdLet is part of the scheduled code I would look regularly Error log file and work on correcting all the bugs in the code produced by Get-CPUInfo CmdLet.

Chain Of Events When PowerShell Error Occurs

Let’s talk a little bit about what is happening when an error occurs in PowerShell and which events are triggered one after another.

  • Call to CmdLet is failing and error has just occurred.
  • Since we have ErrorAction parameter set to value Stop our non-terminating error has forced into terminating error so the execution of code stops.
  • The error that is failing in the CmdLet is written in the $Errorautomatic variable by the PowerShell.
  • We have forced the error to be terminating in order to be able to handle with try catch finally block of error handling.
  • Since our CmdLet call is wrapped in Try block and error is terminating PowerShell can trigger error handling looking for a Catch block.
  • We can have several Catch blocks for one try block. If we have a Catch block that handles the actual error number that block is executed.
  • Otherwise, PowerShell will look Catch block that handles all error numbers.
  • Optionally in the Catch block, we can have code that will write the error in the external text Error log file.
  • We can use the automatic variable $Error or $_ object to read Error data and write them in the external file.
  • If there is no Catch block PowerShell will look for Catch block in parent call if we have nested calls.
  • If there are no further Catch block or no Catch block at all that will handle error then PowerShell looks for Finally block to execute which is used to clean up resources as needed.
  • After executing Finally block error message will be sent to the error stream for further processing.

In further sections of this article, you can read in more detail about the many terms mentioned (ErrorAction, $Error, Try Catch Finally, Terminating, Non-Terminating, etc ) in this bulleted list in order to better understand them.

How To Write PowerShell Errors Into The External Log File

Here I will explain the code of Write-ErrorLog CmdLet that writes error data that occurs in CmdLets and handle the error data into an external text file.

Error data are written in the file named Error_Log.txt in folder PSlogs.

Write-ErrorLog CmdLet is part of the Efficiency Booster PowerShell Project and if you want to download the source code of this CmdLet please click here.

Here is the location of Write-ErrorLog script which is part of the Utils module:
…[My] DocumentsWindowsPowerShellModules2utils

Write-ErrorLog CmdLet Code

Here is the code of the whole Write-ErrorLog CmdLet.

Write-ErrorLog CmdLet Explained

Let’s make our hand’s a little bit “dirty” and dive into PowerShell code.

In the BEGIN block we:

  • Check if folder PSlogs exist in the (My) Documents folder of the current user.
    • If the PSlogs folder doesn’t exist then create the folder.
  • Check if file Error_Log.txt exists in the folder PSlogs.
    • If Error_Log.txt doesn’t exist then create the file.
  • Now we can move on to PROCESS block code.

In the PROCESS block:

  • We format the line of text that we want to write into the log file.
  • Then we pipe formatted text to Out-File CmdLet with the Append parameter to write that line of text in the file.
  • We repeat the process of formatting the line of text and appending of that line to the Error log file.

$PSItem or $_

$PSItem contains the current object in the pipeline object and we use it to read Error properties in this case.

Just a quick explanation of each Error property from the $_ ($PSItem) automatic variable that we collect and write in Logfile:

  • $_.ToString() – This is Error Message.
  • $_.Exception – This is Error Exception.
  • $_.InvocationInfo.ScriptName – This the PowerShell script name where Error occurred.
  • $_.InvocationInfo.ScriptLineNumber – This is line number within the PowerShell script where Error occurred.
  • $_.InvocationInfo.Line – This is the line of code within PowerShell script where Error occurred.
  • $_.InvocationInfo.PSCommandPath – This is the path to the PowerShell script file on the disk.
  • $_.InvocationInfo.PositionMessage – This is a formatted message indicating where the CmdLet appeared in the line.
  • $_.ScriptStackTrace – This is the Trace of the Stack.

As you can see on the screenshot below we collect really useful information about the Error that we handle in the Logfile. The pieces of information are presented in very neatly fashion so we can immediately see:

  • which error occurred,
  • what were the message and exception,
  • where the error occurred (script name, script location, line number and line of the code in the script)
  • even the call stack is shown if needed.

Here are the final result and an example of one formatted error logged in Error_Log.txt file.

Basically, the workflow is as follows.

  • When an error occurs,
  • a non-terminating error has changed into terminating one since we have Stop value on ErrrorAction parameter,
  • then since terminating error has occurred try block will send the error handling to catch block where error can be processed and
  • optionally written to the external log,
  • optionally error handling can continue in the final block.

NOTE: ErrorAction parameter overrides temporarily ErrorActionPreference variable while the call to CmdLet has been processed.

How To Treat All Errors As Terminating

We use the ErrorActionPreference variable to treat all errors as terminating by setting to the value Stop in the:

  • script
  • or session

Write the following line of code at the begging of the script to treat all errors as terminating.

Type in Windows PowerShell Console the same command to setup terminating errors for the session.

ErrorAction Common Parameter

ErrorAction parameter belongs to the set of common parameters that we can use with any CmdLet. If we set CmdLetBinding on Advanced Functions than PowerShell automatically makes common parameters available for that command.

This is a parameter that I always use with CmdLets that need error handling since the ErrorAction Parameter determines how the CmdLet responds to a non-terminating error. It has several values that we will talk about in a minute but the value that I like to use is Stop and it is used to make non-terminating errors into terminating errors as written in previous sections.

The ErrorAction parameter overrides the value of the $ErrorActionPreference variable when applied to the specific command.

Here are valid values:

  • Continue (Default)
    • This is the default setting. Display error then continues execution.
  • Stop
    • Display error, and stop the execution.
  • Inquire
    • Displays error message and the user is asked to continue with execution.
  • SilentlyContinue
    • No error message is displayed and execution is continued. However, the error message is added to the $Error automatic variable.
  • Ignore
    • The same as SilentlyContinue, No error message is displayed and execution is continued. However, Ignore does not add an error message to the $Error automatic variable.
  • Suspend
    • This one is for workflows. A workflow job is suspended to investigate what happened, then the workflow can be resumed.

$ErrorActionPreference Preference Variable Explained

$ErrorActionPreference preference variable determines how Windows PowerShell responds to a non-terminating error (an error that does not stop the cmdlet processing) in a script, cmdlet or at the command line

If we want to override the value of the ErrorActionPreference preference variable for the specific command we use the ErrorAction common parameter as explained here.

The valid values for $ErrorActionPreference preference variable are:

  • Continue (Default)
    • This is the default setting. Display error then continues execution.
  • Stop
    • Display error message, and stop the execution.
  • Inquire
    • Displays error message and the user is asked to continue with execution.
  • SilentlyContinue
    • No error message is displayed and execution is continued. However, the error message is added to the $Error automatic variable.
  • Suspend
    • This one is for workflows. A workflow job is suspended to investigate what happened, then the workflow can be resumed.

Error Handling With Try/Catch/Finally Blocks

Try, Catch, and Finally, blocks are used to handle terminating errors in the scripts, functions, and CmdLets. A non-terminating error does not trigger Try block and Windows PowerShell will not look for Catch block to handle the error. So we need to force a non-terminating error to become terminating error using ErrorAction parameter with value Stop whenever we call some CmdLet or Advanced function.

Try block is used as part of the code that PowerShell will monitor for errors. The workflow in the Try block is as follows:

  • Try block is the section of code that will be monitored for errors by Windows PowerShell.
  • When the error occurs within Try block the error is saved to $Error automatic variable first.
  • Windows PowerShell searches for a Catch block to handle the error if the error is terminating. If the Catch block has not been found in current scope Windows PowerShell will search for catch block in parent scopes for nested calls.
  • Then the Finally block is run if exists.
  • If there is no Catch block than the error is not handled and the error is written to the error stream.

One Try block can have several Catch Blocks that will handle different error types.

Catch block usually handles the error.

The Finally block is optional and can be only one. Usually, it is used to clean up and free the resources.

The syntax for Try, Catch, and Finally block:

Getting Error Information With $Error Automatic Variable

$Error automatic variable is an array of error objects (both terminating and the non-terminating) that occurred in the current PowerShell session. The most recent error that occurred is written with index 0 in the array as $Error[0]. If we have just opened the Windows PowerShell session the $Error variable is an empty array and ready to be used.

Check the number of Errors in $Error variable with the following code:

To prevent the error from being written in $Error automatic variable set ErrorAction parameter to value Ignore.

$Error variable is a rich object that has many useful properties worth reading and helpful for further understanding of the error that just occurred.

Let’s see some useful properties and in section Write-ErrorLog CmdLet Explained I have explained to you some useful examples of properties that are interesting to be written in an external log file.

Write-Error CmdLet

Write-Error CmdLet writes an object to the error stream.

Please read this article from Microsoft PowerShell documentation regarding this CmdLet.

Handling Errors from non-PowerShell processes

We can run applications from PowerShell script like for example, PsExec.exe or robocopy.exe and they are external processes for PowerShell. Since it is an external process, errors from it will not be caught by our try/catch blocks in PowerShell script. So how we will know whether our external process was successful or not.

Well, we can use the $LastExitCode PowerShell automatic variable.

PowerShell will write the exit code to the $LastExitCode automatic variable when the external process exits. Check the external tool’s documentation for exit code values but usually, 0 means success and 1 or greater values mean a failure.

Useful PowerShell Error Handling Articles

About Dejan Mladenović

Hey Everyone! I hope that this article you read today has taken you from a place of frustration to a place of joy coding! Please let me know of anything you need for Windows PowerShell in the comments below that can help you achieve your goals!
I have 18+ years of experience in IT and you can check my Microsoft credentials. Transcript ID: 750479 and Access Code: DejanMladenovic
Credentials
About Me.

Hey Everyone! I hope that this article you read today has taken you from a place of frustration to a place of joy coding! Please let me know of anything you need for Windows PowerShell in the comments below that can help you achieve your goals! I have 18+ years of experience in IT and you can check my Microsoft credentials. Transcript ID: 750479 and Access Code: DejanMladenovic
Credentials About Me.

Recent Posts

This article is a step up from our previous article about writing PowerShell Functions. We will show you that converting your existing PowerShell Functions into Advanced PowerShell Functions is.

Writing PowerShell Function is not difficult but for some reason, some people are uncomfortable writing them. Do not worry this article will show you easy steps with examples so you can write your.

About Dejan Mladenović

Hello I’m Dejan Mladenović

Over the years I have used PowerShell for many of my every day IT tasks to be more efficient so I would like to share my Windows PowerShell scripting experience with you.

If you find a post helpful and feel like rewarding my effort with the price of a cup of coffee, please donate here!

LEGAL INFORMATION

This site is a participant in the Amazon Services LLC Associates Program, an affiliate advertising program designed to provide a means for sites to earn advertising fees by advertising and linking to Amazon.com.

We are compensated for referring traffic and business to Amazon and other companies linked to on this site.

About Dejan Mladenović

Hello, if you find PowerShell’s post helpful and feel like rewarding my effort with the price of a cup of coffee, please donate here!

Источник

SharePoint Online training courses

In this PowerShell tutorial, we will discuss how to create a log file in PowerShell. We use log files to track information while running a PowerShell script.

If you are a little familiar with PowerShell, then you might realize we use PowerShell for simple to complex and to complete a long time-consuming task. Your PowerShell script might be also running automatically through a scheduler.

So it is very much necessary that, we should tack information while the script is running and that when we use log files. We can track information, error, warning, etc on a log file in PowerShell.

But you should not log sensitive information or personal data like password, credit card numbers, email address, social security numbers etc.

So let us see different ways to create a log file in PowerShell.

We will discuss here different approaches here to write information to a log file as well as how to create PowerShell log file.

I always use PowerShell ISE to write, test, and debug PowerShell scripts. You can use the Visual Studio Code also.

Open PowerShell ISE in administrator mode (Right-click and Run as administrator) and go through the examples step by step.

Example-1: PowerShell create simple log file

This is one of the simplest way to write a message to a log file in PowerShell.

$logfilepath="D:Log.txt"
$logmessage="This is a test message for the PowerShell create log file"
$logmessage >> $logfilepath

The above PowerShell script will write the message in the Log.txt file that is presented in D drive.

If the file is presented before then it will keep appending the message like by like. And if the Log.txt file is not presented in the D drive, then it will create the log file first and then write the message.

Example-2: Create simple log file in PowerShell

The below example will check if the log file is presented already, then it will delete the file and create a new file and write the message into it.

$logfilepath="D:Log.txt"
$logmessage="This is a test message for the PowerShell create log file"
if(Test-Path $logfilepath)
{
    Remove-Item $logfilepath
}
$logmessage >> $logfilepath

We can use the Test-Path PowerShell command to check if a file exists or not in the file path.

Example-3:

We can also use the date and time when the message is logged using PowerShell Get-Date.

$logfilepath="D:Log.txt"
$logmessage="This is a test message for the PowerShell create log file"
if(Test-Path $logfilepath)
{
    Remove-Item $logfilepath
}
$logmessage +" - "+ (Get-Date).ToString() >> $logfilepath

The above PowerShell script will log the message with the current date and time.

powershell create log file
powershell log to file

Example-4:

If you want to track multiple log messages, then you can create a PowerShell function like below:

$logfilepath="D:Log.txt"

function WriteToLogFile ($message)
{
$message +" - "+ (Get-Date).ToString() >> $logfilepath
}

if(Test-Path $logfilepath)
{
    Remove-Item $logfilepath
}

WriteToLogFile "This is a test message for the PowerShell create log file"
WriteToLogFile "This is another message"
WriteToLogFile "This is another another message"

The above PowerShell script will check if the log file exists in the path, if yes then it will delete the file. And then it will call 3 times the function to write the messages to the PowerShell log file.

Example-5:

Below is a PowerShell script where we have used the Add-content PowerShell cmdlets to write the message to a .log file.


$logfilepath = "D:Logs.log"

function WriteToLogFile ($message)
{
   Add-content $logfilepath -value $message
}
if(Test-Path $logfilepath)
{
    Remove-Item $logfilepath
}

WriteToLogFile "This is a test message for the PowerShell create log file"
WriteToLogFile "This is another message"
WriteToLogFile "This is another another message"

Rather using a .txt file, most of times we write the extension as .log file.

Example-6:

We can also use Start-Transcript PowerShell cmdlets to write to a log file. This not only writes the log message but also tracks other information like username, machine, host application, PSVersion, CLRVersion, etc.

$logfilepath="D:Log.log"

Start-Transcript -Path $logfilepath

Write-Host "This is a message"

Stop-Transcript

The file looks like below:

create log file in powershell
powershell write to log file

PowerShell Log Levels

In the Write- PowerShell cmdlets, PowerShell provides various types of log levels like:

  • Write-Verbose
  • Write-Debug
  • Write-Information
  • Write-Warning
  • Write-Error

PowerShell create log file with date

Now, let us see how to create log file with date in PowerShell. Basically, it will create the file with the file name as of today’s date.

The script will check if any file exists with name as today’s date, if the file exists, then it will delete will first delete the file, else it will create the file and write the log message into it.

$todaysdate=Get-Date -Format "MM-dd-yyyy"

$logfilepath = "D:"+$todaysdate+".log"

function WriteToLogFile ($message)
{
   Add-content $logfilepath -value $message
}
if(Test-Path $logfilepath)
{
    Remove-Item $logfilepath
}

WriteToLogFile "PowerShell log file message"
WriteToLogFile "This is another message"
WriteToLogFile "This is another another message"

This is how we can create log file with date in PowerShell.

This is how we can create a daily log file in PowerShell that will be created on today’s date.

PowerShell create log file with datetime

Now, we can see how to create a log file with datetime using PowerShell.

We need to format the Get-Date PowerShell cmdlet to get the current date and time like below:

$todaysdate=Get-Date -Format "MM-dd-yyyy-hh-mm"

$logfilepath = "D:"+$todaysdate+".log"

function WriteToLogFile ($message)
{
   Add-content $logfilepath -value $message
}
if(Test-Path $logfilepath)
{
    Remove-Item $logfilepath
}

WriteToLogFile "PowerShell log file message"

In the above example the PowerShell log file will be created on the name as today’s date and time.

PowerShell create log file if not exists

Now, let us see how to create a log file if not exists in PowerShell.

We can use the Test-Path PowerShell cmdlet to check if the file exists in the path or not.

And we can use New-Item PowerShell cmdlets to create a file in the folder path.

$todaysdate=Get-Date -Format "MM-dd-yyyy"
$folderpath="D:"
$filename = $todaysdate+".log"

if (!(Test-Path $logfilepath))
{
New-Item -itemType File -Path $folderpath -Name $filename
}

You can see the output like below:

PowerShell create log file if not exists
PowerShell create log file if not exists

This is how we can create a log file if not exists in PowerShell.

PowerShell create log file with headers

Now, let us see how to create a log file with headers in PowerShell. Here we will create a .csv file and also will add headers to the CSV file.

$filename = "Log-"+ (Get-Date -Format "MM-dd-yyyy")
$outfile = "D:"+$filename+".csv"

$newcsv = {} | Select "DateAndTime","Message" | Export-Csv $outfile

$csvfile = Import-Csv $outfile
$csvfile.DateAndTime = Get-Date -Format "MM-dd-yyyy-hh-mm"
$csvfile.Message = "This is a test message"

$csvfile | Export-CSV $outfile

The above PowerShell script will create a .csv file and then it will add two headers (DateAndTime and Message). We will add a test log message also.

PowerShell create error log file

Now, we will see how to create an error log file in PowerShell. Basically, we will use the try-catch statement and in the catch statement, we will log the error message in a log file in PowerShell.

$filename = "Log-"+ (Get-Date -Format "MM-dd-yyyy")
$logfile = "D:"+$filename+".log"

try
{
1/0
}
catch
{
$_.Exception.Message | Out-File $logfile
}

In the above PowerShell script, whenever an error will appear in the try statement, then the message will be logged in the catch statement in the PowerShell error log file.

PowerShell create csv log file

In the same way, we are creating .txt or .log file, we can also create .csv file in PowerShell to log information.

Now, let us see how to create a .csv log file in PowerShell.

To create a .csv file, we just need to change the file extension to .csv.

$todaysdate=Get-Date -Format "MM-dd-yyyy"

$logfilepath = "D:"+$todaysdate+".csv"

function WriteToLogFile ($message)
{
   Add-content $logfilepath -value $message
}
if(Test-Path $logfilepath)
{
    Remove-Item $logfilepath
}

WriteToLogFile "PowerShell log file message"

This is how we can create csv log file in PowerShell.

Use PowerShell Framework (PSFramework) to Log Information

You can also use PSFramework or PowerShell Framework to log information.

To use PSFramework, the first thing you need to install it by running the following command.

Install-Module PSFramework

By using the PSFramework, we can write to the log file by using the Write-PSFMessage script. Check out moe on PSFramwwork site.

PowerShell create simple log file

Now, let us see how to create a simple log file in PowerShell. The PowerShell script will create a very simple log file and will log a text message. The file will be created on today’s date.

$filename = "Log- "+ (Get-Date -Format "MM-dd-yyyy")
$logfilepath = "C:"+$filename+".log"
New-Item -Path $logfilepath  -ItemType file
Add-Content -Path $logfilepath -Value "This is a text file message"

You may like the following PowerShell tutorials:

  • How to loop through a PowerShell array
  • How to create and use PowerShell ArrayList
  • How to create an array in PowerShell from CSV file
  • What is PowerShell Array
  • How to use PowerShell reference variable
  • What is a PowerShell variable
  • SharePoint 2013 backup and restore using PowerShell
  • The term ‘Get-MsolUser’ is not recognized as the name of a cmdlet

In this tutorial, we learned how to create a log file in PowerShell and the below things:

  • PowerShell create log file
  • PowerShell Log Levels
  • PowerShell create log file with date
  • PowerShell create log file with datetime
  • PowerShell create log file if not exists
  • PowerShell create log file with headers
  • PowerShell create error log file
  • PowerShell create csv log file
  • Use PowerShell Framework (PSFramework) to Log Information
  • PowerShell create simple log file
  • powershell write to log file
  • powershell log to file
  • create log file powershell
  • powershell logging to file

Bijay Kumar

I am Bijay a Microsoft MVP (8 times – My MVP Profile) in SharePoint and have more than 15 years of expertise in SharePoint Online Office 365, SharePoint subscription edition, and SharePoint 2019/2016/2013. Currently working in my own venture TSInfo Technologies a SharePoint development, consulting, and training company. I also run the popular SharePoint website EnjoySharePoint.com

In our “Getting Started with Logging” series, we’ve already covered logging for many languages and frameworks: C#, Java, Python, Ruby, Ruby on Rails, Node.js, Symfony, Kotlin, Flask, Angular, and Laravel. Today, we’ll add another one to this series: PowerShell.

It might be an odd one to add to the list; PowerShell is a scripting language, while the others can be seen as full application programming languages or frameworks. Yet PowerShell is so sufficiently powerful that it can create systems complex and important enough to warrant logging.

PowerShell is often used to perform critical maintenance tasks on computers or servers. It’s important for admins to know how if these scripts ran well or not.

We’ll look into how you can log with PowerShell in a very basic way by writing your own logging function. Then we’ll cover why logging from PowerShell is useful. We’ll also talk about what you should log and what you should not log. Finally, we’ll look at two different options that will make your logging lives easier.

Powershell angle bracket and underscore with scalyr logo signifying powershell logging

A Simple PowerShell Script

Let’s start with this extremely simple PowerShell script:

param (
    [Parameter(Mandatory=$true)][int]$number
)

$digits = $number.ToString().ToCharArray()
$sum = 0

While ($digits.Length -ne 1) {
    $sum = 0
    $digits | ForEach { $sum += [int]$_.ToString() }
    $digits = $sum.ToString().ToCharArray()
}

Write-Output $digits

This basic script takes an integer as input. It then makes the sum of the digits of that number. If the result is longer than a single digit, it performs the operation again, until this results in a single digit.

Here are some examples to clarify things:

  • 45 -> 4 + 5 = 9
  • 397 -> 3 + 9 + 7 = 19 -> 1 + 9 = 10 -> 1 + 0 = 1
  • 3 -> 3

Put this script in a text file and save it as recursiveSum.ps1. Then, you can run it at the PowerShell command line by running recursiveSum.ps1 45, for example. The output will be 9:

This is a script that is simple enough to function as an example but has enough steps that we could be interested in some logging. Of course, real-life PowerShell scripts will be much more complex, warranting the need for logging even more.

The Easiest Way to Log in PowerShell

Let’s write the result of each step to the console. We’re already writing something to the console by using the Write-Output statement, so we can easily use that again. Change the While loop to include such a statement:

While ($digits.Length -ne 1) {
    $sum = 0
    $digits | ForEach { $sum += [int]$_.ToString() }
    $digits = $sum.ToString().ToCharArray()
    Write-Output "Intermediate result: $($sum)"
}

When you run it now, you should be able to see an output like this:

That’s great! We have some logging going on. However, these log statements will always be visible. In a more complex program, it might make sense to show nothing unless the user wants to see the logging. It’s probably also useful to give the user some control over the amount of detail they see. This is where log levels come in.

Log Levels in PowerShell

PowerShell has some Write-* statements that map to traditional log levels like:

  • Write-Verbose
  • Write-Debug
  • Write-Information
  • Write-Warning
  • Write-Error

Verbose

Let’s look at Write-Verbose first. We can change our messages about the intermediate steps to use Write-Verbose. When we run our script now, we won’t see our intermediate steps:

But if we add the -Verbose flag, our logs appear once again:

Debug, Information, and Warning

The Debug, Information, and Warning log levels work a little different than we’re used to from traditional logging frameworks in other languages. Let’s add some extra statements first:

Write-Debug "This is a debug statement"
Write-Information "This is an info statement"
Write-Warning "This is a warning statement"

The Write-Debug will only be visible when you add the -Debug flag. It will also stop your script from running and ask for confirmation to continue. This is useful to run through your scripts step by step.

But you do have some control over whether or not to show warnings and information messages. The Write-Warning and Write-Information commands can be controlled by using the -WarningAction and -InformationAction flags. If we omit these flags, only the warning messages are shown:

If we want to show the information messages, we can do so by adding -InformationAction Continue flag:

And if we want to hide the warnings, we can use the -WarningAction SilentlyContinue option:

Error

Finally, let’s look at how Write-Error works. We can add such a statement to our script:

Write-Error "Something went wrong"

When this statement is executed, PowerShell will stop the execution of our script and write the error to the console, along with some details that might be useful:

What Is Application Logging?

Let’s now take a step away from our PowerShell script and look at what application logging is exactly. The first post in our series defined it well:

Application logging involves recording information about your application’s runtime behavior to a more persistent medium.

We’re already doing the first part: in other words, capturing information about our application’s runtime behavior. We’re showing the intermediate results of our process. But we’re not doing the second part. We’re not recording this information to a persistent medium. When we close our PowerShell session, we’ve lost this information. Let’s do that first.

Logging to a File

We’ll add this Log function to our script file:

Function Log {
    param(
        [Parameter(Mandatory=$true)][String]$msg
    )
    
    Add-Content log.txt $msg
}

It simply takes in a String and then adds it to a log.txt file.

Then we can change our log statement so that it no longer uses Write-Verbose but this Log function instead:

Log "Intermediate result: $($sum)"

Now we can run our script and it will create a file containing our intermediate steps:

But if we run our script multiple times, it will just be a long list of statements. We have no way of knowing when the lines were logged. In a more complex scenario, containing multiple scripts, we also have no way of know where the log line originated—i.e., from which ps1 file. There’s more information that we could be logging.

Again, let’s take a step back and look at what we are trying to achieve with logging.

Why Do We Log?

PowerShell is often used to automate and script certain tasks that would otherwise be tedious and error-prone. Automation really starts to provide value when it is applied to more complex tasks. But these complex scripts might also have more chance of breaking: services may not respond, machine configurations may have changed, etc.

Without logging, it’s hard to know if scripts ran fine or if something went wrong. If something went wrong, logging will help us find out where and why it did. Application logging can also help you gather insights into which pieces of your scripts are being used most or where performance gains can still be made.

What to Log and What Not to: A Quick Detour Into Best Practices

Covering best practices at length would be out of the scope for this post. For starters, this post is but a basic introduction to logging. Also, we already have other posts dedicated to general logging best practices, such as the following ones:

  • Logging Best Practices: The 13 You Should Know About
  • Application Logging Practices You Should Adopt

However, we will cover two important aspects of logging best practices: namely, what you should and shouldn’t log. Even though when it comes to logging, the more the better, generally speaking, there are certain types of data you certainly must not log.

What Shouldn’t You Log?

When you write your logging statements, make sure you leave out sensitive or personal data. Examples include:

  • passwords and access tokens
  • credit card numbers or bank account numbers
  • social security numbers
  • email addresses
  • street addresses
  • encryption keys
  • social security numbers

The OWASP cheat sheet on logging has a section about data to exclude that is interesting to read. Although it is aimed at full-fledged applications, it could be useful for PowerShell scripts too. I can imagine scenario’s where PowerShell scripts handle email addresses or certain API keys.

Logging such personally identifiable information (or PII) is dangerous. Not only do you need explicit consent from the user in most countries, due to GDPR and other similar regulations, but if the data is ever leaked, you or your company could be held accountable. If it’s not really necessary, it’s better to leave this data out.

What Should You Log?

Our article on getting started with Ruby logging makes a great point:

You should think of a log entry as an eventsomething that’s of interest to your app that happened at some time.

This means that at a minimum, we need a timestamp and a description of the event. The description could also include relevant data. This is not always easy to determine upfront. Think about what you would need to troubleshoot a script that failed to run successfully. And if you later encounter a situation where you would have required more information to fix an issue, add it to the logging for next time.

Less obvious, but very useful, is to include a log level:

  • At the lowest level, Debug statements can be useful to track the executed flow of your PowerShell script.
  • A level up are Informational messages about the functional side of your script: what was executed, why, and with what data?
  • Then there’s also the level of Warnings: things that aren’t normal and potentially damaging but that don’t prevent the script from continuing.
  • Finally, Error-level messages are there to log errors that have made the script crash.

You can choose to log all these levels or only a specific level and up (for example, warnings and errors). Then, when you need more information, you can configure your script to also log more details on the next execution.

Our own simple logging function didn’t include log levels. Neither did it add a timestamp. We could add this functionality of course, but luckily others have already created decent logging scripts.

Enter the Logging Script

There is a simple yet useful script on Technet called Write-Log. If we download it, we can change our script to look like this:

param (
    [Parameter(Mandatory=$true)][int]$number
)

. C:UserspeterFunction-Write-Log.ps1

$logPath = "C:Userspeterlog.txt"
$digits = $number.ToString().ToCharArray()
$sum = 0

While ($digits.Length -ne 1) {
    $sum = 0
    $digits | ForEach { $sum += [int]$_.ToString() }
    $digits = $sum.ToString().ToCharArray()
    Write-Log -Message "Intermediate result: $($sum)" -Level Info -Path $logPath
}

Write-Output $digits

We’ve included the Function-Write-Log.ps1 file, removed our Log function, and replaced the call with a call to Write-Log. We can pass in a message, a level, and a path. The level can be Info, Warn, or Error. Notice that this script doesn’t have a Debug level, which is unfortunate. You could, however, easily modify it to include it, since the script is released under the MIT license allowing modification.

When we use this script, our log will look like this:

2021-07-22 09:18:29 INFO: Intermediate result: 51
2021-07-22 09:18:29 INFO: Intermediate result: 6

We now have clear log lines that include a timestamp, the log level, and our message. That’s much better than what we had previously. This may be sufficient for your needs, but let’s take it one step further and look at another option: the PSFramework.

Enter the Logging Framework

The PSFramework is a PowerShell framework that includes several useful utilities. 

PSFramework Core Concepts

Before diving into a hands-on demonstration of PSFramework, let’s take a step back and talk about some of the main concepts of the framework.

Message

A message is what is generated by your code and you want to log. Generally speaking, the message represents some relevant event you consider valuable to record.

Providers

Logging providers are one of the most important concepts in PSFramework. If you have experience with logging in other languages and logging tools, you might be familiar with the same concept under different names:

  • Appenders
  • Writers
  • Targets

These are just to name a few. They’re all the same: they represent possible destinations for your log messages.

In practical terms, you can think of PSFramework logging providers as plugins that know how to handle messages. A basic provider would be one that allows you to log to the filesystem. On the other hand, you could use a provider that allows you to log to Azure Log Analytics, for instance.

Providers Instances

In order to use a given provider, you’ll need a provider instance, which is a prepared “copy” of said provider—not that different from an instance of a class, in object-oriented programming.

Depending on your provider, you can have more than one instance alive simultaneously.

Installing PSFramework

To get started with the PSFramework, you will first need to install it by running Install-Module PSFramework in PowerShell. (You might have to run your PowerShell console as administrator). Then, you can write to the log files by calling the Write-PSFMessage script. In our case, it would look like this:

Write-PSFMessage -Level Output -Message "Intermediate result: $($sum)"

Notice how we’re not using an Info level. Instead, we’re using Output. This is because PSFramework uses log levels that match more nicely with that different output streams that PowerShell has by default: Verbose, Host, Output, Warning, Error, etc.

The log file will also look slightly different than what we’re used to:

"XPS-13-2021","22/07/2021 09:31:07","Output","Intermediate result: 28","Information, Debug","recursiveSum.ps1","<Unknown>","C:UserspeterrecursiveSum.ps1","29","","","c00b36b8-76bc-4251-9549-0f541742236f"
"XPS-13-2021","22/07/2021 09:31:07","Output","Intermediate result: 10","Information, Debug","recursiveSum.ps1","<Unknown>","C:UserspeterrecursiveSum.ps1","29","","","c00b36b8-76bc-4251-9549-0f541742236f"
"XPS-13-2021","22/07/2021 09:31:08","Output","Intermediate result: 1","Information, Debug","recursiveSum.ps1","<Unknown>","C:UserspeterrecursiveSum.ps1","29","","","c00b36b8-76bc-4251-9549-0f541742236f"

The column headers are missing, but that can easily be remedied by setting the logging provider first:

$logFile = Join-Path -path "C:Userspeter" -ChildPath "log-$(Get-date -f 'yyyyMMddHHmmss').txt";
Set-PSFLoggingProvider -Name logfile -FilePath $logFile -Enabled $true;

This log file will include a timestamp in the filename, and will now include a header row. This is because PSFramework’s default log provider will not add a header row, but the logfile provider will. The log file will now look like this:

"ComputerName","File","FunctionName","Level","Line","Message","ModuleName","Runspace","Tags","TargetObject","Timestamp","Type","Username"
"XPS-13-2021","C:UserspeterrecursiveSum.ps1","recursiveSum.ps1","Output","32","Intermediate result: 28","<Unknown>","c00b36b8-76bc-4251-9549-0f541742236f","",,"22/07/2021 09:43:05","Information, Debug","XPS-13-2021peter"
"XPS-13-2021","C:UserspeterrecursiveSum.ps1","recursiveSum.ps1","Output","32","Intermediate result: 10","<Unknown>","c00b36b8-76bc-4251-9549-0f541742236f","",,"22/07/2021 09:43:05","Information, Debug","XPS-13-2021peter"
"XPS-13-2021","C:UserspeterrecursiveSum.ps1","recursiveSum.ps1","Output","32","Intermediate result: 1","<Unknown>","c00b36b8-76bc-4251-9549-0f541742236f","",,"22/07/2021 09:43:05","Information, Debug","XPS-13-2021peter"

This now includes a lot of useful information like the filename of our script, the line where the logging occurred and the username of the user that is running the script. Another great advantage of the logging by PSFramework is that it logs asynchronously. The logging will not slow down your scripts. Finally, it will also automatically clean up your logs. By default, it will remove log files after seven days or when they exceed 100 megabytes.

A Comparison

The Function-Write-Log script matches more with what many application programmers are used to. It includes the standard log levels (except for Debug) and writes a log file like many logging frameworks do: a timestamp, the log level, and the message.

However, it misses some extra functionality that might be crucial to you: log clean up; asynchronous logging; and extra technical information like the script, line number, and executing user.

The PSFramework logging does provide these features, though it requires you to install the entire framework. This shouldn’t be a real issue, but it’s something to keep in mind. The PSFramework also uses different logging levels than we’re used to. However, they match closely to the PowerShell output streams. This may feel more natural to seasoned PowerShell users.

Summary and Next Steps

We’ve taken a look at a simple way to log in PowerShell. We also improved upon our own solution by using a better script (Function-Write-Log.ps1) and by using the logging included in PSFramework.

One thing that struck me was that logging in PowerShell is still fairly basic. It’s not that the options we looked at are bad or lacking. But I didn’t find a single logging script or module that includes multiple log levels, asynchronous logging, log clean up, and, most importantly, multiple log destinations.

What if we want to send errors to an email address? Or what if we want to log to a database? As far as I can see, PSFramework offers the most options here, but email or database aren’t included. It might be possible to write your own and register it in PSFramework, though.

This might be because the world of PowerShell is different that the world of application development. PowerShell scripts may be much more custom-made, meant for a specific scenario, and composed of multiple separate scripts.

Yet logging remains important, especially with scripts that run regularly and have a certain level of complexity. PowerShell is a powerful language to automate a multitude of tasks. When something goes wrong with such a task, you want to know what went wrong, what preceded it, and how to fix it. Logs give you this information.

Log Smarter and Stop Putting Out Fires!

Do you know what’s the biggest tragedy of logging is? It’s the fact that most organizations treat it as a mere troubleshooting mechanism.

Sure, logging is invaluable for understanding and fixing issues, there’s no denying that. Using the data from your log files to make your debugging and post-morten sessions easier is certainly way better than the alternative, which would be going blind.

But what if I told you that logging can be used not only to fix issues but even to prevent them altogether? What if I told you that’s possible to quit putting out fires and start stopping them from beginning?

That’s exactly the value proposition of log management. Log management is the process of collecting your logs, then extracting useful data from them. If that sounds underwhelming, here’s a list of only a few of the use cases for log management:

  • You can set up real time alerts and notifications. A great log management tool can analyze data from your logs, identify concerning trends, and then proactively notify the key people.
  • You get dashboards to visualize your environments in real time.
  • You get super fast search among log entries from your entire organization.

To sum it up, not using log management is pure waste. You have a treasure in the form of all the data inside your logs, and you doing a whole lot of nothing with it.

To leverage log management, you’ll need a tool, and Scalyr is a great one for this. Have look at the Scalyr API for the different options of sending log events to Scalyr. This can easily be called from PowerShell and will improve your ability of monitoring and troubleshooting your PowerShell scripts.

Вы можете использовать простые текстовые лог файлы для контроля запуска и отслеживания всех действий, которые выполняются при запуске PowerShell скриптов. Это удобно при отладке ошибок, для аудита выполненных скриптом действий. В этой статье мы рассмотрим несколько способов ведения текстовых лог-файлов для ваших PowerShell скриптов.

В самом простом случае, если вам нужно записать вывод информационного сообщения или результатов определенной PowerShell команды в текстовый лог файл, вы можете использовать один из следующих форматов перенаправления вывода в txt файл:

Write-Output "Файлы созданы успешно на $env:computername" >> C:PSLogsTestLog.txt

Add-Content -Path C:PSLogsTestLog.txt -Value "Файлы созданы успешно на $env:computername"

"Файлы созданы успешно на $env:computername" | Out-File -FilePath C:PSLogsTestLog.txt –Append

Во всех случаях команды добавляют в указанный текстовый файл новую строку c указанным вами текстом.

Если вам нужно каждый раз перезатирать содержимое ло-файла, используйте Set-Content.

Add-Content запись информации из скрипта powershell в лог

Главный недостаток такого метода – по такому лог файлу нельзя определить, когда была внесена та или иная запись в лог (произошло событие). Вы можете добавить в лог текущую метку даты, времени (timestamp). Это поможет легко идентифицировать время запуска скрипта и конкретного события в нем.

Для удобства можно создать в PowerShell скрипте отдельную функцию, которая будет сохранять переданные ей данные в лог файл и добавлять время записи информации.

Можно сделать функцию:

$Logfile = "C:PSLogsproc_$env:computername.log"
function WriteLog
{
Param ([string]$LogString)

$Stamp = (Get-Date).toString("yyyy/MM/dd HH:mm:ss")
$LogMessage = "$Stamp $LogString"
Add-content $LogFile -value $LogMessage
}

Теперь, чтобы записать что-то в лог файл, вам нужно вызвать функцию WriteLog.

WriteLog "Скрипт запущен”
WriteLog "Выполняю вычисления…."
Start-Sleep 20
WriteLog "Скрипт выполнен успешно"

Теперь в лог файле содержится время, когда была произведена запись.

функция ведения тектового лог файла с меткой времени в powershell

Можете заменить в своем скрипте вызовы Write-host на LogWrite.

В PowerShell также есть встроенная возможность сохранять в текстовый лог файл все команды и результаты, которые выводятся в консоль PS.

Чтобы начать запись текущей PowerShell сессии, используется командлет Start-Transcript.

После запуска этой команды появляется сообщение, в котором указано, в какой файл сохраняются результаты всех команд. По умолчанию лог файл пишется в профиль текущего пользователя:

Transcript started, output file is C:UsersAdministratorDocumentsPowerShell_transcript.DC01.inhP7egx.20210315041442.txt

Можно указать путь к текстовому файлу так:

Start-Transcript -Append C:PSLogsPSScriptLog.txt

Параметр –Append указывает, что нужно дописывать новые сессию в конец лог файла (не перезатирать его).

Выполните несколько PowerShell команд, которые выводят результаты в консоль. Например, выведем список тяжелых запущенных процессов, запущенных служб и состояние репликации в AD:

Get-Process| where-object {$_.WorkingSet -GT 300000*1024}|select processname,@{l="Used RAM(MB)"; e={$_.workingset / 1mb}} |sort "Used RAM(MB)" –Descending
Get-Service | Where-Object {$_.status -eq 'running'}
Get-ADReplicationFailure -Target DC01

Start-Transcript - ведение полного лога действий для powershell скрипта

Завершите запись сессии для текущей сессии:

Stop-Transcript

Теперь откройте текстовый файл с логом.

пример лога всех действий в powershell

Как вы видите, в текстовом логе отображаются вся история PowerShell команд, которые запускались в скрипте и весь вывод, который выводился в консоль.

В лог файл попадают в том числе все ошибки и предупреждения, что бывает крайне удобно при диагностике и отладке сложных PowerShell скриптов.

Вы можете использовать Start-Transcript/Stop-Transcript в своих PowerShell скриптах чтобы нативно логировать все действия и результаты.

С помощью групповой политики Turn on PowerShell Transcription в Computer Configuration –> Administrative Templates –> Windows Components –> Windows PowerShell можно включить автоматическое логирование всех запускаемых PowerShell команд и выводимых результатов на компьютере. После обновления настроек GPO на компьютере, для каждого запущенного процесса powershell.exe будет создаваться отдельный тектовый лог файл, куда будут записываться все PS команды и результаты результаты.

Групповая политика Turn on PowerShell Transcription

Important Update

As of September 2015, I have released version 2 of my PowerShell Logging solution, and is now known as PSLogging.

There has been a number of significant improvements with the most notable being that the PowerShell Logging function library has now been converted into a fully fledged PowerShell module. All of the details about the new version, the improvements and how to use and integrate the new version into your new and existing scripts can be found here – PSLogging PowerShell module.

Note: This version of the logging solution is no longer supported or maintained, so please upgrade to the new version today!


Today I thought I would share some thing that I have developed over the last year or so as I have been working more and more with PowerShell…. some standard functions that can simply be dot sourced and called at any time so that all my scripts have some awesome logs to go along with the awesome script.

Why have logging?

Well personally I feel that this is a very important part of a script or tool that is developed, particularly if it is going to be used regularly and provides some sort of valuable functionality.

Having a log that people can follow, and see what is happening while the script is executing or if the script runs into errors is very useful. Not only that, but I feel people will respect your developed scripts tools as they see it as a mature product, not some dodgey backyard operation that was just copied from the web and had the crap hacked through it.

The other reason is that it makes troubleshooting a hell of a lot easier… particularly 6 – 12 months down the track when lets say you are running the script in a different environment than what it was originally developed for. Having a log and being able to write out the result of each step of your script allows you to easily pin-point where the problem lies, especially if you pipe out error messages to the log (both custom or system errors).

Another great reason is having compassion on the guy the might be having to update or troubleshoot your script tool in the future. Its sort of like having well commented code (which I know everyone does… lol), it makes it easier for someone to be able to track your script and know what is happening and why its happening.

Finally, the reason why I personally like logging is because the way I layout all of my scripts is a seperate function per major task that the script completes. At the end of my scripts I then have an execution section where I call all of my functions in order and pass the required variables.

Using logging with this approach to development is great because it allows you to break your log into sections… each function has its own mini log section if you like, so it makes tracking the script really really easy and plus it looks awesome.

Logging Function Library: The contents

Becuase of the reasons above, I decided to create a logging function library which is essentially just a .ps1 file with a bunch of standard log functions. In each of my scripts I just dot source the .ps1 file and then call the each of the functions as required. The functions included in logging library are:

  • Log-Start: Creates the .log file and initialises the log with date and time, etc
  • Log-Write: Writes a informational line to the log
  • Log-Error: Writes an error line to the log (formatted differently)
  • Log-Finish: Completes the log and writes stop date and time
  • Log-Email: Emails the content of the log

Logging Function Library: Log Formatting

All of my scripts use this logging library and therefore all the logs look kinda the same. This is actually a good thing because it allows your user base to get used to a particular log format and so its just another incentive for them to use and enjoy your script that you worked so hard on.

Here is a screenshot of one of my logs that has been written using this logging function library

PowerShell Log File Example

Logging Function Library: The Code

Ok guys, enough rambling… here it is in all of its raw beauty:

Logging Function Library: Installation Instructions

Here are the instructions to start using this logging function library in your scripts:

  1. Copy the code above and save it in a new .ps1 file called Logging_Functions.ps1. Alternatively you can download the file from here: PowerShell Logging Function Library
  2. Copy the file and store it in a central location that everyone has access to (Note: This is very important otherwise your script will fail for people who don’t have access to the function library)
  3. In your script dot source the Logging_Functions.ps1 file. You should do this at the top of your PowerShell script and it should look something like this:
    
    . "C:ScriptsFunctionsLogging_Functions.ps1"
    
  4. Call the relevant functions as required (see the examples found in the meta information of each of the functions on how to do this)

PowerShell Script Template

If you are unsure on how to do step 4 above, or you would like to fill-out a standard template that have everything you need to create a new PowerShell script, then check out this post: PowerShell Script Template

If you have any problems using my PowerShell Logging Function Library then please let me know in the comments below or send me an email and I will try my hardest to get you all fixed up. Also, if you have any cool ideas or have some thoughts on how to improve the library then I am always intersted in hearing what others are doing and how I can improve myself.

Thanks guys and happy scripting 🙂

Luca


  • Remove From My Forums
  • Question

  • Hi,

    I am trying to create a error logger file, which will contain the list of errors when the powershell was run.

    I tried the below code, but it is not working:

    Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue
    clear
    foreach ($user in $SPWeb.AllUsers){
    $SPWeb = Get-SPWeb "Site collection"
    #other powershell commands
    
     Set-SPUser -Identity $user -SyncFromAD -Web site collection -EA 0 -EV Err
        Write-Host "Updated for User: $user"
           
            if ( $Err )
            {
             ConfigureLogger
             LogError $Err[0].Exception
             Write-Host "Not updated for user : $user" -ForegroundColor DarkCyan $_.Exception
            }
        
    }
    
    $SPWeb.Dispose()
    
    
    function LogError($message)
    {
         $date= Get-Date
         $outContent = "[$date]`tError`t`t $message`n"
         Add-Content "Log$Script:logFile" $outContent
    }
    
    function ConfigureLogger()
    {
         
         if((Test-Path Log) -eq $false)
         {
               $LogFolderCreationObj=New-Item -Name Log -type directory
         }
         
         $Script:logFile="UpdateErrorScripts.log"
    
         Add-Content "Log$logFile" "Date`t`t`tCategory`t`tDetails"
    }

    The line is hitting «ConfigureError» function, but giving error as :

    ConfigureLogger : The term 'ConfigureLogger' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path 
    was included, verify that the path is correct and try again.
    At D:TestTestError.ps1:33 char:10
    +          ConfigureLogger
    +          ~~~~~~~~~~~~~~~
        + CategoryInfo          : ObjectNotFound: (ConfigureLogger:String) [], CommandNotFoundException
        + FullyQualifiedErrorId : CommandNotFoundException
     
    LogError : The term 'LogError' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, 
    verify that the path is correct and try again.
    At D:TestTestError.ps1:34 char:10
    +          LogError $Err[0].Exception
    +          ~~~~~~~~
        + CategoryInfo          : ObjectNotFound: (LogError:String) [], CommandNotFoundException
        + FullyQualifiedErrorId : CommandNotFoundException

    How to fix this?

    Thanks

Answers

  • Hi Venkatzeus,

    The errors appear because your functions appear below your code. Powershell does not import all the functions in a script before running executable code, so you need to move your function definitions to the top of the script, or put your code into another
    function that is executed at the bottom of the sript.

    Good Luck!

    Shane

    • Marked as answer by

      Monday, December 5, 2016 4:58 AM

If you think ahead and implement proper logging throughout your PowerShell scripts, you will have a much easier time troubleshooting anything that goes wrong.

There are many different ways to think about logging. Within PowerShell, this could mean logging to the host, a file, the Windows event log, a database, syslog on Linux or even a combination of these choices.

It helps to have options and flexibility, but the setup process to log to all the different places can be difficult.

One of the preferred ways is to create a custom PowerShell logging function that directs the output where you want it in your scripts and functions.

Building the framework

First, decide on the logging options to include. A good starting point is to log to the host, a file and a database.

Next, we build the skeleton of the PowerShell logging function:

Function Write-Log {
    [CmdletBinding()]

    Param (
        $Message
    )

    Process {
        Write-Host $Message
    }
}

At its simplest, we define a single parameter and output it back to the host. Using CmdletBinding will let us take advantage of advanced cmdlet functionality that is explained further along in this tutorial.

Prepping the parameters

Next, add some additional parameters. One of the common functionalities within PowerShell logging is the level of logging such as information, verbose, error or debug.

Add a few parameters into the Param block and some decorative statements to make the PowerShell logging function more comprehensive.

Param (
    [Parameter(
        Mandatory=$true,
        ValueFromPipeline=$true,
        Position=0)]
    [ValidateNotNullorEmpty()]
    [String]$Message,

  [Parameter(Position=1)]
    [ValidateSet("Information","Warning","Error","Debug","Verbose")]
    [String]$Level = 'Information',

    [String]$Path = [IO.Path]::GetTempPath(),
    [String]$Server,
    [String]$Database,
    [String]$Table,
   
    [Switch]$NoHost,
    [Switch]$SQL,
    [Switch]$File
)

The functionality of the new parameters in the script do the following:

  • Message: This is a string sent to the different destinations in the script. This is mandatory, pipeline-enabled and usable without defining a parameter.
  • Level: The level corresponds to one of the following: information, verbose, error, debug. The default is information. The script makes this usable without defining a parameter.
  • Path: Since the function allows logging to a file, use the default temporary path. The script uses [IO.Path]::GetTempPath() to work cross-platform, defaulting to the temp path in Linux and Windows.
  • Server: To support logging to a database — in this case, SQL Server — the script provides the server, which relies on Windows authentication.
  • Database: Along with the server, the script supplies a database to store the data.
  • Table: This section defines the table where we will send the logs.
  • NoHost: This portion does logging without outputting to the host.
  • SQL: The switch defines whether or not to send logs to SQL Server. This requires setting the Server, Database and Table
  • File: The switch defines whether to send logging output to the file.

Defining the logic

The next portion of our PowerShell logging function centers on the output:

$DateFormat = "%m/%d/%Y %H:%M:%S"

If (-Not $NoHost) {
  Switch ($Level) {
    "information" {
      Write-Host ("[{0}] {1}" -F (Get-Date -UFormat $DateFormat), $Message)
      Break
    }
    "warning" {
      Write-Warning ("[{0}] {1}" -F (Get-Date -UFormat $DateFormat), $Message)
      Break
    }
    "error" {
      Write-Error ("[{0}] {1}" -F (Get-Date -UFormat $DateFormat), $Message)
      Break
    }
    "debug" {
      Write-Debug ("[{0}] {1}" -F (Get-Date -UFormat $DateFormat), $Message) -Debug:$true
      Break
    }
    "verbose" {
      Write-Verbose ("[{0}] {1}" -F (Get-Date -UFormat $DateFormat), $Message) -Verbose:$true
      Break
    }
  }
}

If ($File) {
  Add-Content -Path (Join-Path $Path 'log.txt') -Value ("[{0}] ({1}) {2}" -F (Get-Date -UFormat $DateFormat), $Level, $Message)
}

If ($SQL) {
  If (-Not $Server -Or -Not $Database -Or -Not $Table) {
    Write-Error "Missing Parameters"
    Return
  }

  $connection                  = New-Object System.Data.SqlClient.SqlConnection
  $connection.ConnectionString = "Data Source=$Server;Initial Catalog=$Database;Integrated Security=SSPI;"

  If (-Not ($connection.State -like "Open")) {
    $connection.Open()
  }

  $sqlCommand = New-Object System.Data.SqlClient.SqlCommand
  $sqlCommand.Connection = $connection

  $sqlCommand.CommandText = "INSERT INTO [$Database].dbo.$table ( DateTime, Level, Message ) VALUES ( @DateTime, @Level, @Message )"

  $sqlCommand.Parameters.Add("@DateTime", [System.Data.SqlDbType]::VarChar, 255) | Out-Null
  $sqlCommand.Parameters.Add("@Level",    [System.Data.SqlDbType]::VarChar, 255) | Out-Null
  $sqlCommand.Parameters.Add("@Message",  [System.Data.SqlDbType]::NText) | Out-Null

  $sqlCommand.Parameters['@DateTime'].Value = ( Get-Date -UFormat $DateFormat )
  $sqlCommand.Parameters['@Level'].Value    = $Level
  $sqlCommand.Parameters['@Message'].Value  = ($message | Out-String)

  Try {
    $sqlCommand.ExecuteNonQuery() | Out-Null
  } Catch {
    Write-Error "Unable to Insert Log Record: $($_.Exception.Message)"
  }

  If ($connection.State -like "Open") {
    $connection.Close()
  }
}

There are three different sections to the code, which is structured to allow for multiple logging variations, such as output to three different sources at the same time.

  • Host: You can generate output based on the level required, such as verbose, warning or error. If we have defined the nohost switch, then we will skip this; otherwise, the default of our function is to output to the host. The script forces the Write-Debug and Write-Verbose functions with -Debug:$true and -Verbose:$true to send output to the host.
  • File: Outputting to a file is the simplest of the functions here. The script adds content to a file for each new log line.
  • SQL: The most complicated function is the SQL functionality. Most of this is boilerplate code that will set up the connection and prepare the insert command. The script uses SQL parameterization to make the insert code safer.

The following is an example of a Logs table that contains the fields defined in the insert query to set up the database to insert the data:

CREATE TABLE [dbo].[Logs](
  [DateTime] [varchar](255) NOT NULL,
  [Level] [varchar](255) NOT NULL,
  [Message] [ntext] NOT NULL,
);

For the sake of convenience, here’s the full code:

Function Write-Log {
[CmdletBinding()]

    Param (
        [Parameter(
            Mandatory=$true,
            ValueFromPipeline=$true,
            Position=0)]
        [ValidateNotNullorEmpty()]
        [String]$Message,

      [Parameter(Position=1)]
        [ValidateSet("Information","Warning","Error","Debug","Verbose")]
        [String]$Level = 'Information',

        [String]$Path = [IO.Path]::GetTempPath(),
        [String]$Server,
        [String]$Database,
        [String]$Table,

        [Switch]$NoHost,
        [Switch]$SQL,
        [Switch]$File
    )

    Process {
        $DateFormat = "%m/%d/%Y %H:%M:%S"

        If (-Not $NoHost) {
          Switch ($Level) {
            "information" {
              Write-Host ("[{0}] {1}" -F (Get-Date -UFormat $DateFormat), $Message)
              Break
            }
            "warning" {
              Write-Warning ("[{0}] {1}" -F (Get-Date -UFormat $DateFormat), $Message)
              Break
            }
            "error" {
              Write-Error ("[{0}] {1}" -F (Get-Date -UFormat $DateFormat), $Message)
              Break
            }
            "debug" {
              Write-Debug ("[{0}] {1}" -F (Get-Date -UFormat $DateFormat), $Message) -Debug:$true
              Break
            }
            "verbose" {
              Write-Verbose ("[{0}] {1}" -F (Get-Date -UFormat $DateFormat), $Message) -Verbose:$true
              Break
            }
          }
        }

        If ($File) {
          Add-Content -Path (Join-Path $Path 'log.txt') -Value ("[{0}] ({1}) {2}" -F (Get-Date -UFormat $DateFormat), $Level, $Message)
}

        If ($SQL) {
          If (-Not $Server -Or -Not $Database -Or -Not $Table) {
            Write-Error "Missing Parameters"
            Return
          }

          $connection                  = New-Object System.Data.SqlClient.SqlConnection
          $connection.ConnectionString = "Data Source=$Server;Initial Catalog=$Database;Integrated Security=SSPI;"

          If (-Not ($connection.State -like "Open")) {
            $connection.Open()
          }

          $sqlCommand = New-Object System.Data.SqlClient.SqlCommand
          $sqlCommand.Connection = $connection

          $sqlCommand.CommandText = "INSERT INTO [$Database].dbo.$table ( DateTime, Level, Message ) VALUES ( @DateTime, @Level, @Message )"

          $sqlCommand.Parameters.Add("@DateTime", [System.Data.SqlDbType]::VarChar, 255) | Out-Null
          $sqlCommand.Parameters.Add("@Level",    [System.Data.SqlDbType]::VarChar, 255) | Out-Null
          $sqlCommand.Parameters.Add("@Message",  [System.Data.SqlDbType]::NText) | Out-Null

          $sqlCommand.Parameters['@DateTime'].Value = ( Get-Date -UFormat $DateFormat )
          $sqlCommand.Parameters['@Level'].Value    = $Level
          $sqlCommand.Parameters['@Message'].Value  = ($message | Out-String)

          Try {
            $sqlCommand.ExecuteNonQuery() | Out-Null
          } Catch {
            Write-Error "Unable to Insert Log Record: $($_.Exception.Message)"
          }

          If ($connection.State -like "Open") {
            $connection.Close()
          }
        }
    }
}

Using the logging function

Now that we have our function, what’s the best way to use it? Is there something else we should be logging? What makes the most sense for troubleshooting in the future?

The following examples show a few ways to use the new logging function:

Write-Log -Message "First Message"
Write-Log "Second Message" -Level "Warning"
Write-Log "Third Message" -NoHost -File
Write-Log "Fourth Message" -SQL -Server "SQLServer" -Database "Logging" -Table "Logs"
Write-Log "Fifth Message" -Level "Error" -File

What if we log to SQL but don’t want to define the same parameters repeatedly? PowerShell gives the option to use default parameters that the functions will use unless there is an override on a per-function call basis:

$PSDefaultParameterValues = @{
    'Write-Log:SQL'      = $true
    'Write-Log:Server'   = 'SQLServer'
    'Write-Log:Database' = 'Logging'
    'Write-Log:Table'      = 'Logs'
}

By defining some default parameters, the script can use the following:

# Full Function with all Parameters
Write-Log "Logging Message" -SQL -Server "SQLServer" -Database "Logging" -Table "Logs"
# Same Function Call with Default Parameters
Write-Log "Logging Message"

This makes it much easier to control the logging behavior and provides more flexibility.

The end result is you get a PowerShell logging function that builds output, similar to Figure 1.

PowerShell logging function output

Figure 1. Example of output from the PowerShell logging function.

Additional logging functionality to consider

While the PowerShell logging function in this article is useful, there is much more advanced functionality you can add, such as output to syslog in Linux and even internal logging for the function itself to diagnose when the script does not perform as expected.

Next Steps

5 PowerShell tools to help simplify admin tasks and support

7 PowerShell courses to help hone skills for all levels of expertise

What’s new with PowerShell error handling?

Dig Deeper on Windows Server OS and management

  • 11 lessons learned from writing my first Java program

    By: Darcy DeClute

  • Follow this tutorial to get started with Azure Bicep

    ChristopherBlackden

    By: Christopher Blackden

  • Understanding code smells and how refactoring can help

    JoydipKanjilal

    By: Joydip Kanjilal

  • Use NetBackup version command to gather critical data

    StuartBurns

    By: Stuart Burns

Понравилась статья? Поделить с друзьями:
  • Powershell get error code
  • Powershell generate error
  • Powershell exe системная ошибка
  • Postgresql как изменить пароль пользователя postgres
  • Postgresql как изменить название столбца