How to Configure PowerShell Script Block Logging in Windows 11

Threat actors, ransomware operators, and penetration testers frequently leverage PowerShell for living-off-the-land attacks. By obfuscating command strings using base64 encoding (-EncodedCommand), variable substitutions, or dynamic string concatenations, malicious scripts can evade traditional antivirus file scanners because commands are executed directly in system memory without touching physical disk. Windows 11 includes PowerShell Script Block Logging—an auditing capability that captures the full, de-obfuscated code block executed by the PowerShell engine at runtime. Configuring Script Block Logging via PowerShell ensures comprehensive forensic visibility into command-line activity.

Understanding PowerShell Auditing Levels

PowerShell provides three distinct tiers of operational logging:

  • Module Logging (Event ID 4103): Records pipeline execution details and module calls. Useful for tracking high-level cmdlets, but can generate high log volume without de-obfuscating nested script bodies.
  • Script Block Logging (Event ID 4104): Records the complete content of script blocks as they are executed by the PowerShell parser. Crucially, when an obfuscated script unpacks itself in memory, Script Block Logging captures the final de-obfuscated code block.
  • Transcription: Writes a text file transcript of all interactive console input and output to a secured local or central network directory.

Prerequisites and Environment Setup

  • Windows 11 (Pro, Enterprise, Education, or Home edition).
  • Windows PowerShell 5.1 or PowerShell 7+ running with local Administrator privileges.
  • Windows Event Log service (EventLog) operational.

Enabling Script Block Logging via PowerShell Registry Commands

Script Block Logging is managed under the PowerShell Group Policy registry branch. To enable logging for both Windows PowerShell (5.1) and modern PowerShell 7+ across the workstation:

# 1. Path for Windows PowerShell 5.1
$WinPSPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging"

if (-not (Test-Path $WinPSPath)) {
    New-Item -Path $WinPSPath -Force | Out-Null
}

# Enable Script Block Logging (Value 1 = Enabled)
Set-ItemProperty -Path $WinPSPath -Name "EnableScriptBlockLogging" -Value 1 -Type DWord

# Optional: Log invocation start and stop events
Set-ItemProperty -Path $WinPSPath -Name "EnableScriptBlockInvocationLogging" -Value 1 -Type DWord

# 2. Path for PowerShell 7 Core (if installed)
$CorePSPath = "HKLM:\SOFTWARE\Policies\Microsoft\PowerShellCore\ScriptBlockLogging"

if (-not (Test-Path $CorePSPath)) {
    New-Item -Path $CorePSPath -Force | Out-Null
}

Set-ItemProperty -Path $CorePSPath -Name "EnableScriptBlockLogging" -Value 1 -Type DWord

Write-Host "PowerShell Script Block Logging successfully enabled." -ForegroundColor Green

Auditing and Inspecting Logged Script Blocks (Event ID 4104)

Whenever a script or interactive command executes, Windows writes an entry to the Microsoft-Windows-PowerShell/Operational event log under Event ID 4104.

To inspect recently captured script blocks directly from PowerShell without manually scrolling through Event Viewer:

Get-WinEvent -FilterHashtable @{
    LogName = "Microsoft-Windows-PowerShell/Operational"
    Id = 4104
} -MaxEvents 5 | Select-Object TimeCreated, @{Name="ScriptContent"; Expression={$_.Properties[2].Value}} | Format-List

Each entry reveals:

  • The exact timestamp of execution.
  • The full, de-obfuscated script block content.
  • The total number of message fragments if the script block exceeded the maximum event log entry size.

Configuring PowerShell Transcription Logging

To capture continuous text transcripts of all command-line sessions into an encrypted or restricted administrative directory:

$TransPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\Transcription"

if (-not (Test-Path $TransPath)) {
    New-Item -Path $TransPath -Force | Out-Null
}

# Enable Transcription (Value 1 = Enabled)
Set-ItemProperty -Path $TransPath -Name "EnableTranscripting" -Value 1 -Type DWord

# Set target archive directory
$LogDirectory = "C:\PowerShellTranscripts"
if (-not (Test-Path $LogDirectory)) {
    New-Item -Path $LogDirectory -ItemType Directory -Force | Out-Null
}
Set-ItemProperty -Path $TransPath -Name "OutputDirectory" -Value $LogDirectory -Type String

# Include invocation headers
Set-ItemProperty -Path $TransPath -Name "EnableInvocationHeader" -Value 1 -Type DWord

Managing Event Log Retention and Disk Overhead

On active development systems or continuous automation servers, Script Block Logging can generate substantial event volume. Ensure the PowerShell operational log has adequate capacity:

# Expand PowerShell Operational log size to 512MB
$LogName = "Microsoft-Windows-PowerShell/Operational"
$Log = Get-WinEvent -ListLog $LogName
$Log.MaximumSizeInBytes = 536870912
$Log.SaveChanges()

Best Practice Enterprise Security Recommendations

  • SIEM Ingestion: Forward Event ID 4104 entries to your central Security Information and Event Management (SIEM) platform (such as Microsoft Sentinel, Splunk, or Elastic) to detect suspicious keywords (e.g., DownloadString, IEX, Mimikatz, Invoke-Expression).
  • Protected Event Logging: In high-security environments, enable Protected Event Logging (PEL) to encrypt script blocks using a public certificate before writing to disk, protecting sensitive credentials that might be passed in scripts from unauthorized local inspection.

Get the best tech tips delivered straight to your inbox.

Join thousands of readers mastering Apple, Google, Microsoft, and Linux.