Index


What is logging?

Wikipedia describes logging as the act of keeping a log of events that occur in a computer system, such as problems, errors or just information on current operations. A message or log entry is recorded for each such event. These log messages can then be used to monitor and understand the operation of the system, to debug problems, or during an audit.

Using variables

The first way to do logging, where we are looking at, is by using variables. First we create a variable called $MyCustomLog at the start of our script and make it an array.

$MyCustomLog = @()

Then we can start adding lines of logging to the variable at all the places in our script where we need a log entry.

$MyCustomLog = "This is the first line of the log"
$MyCustomLog += "This is another line of the log"

When we are at the end of our script we can output all lines of our code. To do so we add $MyCustomLog to the end of the script.

But if our session is closed those logs are gone. This problem we are going to solve by outputting to a file.

Using Out-File

Instead of writing only our variable name at the end of the script, we write the following:

$MyCustomLog | Out-File -FilePath "<Path_to_the_preferred_logdirectory>\<filename>.txt"

We fill the -FilePath parameter with the path to the directory in which we want to place our log file(s). Then we append to it the name of the log file. For the extension we commonly use .txt or .log, but you can choose your own extension here. But the contents remain plain text. So don’t expect the Cmdlet to automatically add syntaxes when outputting to an .html for example.

Now, instead of seeing the contents of our variable, we are outputting all of the contents into this file. And if we close our session, after the script has run, we still have our log. But what if the session exits when the script is not finished yet. Because we write the contents of the variable only at the end of our script we lose all of our logging. We could write the contents of the variable after each entry to the variable.

$MyCustomLog = "This is the first line of the log"
$MyCustomLog | Out-File -FilePath "<Path_to_the_preferred_logdirectory>\<filename>.txt"

$MyCustomLog += "This is another line of the log"
$MyCustomLog | Out-File -FilePath "<Path_to_the_preferred_logdirectory>\<filename>.txt"

This solution is not very ideal, but we could ditch the variable part. We do this by rewriting our script lines. Ok, lets change the -FilePath parameter to a variable containing "<Path_to_the_preferred_logdirectory>\<filename>.txt" to not constantly needing to write the whole path and if we decide to change the path in the future we don’t need to change it all over our script. Then we change putting the log entry in a variable to directly add the log entry to Out-File.

$LogFilePath = "<Path_to_the_preferred_logdirectory>\<filename>.txt"

"This is the first line of the log" | Out-File -FilePath $LogFilePath
"This is another line of the log" | Out-File -FilePath $LogFilePath

When we run our script and look into the file we see something strange. We only have the last line of our log. Why? The answer is very simple, we overwrite the file with every write action we do. We fix this by adding a parameter to Out-File, -Append. Now we are appending (adding) to the file instead of overwriting it.

$LogFilePath = "<Path_to_the_preferred_logdirectory>\<filename>.txt"

"This is the first line of the log" | Out-File -FilePath $LogFilePath -Append
"This is another line of the log" | Out-File -FilePath $LogFilePath -Append

Is this the best solution?  

No, every line we want to add we need to write ourself. We need a better solution still.

Using transcripts

We find that solution in Transcripts. What are transcripts?

A transaction log is a log of the communications between a system and the users of that system, or a data collection method that automatically captures the type, content, or time of transactions made by a person from a terminal with that system.

But how do we use those? First we start by adding the following line at the start of our script.

Start-Transcript -Path $LogFilePath

At the end of the script we use Stop-Transcript to neatly close the writing to the file. Whenever we add any sort of output in our script, between the `Start-Transcript` and the `Stop-Transcript`, it’s automatically written to the file. So if we write the following script:

Start-Transcript -Path "C:\Temp\LoggingDemo.txt"

Write-output "This is the first line of the log"

Get-Date

Write-Warning "This is a warning!"

Get-Location

Stop-Transcript

The output looks like this:

**********************
PowerShell transcript start
Start time: 20240724114550
Username: ***\***
RunAs User: ***
Configuration Name:
Machine: *** (Microsoft Windows NT 10.0.22631.0)
Host Application: C:\Program Files\PowerShell\pwsh.dll -noexit -nologo
Process ID: 15788
PSVersion: 7.4.3
PSEdition: Core
GitCommitId: 7.4.3
OS: Microsoft Windows 10.0.22631
Platform: Win32NT
PSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0, 5.1, 6.0, 7.0
PSRemotingProtocolVersion: 2.3
SerializationVersion: 1.1.0.1
WSManStackVersion: 3.0
**********************
Transcript started, output file is C:\Temp\LoggingDemo.txt
This is the first line of the log

