← All posts

PowerShell Protected Event Logging: Encrypting Script Block and Transcript Evidence at the Source

Script block logging and transcription leak secrets to anyone who reads the event log. Protected Event Logging encrypts that forensic trail at write time so only your IR team can open it.

Turning on PowerShell logging is one of the highest-value hardening steps on Windows. Script block logging (event ID 4104 in the Microsoft-Windows-PowerShell/Operational log) and transcription capture exactly what attackers ran, even when they obfuscate or run in-memory. But there is a quiet problem most teams never think about: the logs themselves become a secrets database. Every credential that gets passed on a command line, every connection string, every API token embedded in a one-liner, every decoded payload — it all lands in plaintext in a log that, by default, local administrators and anyone with Event Log Readers rights can read.

Protected Event Logging (introduced in Windows 10 / PowerShell 5.0) fixes this. It encrypts log content to a certificate's public key the moment it is written, so the on-disk forensic trail is opaque to everyone except the holder of the matching private key — typically your incident response team on an offline workstation.

The threat: your audit trail is an exfiltration target

Think through what an attacker who already has local admin — the exact scenario your logging is supposed to help you investigate — can do with unencrypted logs:

In other words, the better your PowerShell logging, the juicier the log becomes. Protected Event Logging closes that gap by making the written events useless to anyone without the decryption key — including the attacker standing on the box.

How it works

Protected Event Logging uses the Cryptographic Message Syntax (CMS / PKCS#7). You supply an encryption certificate (document-encryption EKU, OID 1.3.6.1.4.1.311.80.1) via Group Policy. Windows encrypts eligible event content to that certificate's public key before it hits disk. Only a host that possesses the corresponding private key can decrypt it, using Unprotect-CmsMessage. The private key never needs to live on the monitored endpoints at all.

1. Create a document encryption certificate

On an isolated analyst workstation, generate the keypair and export only the public certificate for distribution:

$cert = New-SelfSignedCertificate `
  -Subject "CN=PEL-IR-2026" `
  -KeyUsage KeyEncipherment,DataEncipherment `
  -Type DocumentEncryptionCert `
  -CertStoreLocation Cert:\CurrentUser\My

# Export PUBLIC cert for deployment to endpoints (no private key).
Export-Certificate -Cert $cert -FilePath C:\keys\pel-public.cer

In production, issue this from your enterprise CA with a document-encryption template so you get rotation and revocation for free. Keep the private key off the fleet — on a hardware token or an offline, well-controlled IR host.

2. Turn it on via Group Policy

Enable Computer Configuration → Administrative Templates → Windows Components → Administrative Templates → Event Logging → Enable Protected Event Logging and paste the public certificate content (or point it at a store thumbprint). The registry equivalent for scripted rollout:

$base = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\EventLog\ProtectedEventLogging"
New-Item -Path $base -Force | Out-Null
Set-ItemProperty -Path $base -Name EnableProtectedEventLogging -Value 1 -Type DWord
# EncryptionCertificate expects the full base64 cert content.
Set-ItemProperty -Path $base -Name EncryptionCertificate `
  -Value (Get-Content C:\keys\pel-public.cer -Raw)

Pair this with the two settings that actually generate the sensitive content — script block logging and transcription — under Windows Components → Windows PowerShell. Protected Event Logging has nothing to encrypt if those are off.

3. Decrypt, on your terms

On the endpoint, 4104 events now carry a CMS blob instead of readable script text. On the analyst host that holds the private key:

Get-WinEvent -LogName 'Microsoft-Windows-PowerShell/Operational' |
  Where-Object Id -eq 4104 |
  ForEach-Object { $_.Message | Unprotect-CmsMessage }

What it protects — and what it doesn't

Protected Event Logging is confidentiality for the audit trail. It is not tamper-proofing and not a substitute for shipping logs off the box.

An attacker with local admin can still delete or stop writing logs; encryption stops them reading content, not destroying it. So Protected Event Logging is one layer in a stack:

Where WinSentinel fits

This is exactly the kind of setting that is easy to enable once and impossible to remember to verify. WinSentinel audits your PowerShell logging posture as part of its standard scan — whether script block logging and transcription are on, whether Protected Event Logging is configured, and whether the referenced certificate is actually present and valid rather than a dangling thumbprint from a decommissioned cert.

On a single machine, that full audit runs free with no limits — all of WinSentinel's modules are available on one endpoint without a license. For teams running many machines, the Pro fleet capabilities let a central node roll this check across every endpoint, flag the hosts where logging silently regressed, and surface drift — for example, a golden image that shipped with Protected Event Logging on but a subset of machines where a later GPO change turned it back off. That rollup turns "we enabled it in 2024" into "every node is still enforcing it this morning."

Encrypting your PowerShell forensic trail costs almost nothing and removes an entire class of secret leakage from the one place defenders assumed was safe. Turn on the logging, encrypt what it captures, forward it off the box, and verify the posture holds over time.