How to Enable PowerShell Logging and Script Block Logging on Windows Server 2025

PowerShell is one of the most powerful administration tools in the Windows Server 2025 ecosystem — and, consequently, one of the most frequently abused vectors in post-exploitation attack chains. Attackers who gain initial access to a server routinely leverage PowerShell to download payloads, move laterally, and exfiltrate data, often using obfuscated scripts that evade signature-based antivirus detection. The Windows PowerShell logging subsystem counters this by capturing the complete content of every script block executed, every module loaded, and every command run in a transcript — creating a detailed forensic record that security operations teams and incident responders can query long after an intrusion. Windows Server 2025 ships with four complementary logging mechanisms: Module Logging, Script Block Logging, Transcription Logging, and Protected Event Logging. This guide explains how to configure each using Group Policy and PowerShell, how to query the resulting logs, and how to avoid the operational pitfalls of excessive logging.

Prerequisites

  • Windows Server 2025 (Standard or Datacenter) with PowerShell 5.1 or PowerShell 7.x.
  • Local Administrator or Domain Administrator privileges.
  • Group Policy Management Console (GPMC) for domain-wide GPO deployment.
  • For Protected Event Logging: a code-signing or document-encryption certificate and the CmsMessage module.
  • A SIEM or log aggregation solution for production deployments (Splunk, Microsoft Sentinel, Elastic, or similar).

Step 1: Enable Module Logging

Module Logging records the pipeline execution details of commands invoked from specified PowerShell modules, writing events to the Windows PowerShell event log under Event ID 4103. Unlike Script Block Logging, it captures individual command invocations and their output rather than full script text.

Enable via Group Policy:

  1. Open Group Policy Management, create or edit a GPO linked to your servers OU.
  2. Navigate to Computer Configuration > Administrative Templates > Windows Components > Windows PowerShell.
  3. Open Turn on Module Logging, set to Enabled.
  4. Click Show next to Module Names and enter * to log all modules, or specify individual module names such as ActiveDirectory, NetTCPIP.

Enable Module Logging via registry (for scripted deployment or when GPO is not available):

# Enable Module Logging for all modules
$ModulePath = "HKLM:SOFTWAREPoliciesMicrosoftWindowsPowerShellModuleLogging"

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

Set-ItemProperty -Path $ModulePath -Name EnableModuleLogging -Value 1 -Type DWord

$ModuleNamesPath = "$ModulePathModuleNames"
if (-not (Test-Path $ModuleNamesPath)) {
    New-Item -Path $ModuleNamesPath -Force | Out-Null
}

# Log all modules using wildcard
Set-ItemProperty -Path $ModuleNamesPath -Name "*" -Value "*"

Step 2: Enable Script Block Logging

Script Block Logging is the most forensically valuable PowerShell logging feature. It captures the complete text of every script block (function, script file, or command block) as it is compiled by the PowerShell engine, before execution. This means that even if a script uses string concatenation, environment variable substitution, or Base64 encoding to obfuscate its content, the deobfuscated script block text is logged. Events are written to the Microsoft-Windows-PowerShell/Operational log under Event ID 4104.

Enable via Group Policy:

  1. Navigate to Computer Configuration > Administrative Templates > Windows Components > Windows PowerShell.
  2. Open Turn on PowerShell Script Block Logging, set to Enabled.
  3. Optionally check Log script block invocation start/stop events to also capture Event IDs 4105 and 4106 (invocation start and stop). This significantly increases log volume.

Enable Script Block Logging via registry:

# Enable Script Block Logging
$SBLPath = "HKLM:SOFTWAREPoliciesMicrosoftWindowsPowerShellScriptBlockLogging"

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

Set-ItemProperty -Path $SBLPath -Name EnableScriptBlockLogging -Value 1 -Type DWord

# Optionally enable invocation logging (higher volume — use only if needed)
Set-ItemProperty -Path $SBLPath -Name EnableScriptBlockInvocationLogging -Value 1 -Type DWord

