Skip to main content
4104Microsoft-Windows-PowerShell/OperationalApplicationSecurity tier 1

Event ID 4104: Creating Scriptblock text

Event ID 4104 is the Windows PowerShell Script Block Logging event, written to the Microsoft-Windows-PowerShell/Operational channel whenever PowerShell compiles a script block. It captures the full, deobfuscated code, including decoded Base64 and reconstructed download-and-execute chains, making it one of the richest sources for hunting fileless PowerShell activity.

What Triggers Event 4104

Event ID 4104 comes from the Microsoft-Windows-PowerShell provider and lands in the Microsoft-Windows-PowerShell/Operational channel. It fires every time the PowerShell engine parses a script block: a function definition, a loop, a one-liner typed at the prompt, or a block built dynamically at runtime through Invoke-Expression or Add-Type.

Two independent mechanisms can put a 4104 in the log, and mixing them up is the single most common mistake analysts make when scoping a hunt.

The first mechanism is deliberate: Script Block Logging turned on through Group Policy under Administrative Templates, Windows Components, Windows PowerShell, Turn on PowerShell Script Block Logging, or through the equivalent registry value EnableScriptBlockLogging under HKLM\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging.

With this enabled, every script block PowerShell processes gets logged, at Verbose level, whether the session is interactive, remote, or a scheduled task. This is the setting that gives full-fidelity coverage, and it is also the setting most environments still leave off because of the log volume it generates on build servers and DevOps hosts that run PowerShell constantly.

The second mechanism runs regardless of that policy. PowerShell carries a built-in list of suspicious constructs, patterns tied to encoding, reflection, and known bypass techniques, and any script block that matches gets logged automatically at Warning level even on a host where Script Block Logging was never enabled. This is why an analyst can sometimes find a 4104 for a single malicious one-liner on a machine that otherwise has none of this logging turned on.

It is a safety net, not a substitute for the GPO: it only catches patterns Microsoft already knows about, and it says nothing about the benign 4104 traffic the analyst would otherwise use as a baseline. Filtering on LevelDisplayName equal to Warning is a legitimate, cheap first pass across a fleet where full logging is not deployed everywhere.

The event's payload carries the fields that make it useful. ScriptBlockText is the actual code, post-parse, which is the important part: PowerShell logs what the interpreter is about to run, not what the operator typed. ScriptBlockId is a GUID that ties related events together.

Path records the originating script file when the block came from a .ps1 on disk, and is empty for ad hoc or in-memory blocks, which is itself a signal worth tracking. MessageNumber and MessageTotal exist because the event payload has a size limit; PowerShell splits a large script block into multiple 4104 events sharing one ScriptBlockId, with MessageNumber marking each fragment's position and MessageTotal recording how many fragments to expect.

An analyst reconstructing a long or heavily obfuscated script has to group by ScriptBlockId, sort by MessageNumber, and concatenate ScriptBlockText across every fragment before reading it as one artifact. Skipping this step is how analysts miss the back half of a payload that only reveals its intent in the last few hundred characters.

In a typical SOC, 4104 shows up constantly from legitimate sources: PSReadLine tab completion, module autoloading, Exchange and Active Directory management scripts, configuration management tools like DSC, and vendor agents that shell out to PowerShell for routine tasks.

It also shows up from attackers at almost every stage past initial access: a downloader pulling a second-stage payload with IEX and a WebClient object, a reconnaissance script enumerating AD group membership, a credential-dumping module reflected into memory, or a ransomware precursor disabling shadow copies.

The neighboring events matter for context. Event 4103 (module logging) records the pipeline execution details, parameter binding and cmdlet invocation, of specific modules that have been opted into logging, giving a shallower but broader view than 4104's full text capture.

Event 4688 (process creation) shows that powershell.exe or pwsh.exe launched at all, with the command line as originally typed, before any decoding, which is why a heavily obfuscated -EncodedCommand argument in 4688 and its plaintext, decoded equivalent in 4104 are two views of the same execution, useful for confirming that what launched matches what actually ran.

