How to Write to a Log File in PowerShell

To write to a log file in PowerShell, use Add-Content with a timestamp from Get-Date. Add-Content creates the file the first time and appends a new line on every call after that:

$log = 'C:\psfaqs\Logs\backup.log'

Add-Content -Path $log -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Backup started"
Add-Content -Path $log -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') Copied 1,284 files"

Get-Content -Path $log

Output:

2026-09-28 08:21:39 Backup started
2026-09-28 08:21:39 Copied 1,284 files

Below I turn this into a reusable Write-Log function, add daily log files and error logging, and show one file-name mistake that hides your log. I ran every example in PowerShell 7.6 and Windows PowerShell 5.1.

Create the log folder first

Add-Content creates the file but not a missing folder. New-Item -Force creates the folder if needed and does nothing if it’s already there:

$folder = 'C:\psfaqs\Logs\Payroll'
New-Item -Path $folder -ItemType Directory -Force | Out-Null

Add-Content -Path "$folder\payroll.log" -Value 'Folder and log file are ready'
Get-Content -Path "$folder\payroll.log"

Output:

Folder and log file are ready

A reusable Write-Log function

Most scripts need the same format on every line: a timestamp, a level and the message. This function handles it, and ValidateSet rejects typos like -Level WARNING:

function Write-Log {
    param(
        [Parameter(Mandatory)] [string]$Message,
        [ValidateSet('INFO', 'WARN', 'ERROR')] [string]$Level = 'INFO',
        [string]$Path = 'C:\psfaqs\Logs\app.log'
    )
    $line = '{0} [{1}] {2}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Level, $Message
    Add-Content -Path $Path -Value $line
}

Write-Log -Message 'Nightly backup started'
Write-Log -Message 'Drive D is 85% full' -Level WARN
Write-Log -Message 'Backup finished in 4 minutes'

Get-Content -Path C:\psfaqs\Logs\app.log

Output:

2026-09-28 08:21:43 [INFO] Nightly backup started
2026-09-28 08:21:43 [WARN] Drive D is 85% full
2026-09-28 08:21:43 [INFO] Backup finished in 4 minutes
PowerShell Write-Log function with INFO and WARN levels
Each line gets a timestamp and a level (PowerShell 7)

I use the yyyy-MM-dd HH:mm:ss format because it sorts correctly as text. If servers in several time zones write to one place, log in UTC with (Get-Date).ToUniversalTime().

The function appends each entry with Add-Content, which is covered in PowerShell Add-Content.

To stamp each log line with the function that wrote it, see how to get the current function name in PowerShell.

Create a log file with the date in its name

Put the date in the file name to start a new log each day, or add the time to get one file per run:

$today   = Get-Date -Format 'yyyy-MM-dd'
$logFile = "C:\psfaqs\Logs\backup-$today.log"
Add-Content -Path $logFile -Value 'Daily backup ran'

$runLog = "C:\psfaqs\Logs\export-$(Get-Date -Format 'yyyy-MM-dd_HHmmss').log"
Add-Content -Path $runLog -Value 'Export ran'

(Get-ChildItem -Path C:\psfaqs\Logs).Name

Output:

backup-2026-09-28.log
export-2026-09-28_082145.log

The time part uses HHmmss with no separators. That’s on purpose, as the next section shows.

Don’t put a colon in the file name

A format like ‘yyyy-MM-dd HH:mm’ looks natural, but Windows doesn’t allow colons in file names. Instead of an error, you get something worse:

$stamp = ([datetime]'2026-09-28 14:30').ToString('yyyy-MM-dd HH:mm')
Add-Content -Path "C:\psfaqs\Logs\run-$stamp.log" -Value 'Job finished'

Get-ChildItem -Path C:\psfaqs\Logs | ForEach-Object { "File: [$($_.Name)]  Size: $($_.Length) bytes" }
Get-Item -Path 'C:\psfaqs\Logs\run-2026-09-28 14' -Stream * | ForEach-Object { "Stream: $($_.Stream)" }

Output:

File: [run-2026-09-28 14]  Size: 0 bytes
Stream: :$DATA
Stream: 30.log
PowerShell log file name with a colon creates an alternate data stream
The log text went into a hidden stream named 30.log (Windows PowerShell 5.1)

NTFS read everything after the colon as the name of an alternate data stream. You get an empty file, and the log text is hidden where File Explorer won’t show it. Microsoft’s file naming rules list the reserved characters.

Log errors from try/catch

Write the error message and line number to the log in the catch block. Add -ErrorAction Stop, because most cmdlet errors don’t trigger catch without it:

function Write-Log {
    param(
        [Parameter(Mandatory)] [string]$Message,
        [ValidateSet('INFO', 'WARN', 'ERROR')] [string]$Level = 'INFO',
        [string]$Path = 'C:\psfaqs\Logs\app.log'
    )
    $line = '{0} [{1}] {2}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Level, $Message
    Add-Content -Path $Path -Value $line
}

try {
    Copy-Item -Path C:\psfaqs\Reports\q4-sales.xlsx -Destination C:\psfaqs\Archive -ErrorAction Stop
    Write-Log -Message 'Copied q4-sales.xlsx'
}
catch {
    Write-Log -Level ERROR -Message ('{0} (line {1})' -f $_.Exception.Message, $_.InvocationInfo.ScriptLineNumber)
}

Get-Content -Path C:\psfaqs\Logs\app.log

Output:

2026-09-28 08:21:48 [ERROR] Cannot find path 'C:\psfaqs\Reports\q4-sales.xlsx' because it does not exist. (line 12)
PowerShell try catch writes the error message to a log file
The error message and script line number in the log (PowerShell 7)

$_.Exception.Message holds the text, and $_.InvocationInfo.ScriptLineNumber the line that failed. Microsoft covers the rest in about_Try_Catch_Finally.

Log everything with Start-Transcript

When you want a full record of a run, Start-Transcript copies everything that appears in the console to a file, including Write-Host output:

Start-Transcript -Path C:\psfaqs\Logs\session.log | Out-Null
'Checking disk space...'
Write-Host 'Drive C has 120 GB free'
Stop-Transcript | Out-Null

'--- Found in the transcript:'
Get-Content -Path C:\psfaqs\Logs\session.log | Select-String -Pattern '^(Checking|Drive C)' | ForEach-Object Line

Output:

Checking disk space...
Drive C has 120 GB free
--- Found in the transcript:
Checking disk space...
Drive C has 120 GB free

The first two lines come from the console, and the last two from the transcript file. Stop-Transcript closes the file.

The transcript header includes the user name and computer name, so check before you share one. In PowerShell 7, -UseMinimalHeader leaves them out, as the