Running commands on a modern Windows host is not silent. Over the last decade Microsoft added layers of telemetry that record what code ran, which script content was compiled, and even hand script content to antivirus before it executes. An operator who understands each layer — where it records, what it misses, and why — can reason about how loud any action is. This page is that map. It explains the mechanisms; the specific bypass tooling belongs to in-memory tradecraft and the Toolkit.
The layers, from disk to memory
| Mechanism | What it captures | Where |
|---|---|---|
| PSReadline history | Every command physically typed at a PowerShell console, saved to a text file across sessions | A per-user ConsoleHost_history.txt |
| Module logging | Pipeline execution details for modules that opt in | Windows PowerShell log, event ID 4103 |
| Script Block logging | The actual content of every script block the engine compiles — including de-obfuscated code | PowerShell/Operational, event ID 4104 |
| Transcription | A full console transcript — commands and their output — written to a directory | Text transcripts on disk |
| AMSI | Script/buffer content handed to the registered AV for a verdict before execution | Defender/Operational, event IDs 1116 / 1117 |
| ETW | The underlying event pipe most of the above feed into | Kernel/user event tracing |
PSReadline command history
PSReadline gives the console its Unix-like editing and history (Ctrl+R recall), and to do that it persists typed commands to a file that survives reboots. It is not a security control — it is a convenience feature — but it is a rich forensic artifact: an investigator can grep every user's history file for tool names. It only ever captures what is typed at a console, so commands executed programmatically (through a C2 implant, an assembly, or a runspace) never touch it. That gap is exactly why operators avoid interactive consoles and work through in-memory execution.
Module logging (4103)
Introduced in PowerShell 3.0, module logging records pipeline and command-execution events,
but only for modules whose LogPipelineExecutionDetails property is set — by
default most are off. Because the switch is a per-module property that lives in the current
session's objects, its coverage is narrow and its state is mutable from within the session.
Script Block logging (4104) — the important one
Script Block logging is the layer that matters most. It records the content of every script block the PowerShell engine compiles, regardless of the host application and — critically — after de-obfuscation, because the engine has to decode a script to run it. Long scripts are split across multiple 4104 events sharing one ScriptBlock ID.
This defeats the classic evasions: string concatenation, base64, and encoded commands all get logged in their final, readable form because the engine must expand them to execute. Against Script Block logging, hiding the text of a script is close to pointless — which is a major reason offensive tradecraft moved away from PowerShell scripts toward compiled in-memory .NET assemblies and BOFs that never pass through the PowerShell engine at all.
Transcription
System-wide transcription makes any host that uses the PowerShell engine
(powershell.exe, ISE, and .NET applications that load
System.Management.Automation) write a full transcript of commands and output to
a configured directory. Its weakness is operational: if the output path is predictable and
not access-controlled, the recorded evidence can simply be deleted or altered on a host the
attacker already controls. Robust deployments push transcripts to a write-only remote share
so a local attacker cannot reach them.
AMSI — content inspection before execution
The Antimalware Scan Interface is different in kind from the logging above:
it is not a record after the fact but an inspection before execution. AMSI lets an
application hand a buffer — a script, an -EncodedCommand, an in-memory .NET
assembly — to the registered antivirus for a malicious/clean verdict, and blocks it on a
hit. That is why AMSI catches malware regardless of input method (disk, encoded, in-memory):
it sees the content at the moment of use, not the file. Detections surface in the Defender
Operational log as events 1116/1117.
AMSI's strength is also the shape of its weakness. It runs in the same process as the code being scanned, so code already executing in that process can neuter the interface — typically by patching the in-memory scan function to always return “clean,” or by corrupting the provider's initialization so it never engages. After that patch, everything in that process is invisible to AMSI. This is a memory patch, not a file change, so it leaves no disk artifact — and it is why modern loaders bundle an AMSI (and ETW) bypass that runs first.
ETW — the pipe underneath
Event Tracing for Windows is the plumbing many of these logs flow through, including the .NET/CLR provider that reports assembly loads. Disabling or corrupting the relevant ETW provider in the current process — again, an in-memory patch — starves those logs of events, which is why AMSI and ETW bypasses are usually applied together before running post-exploitation tooling in a process.
How the layers combine (and why in-memory wins)
Read together, the layers explain a clear direction of travel in offensive tradecraft:
- On-disk scripts are the worst case — caught by AMSI, Script Block logging, transcription, and Defender's file scanning at once.
- Encoded / obfuscated scripts still lose to Script Block logging (de-obfuscated content is logged) and AMSI (content inspected at runtime).
- Compiled .NET assemblies run in memory skip the PowerShell engine entirely, so PSReadline, module, script-block and transcription logging never see them — leaving only AMSI/ETW (patched first) and endpoint behavioural detection.
None of these are impenetrable, and none should lull an operator: bypasses are per-process and per-session, EDR watches for the bypass patterns themselves, and behavioural detection does not care what the logs say. Knowing the layers is about reading a host's real visibility, not assuming invisibility.
The takeaway
Windows records command-line activity at several layers — a per-user history file, module (4103) and script-block (4104) logging, transcription, and content inspection via AMSI (1116/1117) over ETW. Each has a specific reach and a specific blind spot, and the sum of those blind spots is why offensive tooling migrated to compiled, in-memory execution with AMSI/ETW patched first. The mechanics of that execution are in in-memory tradecraft, and the tooling is in the Toolkit.