Event 4104 Fields

FieldWhat it tells youSimulated value
ScriptBlockTextThe full, deobfuscated text of the script block PowerShell compiled, including resolved Base64 or concatenated payloads.simulated-scriptblocktext-25B818
ScriptBlockIdA GUID that ties every fragment of the same script block together across one or more 4104 events.simulated-scriptblockid-DA86F0
MessageNumberThe position of this event's fragment within a multi-part script block, used with MessageTotal to reassemble the original text.simulated-messagenumber-954FF7
MessageTotalThe total number of fragments a large script block was split across for logging.simulated-messagetotal-DF29D7
PathThe originating .ps1 file path when the block came from a script on disk; empty for interactive or in-memory blocks.simulated-path-4E60D1
UserIdThe account under whose session the script block executed.simulated-userid-447D55
TimeCreatedTimestamp of when PowerShell compiled the script block, used to correlate with process creation and network events.simulated-timecreated-F3C3AA
LevelVerbose when full Script Block Logging is enabled, or Warning when PowerShell auto-flagged the block as suspicious without full logging on.simulated-level-37D1A2
ComputerThe host on which the script block was compiled, used to scope a hunt to specific machines or OU.WS-SIM-551
OpcodeCreate, marking the point at which PowerShell begins compiling the script block for execution.simulated-opcode-C4235F

Inspect Event 4104 Values

Click a field to inspect the full simulated value, the way you would expand it in a SIEM event view.

Example Event 4104 Log

A representative Microsoft-Windows-PowerShell/Operational entry for Event 4104, generated deterministically by our telemetry engine so every field holds a realistic, internally consistent value.

Simulated example generated by SOCSimulator Research
<Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
  <System>
    <Provider Name="Microsoft-Windows-PowerShell" />
    <EventID>4104</EventID>
    <Version>0</Version>
    <Level>0</Level>
    <Task>0</Task>
    <Opcode>0</Opcode>
    <Keywords>0x8020000000000000</Keywords>
    <TimeCreated SystemTime="2025-08-23T17:18:09.000Z" />
    <EventRecordID>209604</EventRecordID>
    <Channel>Microsoft-Windows-PowerShell/Operational</Channel>
    <Computer>WS-SIM-001.corp.socsimulator.example</Computer>
    <Security />
  </System>
  <EventData>
    <Data Name="ScriptBlockText">simulated-scriptblocktext-25B818</Data>
    <Data Name="ScriptBlockId">simulated-scriptblockid-DA86F0</Data>
    <Data Name="MessageNumber">simulated-messagenumber-954FF7</Data>
    <Data Name="MessageTotal">simulated-messagetotal-DF29D7</Data>
    <Data Name="Path">simulated-path-4E60D1</Data>
    <Data Name="UserId">simulated-userid-447D55</Data>
    <Data Name="TimeCreated">simulated-timecreated-F3C3AA</Data>
    <Data Name="Level">simulated-level-37D1A2</Data>
    <Data Name="Computer">WS-SIM-551</Data>
    <Data Name="Opcode">simulated-opcode-C4235F</Data>
  </EventData>
</Event>

Detecting Event 4104

Encoded or obfuscated PowerShell invocation markers

index=windows source="WinEventLog:Microsoft-Windows-PowerShell/Operational" EventCode=4104 (ScriptBlockText="*FromBase64String*" OR ScriptBlockText="*-EncodedCommand*" OR ScriptBlockText="*-enc *" OR (ScriptBlockText="*IEX*" AND ScriptBlockText="*DownloadString*")) | table _time Computer UserId ScriptBlockId ScriptBlockText

Script blocks PowerShell auto-flagged as suspicious

index=windows source="WinEventLog:Microsoft-Windows-PowerShell/Operational" EventCode=4104 Level=3 | stats count by Computer UserId ScriptBlockText

Known offensive PowerShell framework and tool strings