woensdag 24 juli 2024 11:45:50
WARNING: This is a warning!

Drive        : C
Provider     : Microsoft.PowerShell.Core\FileSystem
ProviderPath : C:\Users\Gebruiker
Path         : C:\Users\Gebruiker

**********************
PowerShell transcript end
End time: 20240724114550
**********************

As we can see we didn’t do anything extra, except starting the Transcript, but we have all of our output.

We could even add the Start-Transcript to our PowerShell profile and log all our commands we used including the output.

Start-Transcript parameters

We saw the basic use of Start-Transcript, but what more can we do with it? Let look at some parameters.

-Append

Indicates that this cmdlet adds the new transcript to the end of an existing file. Use the Path parameter to specify the file.

-Force

Allows the cmdlet to append the transcript to an existing read-only file. When used on a read-only file, the cmdlet changes the file permission to read-write. The cmdlet can’t override security restrictions when this parameter is used.

-NoClobber

Indicates that this cmdlet doesn’t overwrite an existing file. By default, if a transcript file exists in the specified path, Start-Transcript overwrites the file without warning.

-IncludeInvocationHeader

Indicates that this cmdlet logs the time stamp when commands are run. If we use this e.g. in our PowerShell profile, each command we type and run will have a start timestamp. This parameter does nothing in a script.

C:\Users\Gebruiker>
**********************
Command start time: 20240724120053
**********************
[PS] Write-Warning "This is a warning!"
WARNING: This is a warning!
C:\Users\Gebruiker>
**********************
Command start time: 20240724120057
**********************
[PS] ping google.nl

Pinging google.nl [142.250.179.195] with 32 bytes of data:
Reply from 142.250.179.195: bytes=32 time=2ms TTL=117
Reply from 142.250.179.195: bytes=32 time=2ms TTL=117
Reply from 142.250.179.195: bytes=32 time=2ms TTL=117
Reply from 142.250.179.195: bytes=32 time=2ms TTL=117

Ping statistics for 142.250.179.195:
    Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 2ms, Maximum = 2ms, Average = 2ms
C:\Users\Gebruiker>

-UseMinimalHeader

Prepend a short header to the transcript, instead of the detailed header included by default. This parameter was added in PowerShell 6.2.

**********************
PowerShell transcript start
Start time: 20240724115714
**********************

-OutputDirectory

Specifies a specific path and folder in which to save a transcript. PowerShell automatically assigns the transcript name

Suggestions regarding building filenames

We covered some diverent methods for creating logs. But especially when using files to log into, we need a good name for the file. Let’s look at some ways to make creating the names more easy.

Adding date or time

We could add the date and/or time to our filename. The easiest method is using Get-Date with its parameter -Format.

# Year (4-digits), Month (2 digits), Day (2 digits)
Get-Date -Format "yyyyMMdd" ## Output: 20240724

# Full Name of the month, Day (2 digits)
Get-Date -Format "MMMM_dd" ## Output: July_24

# Hour (2 digits, 24 hour notation), Minute (2 digits), Second (2 digits)
Get-Date -Format "HH.mm-ss" ## Output: 18.13-54

Adding the filename of the script

We could also add the filename of the running script to our filename.

$MyInvocation.MyCommand.Name ## Output: MyTranscriptDemo

This will only work if the file containing the script is runned.

Combining date and script name

If we combine both of the previous suggestions then we could get something like this:

$LogDirectory = "C:\Transcripts"
$LogFileName = "Transcript_$($MyInvocation.MyCommand.Name)_$(Get-Date -Format 'yyyyMMdd').txt"

# Starting a transcript in "C:\Transcripts\Transcript_MyTranscriptDemo_20240724.txt"
Start-Transcript -Path "$LogDirectory$LogFileName"

## Your Code Goes Here ##

Stop-Transcript

Summary

Summarizing we can conclude that the easiest way to create an extensive log is by using Start-Transcript, because we can easily switch it ‘on’ and it logs all of our output to the file. Even if you add or encounter a terminating error in your script and it exits the session the log entries are not lost because it writes them to a file instead of caching it in memory or just only on the screen. And maybe more important it also logs those errors.

Leave a Reply

Your email address will not be published. Required fields are marked *