# Verify the settings took effect
Get-ItemProperty -Path $SBLPath

Script Block Logging also activates automatically (regardless of GPO settings) when it detects suspicious patterns — including known obfuscation techniques, Invoke-Expression calls, and encoded command strings. This automatic triggering creates Event ID 4104 with a Warning level for suspicious content.

Step 3: Configure Transcription Logging

Transcription logging captures every input and output of PowerShell sessions to plain-text log files on disk. Each session creates a separate timestamped file at the configured path. Transcripts are human-readable and particularly useful for capturing interactive session activity, but they differ from Script Block Logging in that they capture the console I/O rather than the compiled script block.

# Start a transcript manually in an interactive session
Start-Transcript -Path "C:TranscriptsSession_$(Get-Date -Format 'yyyyMMdd_HHmmss').txt" -Append

# Run some commands — all output will be captured
Get-Service | Where-Object Status -eq Running

# Stop the transcript
Stop-Transcript

Enable system-wide transcription via Group Policy:

  1. Navigate to Computer Configuration > Administrative Templates > Windows Components > Windows PowerShell.
  2. Open Turn on PowerShell Transcription, set to Enabled.
  3. Specify the Transcript output directory — use a UNC path to a centralised share (e.g., \logserverpstranscripts$) so transcripts are collected from all servers in one location.
  4. Check Include invocation headers to prepend timestamp and user information to each transcript entry.

Enable transcription via registry:

$TransPath = "HKLM:SOFTWAREPoliciesMicrosoftWindowsPowerShellTranscription"

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

Set-ItemProperty -Path $TransPath -Name EnableTranscripting -Value 1 -Type DWord
Set-ItemProperty -Path $TransPath -Name OutputDirectory -Value "\logserverpstranscripts$"
Set-ItemProperty -Path $TransPath -Name EnableInvocationHeader -Value 1 -Type DWord

Ensure the transcript destination directory has write permissions for the servers’ computer accounts if writing to a UNC share, and restrict read access to security personnel only.

Step 4: Configure Protected Event Logging

Script Block Logging stores script content in the event log as plaintext, which means anyone with access to the event log can read potentially sensitive data such as passwords embedded in scripts. Protected Event Logging encrypts event log content using a specified certificate’s public key, so only holders of the matching private key can decrypt and read the log data.

# Export the public key certificate to a Base64-encoded string
# First, ensure you have a suitable encryption certificate in the cert store
$Cert = Get-ChildItem Cert:LocalMachineMy | Where-Object {
    $_.EnhancedKeyUsageList.FriendlyName -contains "Document Encryption"
} | Select-Object -First 1

# Export the certificate to a temp file and read it as Base64
$CertPath = "$env:TEMPProtectedLogging.cer"
Export-Certificate -Cert $Cert -FilePath $CertPath -Type CERT | Out-Null
$CertBase64 = [Convert]::ToBase64String([IO.File]::ReadAllBytes($CertPath))

Enable Protected Event Logging via Group Policy:

  1. Navigate to Computer Configuration > Administrative Templates > Windows Components > Event Logging.
  2. Open Enable Protected Event Logging, set to Enabled.
  3. Paste the Base64-encoded certificate public key into the Encrypting Certificate field.

Decrypt protected event log messages using the private key:

# Decrypt a protected event log message (requires private key access)
$Events = Get-WinEvent -LogName "Microsoft-Windows-PowerShell/Operational" |
    Where-Object Id -eq 4104

foreach ($Event in $Events) {
    $Decrypted = Unprotect-CmsMessage -Content $Event.Message
    Write-Output $Decrypted
}

Step 5: Query PowerShell Logs with Get-WinEvent

All PowerShell operational events are written to the Microsoft-Windows-PowerShell/Operational event log. Use Get-WinEvent to query, filter, and export these events for security analysis or SIEM ingestion.

