PowerShell is a defender’s paradox: attackers love it because it is powerful and everywhere, and defenders should love it because, configured correctly, it logs its own abuse in remarkable detail. Most environments never turn that logging on.

There are three layers worth enabling, each answering a different question.

1. Module logging (Event ID 4103)

Records pipeline execution details for the modules you choose. It answers “which commands ran?”, useful breadth, moderate volume.

2. Script block logging (Event ID 4104)

The important one. Windows logs the content of every script block as it is executed, including code that was obfuscated on disk but must deobfuscate to run. It answers “what did the code actually do?”

Event ID: 4104
Channel:  Microsoft-Windows-PowerShell/Operational
Contains: The deobfuscated script content, chunked if large

Enable it via Group Policy:

Computer Configuration
└── Administrative Templates
    └── Windows Components
        └── Windows PowerShell
            └── Turn on PowerShell Script Block Logging: Enabled

3. Transcription

Writes a full transcript of every session, commands and output, to a file share. Invaluable for incident response reconstruction; keep the destination on a write-only share attackers can’t purge.

Detection ideas that build on 4104

  • FromBase64String combined with Invoke-Expression or IEX
  • Download cradles: Net.WebClient, Invoke-WebRequest piped to execution
  • -EncodedCommand on the command line (pair with process creation events)
  • AMSI bypass strings (amsiInitFailed and friends)

Frequently asked

Won’t script block logging flood my SIEM? 4104 volume is real but manageable: filter warning-level events (Windows flags suspicious blocks automatically) into your alert path and keep verbose blocks in cheaper retention.

Does PowerShell v2 downgrade evade this? Yes, which is why removing the PowerShell 2.0 engine feature is part of the logging rollout, not an afterthought.

This article was originally published on Medium.