PowerShell Transcription and Script Block Logging for Server Audit Trails

Security and change-control teams ask two questions that most Windows administrators cannot answer quickly: who changed this, and when? By default Windows Server keeps very little record of what an administrator actually typed on the console or in an RDP session. PowerShell transcription closes that gap, logging every command and its output to a text file you can archive, review, and attach to a change ticket.

What transcription captures — and what it does not

Transcription records everything that passes through a PowerShell host: the command, the parameters, and the console output. It does not record GUI actions, file edits made in Notepad, or commands run from a shell that does not use the PowerShell engine. Treat it as one control among several, not a complete audit trail.

ControlRecordsGap
PowerShell transcriptionCommands + outputGUI actions, non-PS shells
Script Block LoggingScript blocks executedOutput content
Audit Process CreationProcess launchesCommand details
RDP session recordingFull visual sessionStorage heavy

Enable it system-wide with Group Policy

The durable way to turn transcription on across a server is Group Policy, not a profile edit that the next admin will not have. The path is Computer Configuration → Administrative Templates → Windows Components → Windows PowerShell. Enable Turn on PowerShell Transcription and set the output directory to a UNC path or a local folder that is archived:

# Local Policy / registry equivalent (REG_SZ values under this key)
$key = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\Transcription'
New-Item -Path $key -Force | Out-Null
Set-ItemProperty $key -Name EnableTranscripting -Value 1 -Type DWord
Set-ItemProperty $key -Name OutputDirectory `
    -Value 'C:\Audit\PSTranscripts' -Type String
Set-ItemProperty $key -Name EnableInvocationHeader -Value 1 -Type DWord

EnableInvocationHeader is worth enabling: it prepends a timestamp and the account name, which is the difference between a transcript you can correlate to a change window and one you cannot.

Send transcripts to a share the admin cannot alter

A transcript written to the same server the administrator controls is not evidence. Set OutputDirectory to a UNC path on a separate host and grant the server’s computer account only write permission. The admin can then run commands, but cannot delete or edit the record afterwards.

  • Grant the server’s computer account Write (not Modify, not Full) on the share.
  • Turn on Transcription output to a per-server subfolder so files do not collide.
  • Apply a retention policy — transcripts grow quickly and a full disk is its own incident.
  • Do not rely on the default %USERPROFILE%\Documents location.

Script Block Logging completes the picture

Transcription has a blind spot: scripts scheduled to run without an interactive host may not produce a transcript. Script Block Logging writes every script block the engine compiles into the Microsoft-Windows-PowerShell/Operational event log, which is captured even for non-interactive execution. Enable it under the same policy node:

$k = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging'
New-Item -Path $k -Force | Out-Null
Set-ItemProperty $k -Name EnableScriptBlockLogging -Value 1 -Type DWord

# Confirm events are landing
Get-WinEvent -LogName 'Microsoft-Windows-PowerShell/Operational' -MaxEvents 5 |
    Select-Object TimeCreated, Id, Message

Finding transcripts after the fact

A transcript is only useful if you can locate the right one quickly. With EnableInvocationHeader set, each file begins with a header block containing the start time and account. When investigating an incident, search by that header rather than by filename:

# Find transcripts touching a specific account
Get-ChildItem 'C:\Audit\PSTranscripts' -Recurse -Filter *.txt |
    Select-String -Pattern 'Username: CONTOSO\\admin' -List |
    Select-Object Path

# Find what ran in a specific time window
Get-ChildItem 'C:\Audit\PSTranscripts' -Recurse -Filter *.txt |
    Where-Object { $_.LastWriteTime -gt [datetime]'2026-09-27 02:00' }

For anything beyond ad-hoc searches, forward the operational log to a central collector. Audio-visual RDP session recording exists as a Windows feature as well, but it is storage-intensive and usually reserved for privileged-access workstations rather than general-purpose servers.

Avoiding the performance trap

Transcription adds a file write per command segment. On a server running tight automation loops — a build agent firing hundreds of short scripts, for example — that overhead becomes measurable. Two mitigations keep it cheap: exclude high-frequency, low-value hosts such as scheduled maintenance runspaces from transcription via PSScriptPolicyTest-style exclusions, or simply keep transcripts on a fast local volume and let a scheduled job move completed files to the archive share out of band.

# Nightly ship-and-purge, keeps local volume small
$src = 'C:\Audit\PSTranscripts'
$dst = '\\fileserver\audit$\transcripts\$env:COMPUTERNAME'
Get-ChildItem $src -Recurse -Filter *.txt |
    Where-Object { $_.LastWriteTime -lt (Get-Date).AddHours(-24) } |
    ForEach-Object {
        Copy-Item $_.FullName -Destination $dst -Force
        Remove-Item $_.FullName -Force
    }

Scope the rollout before you commit

Turn transcription on for one server for a week before applying it estate-wide. Measure two things during that pilot: how many megabytes the transcripts consume, and whether any legitimate workflow breaks because it was silently relying on an unlogged step. Adjust retention and exclusions based on real numbers rather than assumptions, then roll out through the same Group Policy that already governs the machines. A staged rollout also gives you a chance to confirm the archive share handles the write rate from several servers at once without contention.

Operational notes on a VPS

  • Disk growth is real. A busy automation server can generate hundreds of megabytes a month. Monitor the transcript volume and rotate.
  • Transcripts may contain secrets. Commands that include passwords or connection strings are recorded verbatim. Secure the output share accordingly — this is a reason to run command handling through credential objects rather than plaintext parameters.
  • Test with a fresh session. Policy applies at host start; open a new PowerShell window to confirm output appears before you declare it working.
  • Correlate, do not just collect. The value is in being able to answer “what changed between 02:00 and 02:15” during an incident review.

Transcription plus Script Block Logging gives you a defensible audit record for a small amount of setup work. On any server that more than one person can log into, that record is the difference between a guess and an answer.

Leave a Comment