PowerShell Event 4103: Module logging recorded a command
PowerShell recorded a command as it ran, with the values it was actually given after variables were expanded. Where script block logging shows you the code, module logging shows you the runtime — including parameter values a script only assembled at the last moment.
Also written as PowerShell 4103Event ID 4103 module loggingpowershell module logging event
How oftenFires constantly
Vendor calls itInformation
LogMicrosoft-Windows-PowerShell/Operational
SourceMicrosoft-Windows-PowerShell
What it means for you
On a personal computer
Off by default and not something a personal machine needs. If you have it on, these entries are simply a diary of PowerShell commands.
For an analyst
Enable via Group Policy under Windows Components, Windows PowerShell, Turn on Module Logging, with a module name of * to cover everything. The distinction people miss: 4104 logs the script as written, 4103 logs the pipeline as executed, so a download URL built by concatenating variables appears literally in 4103 and only as string arithmetic in 4104. The trade is volume — 4103 is materially noisier than 4104, and the ContextInfo block repeats a fixed header on every event.
What it looks like
Sanitised. Addresses come from the RFC 5737 documentation ranges.
Sample
CommandInvocation(Invoke-WebRequest): "Invoke-WebRequest"
ParameterBinding(Invoke-WebRequest): name="Uri"; value="https://files.example.invalid/stage2.bin"
ParameterBinding(Invoke-WebRequest): name="OutFile"; value="C:\Users\jbrooks\AppData\Local\Temp\svc.bin"
Context:
Severity = Informational
Host Name = ConsoleHost
Host Application = powershell.exe -nop -w hidden -enc SQBFAFgAIAAoAA==
Engine Version = 5.1.19041.4291
Runspace ID = 0a1b2c3d-0000-0000-0000-00000000abcd
User = CORP\jbrooks
The fields that decide it
Everything else in the log line is context.
CommandInvocation
Which command ran and how it was resolved — the cmdlet name, and whether it came from an alias, a function, or a script.
ParameterBinding
The parameters and the values bound to them at run time. This is the field that justifies the whole feature, because it shows what a variable actually contained.
ContextInfo
A fixed header carrying the user, the host application, the runspace ID and the command line. Repetitive, but it is where you get the identity behind the pipeline.
Host Application: powershell.exe -enc ...The encoded command that launched the session. Decode it and compare against the 4104 for the same runspace.
Host Application: wsmprovhost.exeThe command arrived over PowerShell remoting rather than being typed locally.
Host Application: powershell_ise.exeInteractive scripting host — expected on an admin's workstation, not on a server.
Runspace ID
Ties every event from the same PowerShell session together, including the 4104 script blocks. Pivot on it rather than on time.
Payload
The pipeline output where PowerShell chose to record it. Frequently truncated, so treat it as a hint rather than a complete record.
Ordinary reasons this happens
Most of the time it is one of these.
Everyday administrative scripting, which produces the bulk of the volume.
Configuration management agents running PowerShell on a schedule.
Inventory and monitoring collectors, which call the same handful of cmdlets thousands of times a day.
Module autoloading, where importing a module logs its own internal pipeline activity.
Scheduled tasks that wrap PowerShell, which produce identical events at identical intervals.
When it is not ordinary
A download cradle whose URL is assembled at run time so it never appears literally in the script.
What gives it awayParameterBinding showing the fully resolved URL passed to DownloadString or Invoke-WebRequest, where the 4104 for the same runspace shows only variable concatenation.
Take the Runspace ID and pull every 4103 and 4104 that shares it. One event out of a session is rarely enough to judge.
Read ParameterBinding before CommandInvocation. The command name is often ordinary and the argument is the interesting part.
Check the Host Application line in ContextInfo — an encoded command line or wsmprovhost.exe changes the picture immediately.
Compare the resolved values in 4103 against the script text in 4104. A mismatch is the point of having both.
Confirm whether the same pipeline appears on many machines at the same minute, which usually means your own automation.
If the volume is unmanageable, scope module logging to the modules that matter rather than turning it off — losing resolved parameters costs you more than the storage.
Queries to run
kqlPipelines that arrived over remoting. On servers nobody administers remotely this is close to a standalone alert.
Event | where EventLog == 'Microsoft-Windows-PowerShell/Operational' and EventID == 4103 | where RenderedDescription has 'wsmprovhost.exe' | project TimeGenerated, Computer, UserName, RenderedDescription
kqlSorted ascending on purpose — the rare commands are the interesting ones, and the common ones are your own tooling.
Event | where EventID == 4103 | extend Cmd = extract(@'CommandInvocation\(([^)]+)\)', 1, RenderedDescription) | summarize count() by Cmd, Computer | order by count_ asc
What is the difference between PowerShell Event 4103 and 4104?
4104 is script block logging: it records the code PowerShell was about to run, after decoding and de-obfuscation. 4103 is module logging: it records the pipeline as it executed, including the values that were bound to each parameter. A URL built by joining variables together shows up as readable text in 4103 and only as the concatenation expression in 4104, which is why the two are worth having together.
How do I turn on PowerShell module logging?
Group Policy, under Computer Configuration, Administrative Templates, Windows Components, Windows PowerShell, 'Turn on Module Logging'. Open the module names list and add a single entry of * to cover every module. Naming individual modules is possible but tends to leave gaps, because attackers use the modules you did not list.
Is Event 4103 too noisy to keep?
It is the noisiest of the PowerShell events, and on a server running configuration management it can dominate a log pipeline. The usual answer is not to disable it but to filter known automation at collection — service accounts, your own scripts' runspaces, the monitoring agent — and keep everything else. The resolved parameter values are hard to reconstruct any other way.
Does Event 4103 show the output of commands?
Sometimes, in the Payload field, and never reliably. PowerShell records output where the module declares it, and long output is truncated. Treat any output you get as a bonus; the dependable content is the command and its bound parameters.