index=windows source="WinEventLog:Microsoft-Windows-PowerShell/Operational" EventCode=4104 (ScriptBlockText="*Invoke-Mimikatz*" OR ScriptBlockText="*Invoke-DCSync*" OR ScriptBlockText="*Invoke-DllInjection*" OR ScriptBlockText="*Get-DomainUser*" OR (ScriptBlockText="*System.Net.WebClient*" AND ScriptBlockText="*FromBase64String*")) | table _time Computer UserId ScriptBlockText

Fragmented multi-part script blocks needing reassembly

index=windows source="WinEventLog:Microsoft-Windows-PowerShell/Operational" EventCode=4104 MessageTotal>1 | stats values(MessageNumber) as parts max(MessageTotal) as total values(ScriptBlockText) as fragments by ScriptBlockId Computer | where mvcount(parts)==total

PowerShell downgrade attack combined with script block execution (Lucene carries the 4688 downgrade base filter; join to 4104 in your SIEM)

index=windows (source="WinEventLog:Microsoft-Windows-PowerShell/Operational" EventCode=4104) OR (source="WinEventLog:Security" EventCode=4688 CommandLine="*-version 2*") | transaction Computer maxspan=2m | search ScriptBlockText="*IEX*" OR ScriptBlockText="*DownloadString*"

True Positive vs False Positive

The single biggest false-positive source for 4104 is legitimate administration. Domain controllers, Exchange servers, and CI/CD runners generate large volumes of 4104 from scheduled maintenance scripts, group policy processing scripts, and configuration drift remediation.

These blocks are almost always associated with a Path pointing to a known, version-controlled .ps1 file in a predictable location, run under a service account, at a predictable cadence. If ScriptBlockText matches a script an admin can produce on request and the Path, account, and timing all line up with change records, treat it as benign and use it to build the environment's PowerShell baseline rather than discarding it.

The pattern that should always get attention is a script block with no Path (built or pasted interactively, or assembled in memory) combined with content that has no legitimate business reason to exist: a WebClient object immediately followed by FromBase64String, an IEX wrapping a DownloadString call, or a -EncodedCommand argument that decodes into something unrelated to the process that launched it. These combinations are what commodity loaders and post-exploitation frameworks produce, because they need to pull and execute a second stage without writing it to disk first.

Known offensive tooling gives a much higher-confidence signal than obfuscation alone, because tool names rarely appear in production automation. ScriptBlockText containing Invoke-Mimikatz, Invoke-DCSync, PowerView cmdlets like Get-DomainUser or Find-DomainShare, or PowerSploit and Empire-style function names is close to definitive; the false-positive rate for these strings in a real production environment is close to zero outside of an authorized red team engagement, which is exactly why they should be pre-approved and whitelisted before an engagement starts.

The automatically Warning-tagged events deserve a slightly different read. Because PowerShell promotes them without any custom tuning, they are a low-effort triage queue: anything at Warning level that does not correspond to a known, approved administrative script or an authorized pentest is worth a look.

What separates a true from a false positive here is almost always the combination of account context and Path. A Warning-level 4104 fired by a help desk technician's interactive session, with no Path and content resembling a downloader, is a very different event from the same content firing under a batch job that has run identically every night for a year.

AMSI-triggering keywords, obfuscation layering (nested encoding, string concatenation designed to break signature matching), and the presence of a fragmented, multi-part script with an unusually high MessageTotal are all secondary signals that shift a borderline Warning event toward true positive, especially when two or more of them appear in the same script block.

MITRE ATT&CK® Techniques

Event 4104 telemetry feeds detections for these ATT&CK® techniques:

Practice Investigating Event 4104

Reading about creating scriptblock text is one thing. Working the detection inside a live console is another. These free SOCSimulator operations cover the attack techniques Event 4104 helps you detect:

Start investigating free

Frequently Asked Questions