# Query the last 50 Script Block Logging events (ID 4104)
Get-WinEvent -LogName "Microsoft-Windows-PowerShell/Operational" `
    -MaxEvents 50 |
    Where-Object Id -eq 4104 |
    Select-Object TimeCreated, Id, LevelDisplayName, Message |
    Format-List

# Search for suspicious keywords in script block log
Get-WinEvent -LogName "Microsoft-Windows-PowerShell/Operational" |
    Where-Object { $_.Id -eq 4104 -and $_.Message -match "Invoke-Expression|IEX|DownloadString|WebClient" } |
    Select-Object TimeCreated, Message

# Use FilterHashtable for faster queries on large logs
Get-WinEvent -FilterHashtable @{
    LogName   = "Microsoft-Windows-PowerShell/Operational"
    Id        = 4104
    StartTime = (Get-Date).AddDays(-7)
} | Select-Object TimeCreated, Message

# Export events to a CSV for offline analysis
Get-WinEvent -FilterHashtable @{
    LogName = "Microsoft-Windows-PowerShell/Operational"
    Id      = @(4103, 4104, 4105, 4106)
} | Export-Csv -Path "C:LogsPSLogs_$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation

# Check the Windows PowerShell legacy log as well
Get-WinEvent -LogName "Windows PowerShell" -MaxEvents 20 |
    Select-Object TimeCreated, Id, Message

Step 6: Understand AMSI Integration

The Antimalware Scan Interface (AMSI) is a Windows security feature that PowerShell integrates with natively on Windows Server 2025. Before executing any script block, PowerShell submits the deobfuscated content to AMSI, which passes it to the registered antivirus engine (Windows Defender or a compatible third-party product) for real-time scanning. AMSI operates at the language level — it sees the script content after all obfuscation is resolved, making it highly effective against encoded and concatenated payloads.

# Check if AMSI is active — any AMSI-related providers loaded
[Ref].Assembly.GetType("System.Management.Automation.AmsiUtils") | Out-Null
Write-Output "AMSI integration active in this PowerShell session"

# View AMSI scan results in Windows Defender logs
Get-WinEvent -LogName "Microsoft-Windows-Windows Defender/Operational" |
    Where-Object { $_.Id -in @(1116, 1117) } |
    Select-Object TimeCreated, Message -First 10

Step 7: Manage Log Volume and Filtering

Script Block Logging can generate extremely high event volumes on busy servers where administrators run frequent scripts. The default maximum log size for Microsoft-Windows-PowerShell/Operational is only 15 MB. Increase this and configure overwrite behaviour to avoid losing forensic data:

# Increase the maximum log size to 1 GB
$LogName = "Microsoft-Windows-PowerShell/Operational"
$Log = Get-WinEvent -ListLog $LogName
$Log.MaximumSizeInBytes = 1GB
$Log.SaveChanges()

# Verify the new size
Get-WinEvent -ListLog $LogName |
    Select-Object LogName, MaximumSizeInBytes, LogMode

# Set log to overwrite oldest events when full (circular logging)
wevtutil sl "Microsoft-Windows-PowerShell/Operational" /rt:false /ab:true

To reduce noise without losing security visibility, consider enabling Script Block Logging only for specific accounts or excluding known-safe administrative scripts using SIEM-side filtering rules rather than suppressing logging at the source. Forwarding critical Event IDs (4104 with Warning level) to a centralised Windows Event Collector (WEC) server ensures high-value events are preserved even if local logs rotate.

Conclusion

PowerShell logging on Windows Server 2025 is one of the most cost-effective security controls available: it requires no additional software, integrates with your existing Group Policy infrastructure, and produces rich, queryable forensic data that can be the difference between a contained incident and an undetected breach. Start with Script Block Logging and Module Logging enabled across all servers, use transcription logging selectively for high-sensitivity systems, and pipe events to a SIEM for long-term retention and automated alerting. Pair the logging stack with Protected Event Logging on servers that process credentials or secrets, and ensure your log maximum sizes are large enough to survive a busy weekend without rotating away critical evidence before your security team can review it.