What is event ID 4104 in Windows?
It is the PowerShell Script Block Logging event, generated by the Microsoft-Windows-PowerShell provider and written to the Microsoft-Windows-PowerShell/Operational channel. It records the full text of a script block PowerShell compiled, after parsing, so obfuscated or encoded commands appear in their decoded form.
How do you enable event ID 4104 logging?
Enable the Turn on PowerShell Script Block Logging policy under Administrative Templates, Windows Components, Windows PowerShell in Group Policy, or set EnableScriptBlockLogging to 1 under HKLM\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging in the registry. A subset of suspicious script blocks gets logged at Warning level even without this policy enabled.
What is the difference between event ID 4103 and 4104?
4103 is module logging: it records pipeline execution details and parameter values for modules that have opted into logging, which is broader in scope but shallower in detail. 4104 is script block logging: it captures the complete, deobfuscated text of every script block PowerShell processes, which is narrower but far more revealing for reconstructing what actually ran.
Why does event ID 4104 show decoded PowerShell instead of the original encoded command?
PowerShell logs a script block after its parser has already processed the input, not the raw text an operator typed. A -EncodedCommand argument, or a string built through Base64 decoding and concatenation, gets fully resolved before the interpreter executes it, and that resolved form is what lands in ScriptBlockText.
Where can I find PowerShell script block logs in Event Viewer?
Open Event Viewer and navigate to Applications and Services Logs, Microsoft, Windows, PowerShell, Operational. Filter on Event ID 4104 to see script block events, or on 4103 for module logging in the same log.

Sources

Event ID 4104 field values (EventId 4104/0x1008, Channel Operational, Level Verbose), the Microsoft-Windows-PowerShell/Operational channel name, and the Group Policy path and registry key used to enable Script Block Logging.

Splunk Security Content detection logic hunting EventCode 4104 on ScriptBlockText, including encoded-command, WebClient plus FromBase64String, and known offensive-tool keyword patterns.

SigmaHQ detection rule matching ScriptBlockText in EventID 4104 against known offensive PowerShell tool names such as Invoke-Mimikatz and PowerView cmdlets.

T1059 (Command and Scripting Interpreter) is a current, non-deprecated MITRE ATT&CK Enterprise technique under the Execution tactic.

Glossary

What is EDR? SOC Glossary

Endpoint Detection and Response (EDR) is a security technology that continuously monitors endpoint activity, recording p…

Read more
Glossary

What is Threat Hunting? SOC Glossary

Threat hunting is the proactive, human-led process of searching through security telemetry to find hidden threats that e…

Read more
Glossary

What is SIEM? SOC Glossary

Security Information and Event Management (SIEM) is a platform that aggregates, normalizes, and correlates log data from…

Read more
Career Path

Detection Engineer Career Guide: Salary & Skills

Detection Engineers build the rules, analytics, and automated workflows that determine what the SOC can see. You transla…

Read more
Career Path

SOC Analyst (Tier 2) Career Guide: Salary & Skills

Tier 2 SOC Analysts handle the investigations that Tier 1 escalates. You dig into multi-stage attacks, coordinate contai…

Read more
Technique

Command and Scripting Interpreter (T1059): Detection Training

Because interpreters such as PowerShell, cmd.exe, bash, WMI, and Python ship on every host and are trusted by most contr…

Read more
Comparison

SOCSimulator vs. LetsDefend: Platform Comparison

SOCSimulator wins on operational realism. You get multi-tool shift simulation with SLA pressure, noise injection, and al…

Read more
Event ID

Windows Event ID Library: SOC Reference

Windows Security and Sysmon event IDs with simulated log samples, field tables, and tested detection queries.

Read more
Feature

Shift Mode: Real-Time SOC Simulation

Practice alert triage under realistic time pressure with SLA timers and noise injection.

Read more
Feature

Operations: Guided Training Operations

Structured CTF-style investigation operations covering real-world attack scenarios.

Read more
Blog

SOCSimulator Blog: Security Training Insights

Articles on SOC analyst skills, detection engineering, and career development.

Read more
Feature

SOCSimulator Pricing: Free Plan Available

Compare free and Pro tiers.

Read more