
powershell history file exposes every command typed
gc "$env:APPDATA\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt"your powershell history is a diary and it never lies
every time you open powershell and type a command, something is quietly writing it down. not logging in the "we might check this someday" sense, actual plain text, sitting in a file on disk, waiting. most people have no idea it exists. that's not a flaw, it's a feature called psreadline, and once you know it's there, it becomes one of the best free forensic tools on your own machine.
this post is about finding that file, reading it, understanding what it exposes, and then using event logging to see the stuff the history file alone can't show you, like commands that were obfuscated before they ran.
the command and what it actually does
gc "$env:APPDATA\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt"
gc is short for get-content, it just dumps a file's contents to the screen. the interesting part is the path. psreadline, the module that gives you command history, tab completion, and syntax coloring in the powershell console, saves every command you type into a plain text file at that location. no encryption, no obfuscation, just line after line of exactly what was typed.
that means if someone typed a command on this machine, whether it was you, another user profile, or an attacker who got a shell, it's probably sitting in that file right now.
why this matters for defenders
attackers love living-off-the-land techniques, meaning they use powershell that's already installed instead of dropping obvious malware. a common pattern is a "download cradle," something like using invoke-expression combined with a call that grabs a script from a remote url and runs it immediately in memory. it looks roughly like iex paired with a downloadstring call. if that ever got typed or pasted into a powershell session on your machine, it's sitting in that history file in plain text, timestamped by nothing but its position in the file, but present nonetheless.
checking this file is one of the fastest ways to answer "did anything weird run here" without needing fancy edr tooling. it's already built into windows.
the catch: history only shows what was typed, not what actually ran
here's the limitation. psreadline logs the literal text that was entered. if an attacker obfuscated their payload, base64 encoded it, split it into variables, whatever, the history file shows you the ugly obfuscated mess, not the real decoded command that executed. that's where script block logging comes in.
if script block logging is enabled on the system, windows will log event id 4104 whenever a script block runs, and critically, it logs the deobfuscated version of what actually executed. so you get the real payload, even if the attacker tried to hide it. this is why serious environments turn it on everywhere, not just servers.
you can check if it's enabled and pull recent 4104 events like this:
Get-WinEvent -LogName "Microsoft-Windows-PowerShell/Operational" |
Where-Object { $_.Id -eq 4104 } |
Select-Object TimeCreated, Message -First 20
how to actually use this on your own systems
start by reading your own history file with the gc command above. scan for anything referencing iex, downloadstring, invoke-webrequest piped into iex, encoded commands (usually flagged with -enc or -e), or base64 blobs. none of that is normal for day to day admin work.
then confirm script block logging is turned on so you're not relying on history alone. check the registry key:
Get-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging"
if it's not there or not enabled, turn it on through group policy or the registry so future activity gets properly captured, not just what happens to survive in a text file.
the takeaway
your own powershell history file is free, built in, and brutally honest. check it on any machine you're responsible for, especially ones you think might have been touched by someone else. don't stop there though, history can be cleared or the attacker might avoid typing anything memorable. pair it with script block logging and event 4104 so you have a second, harder to evade source of truth. the goal isn't paranoia, it's making sure that if something ran on your system, you have a way to find out about it later instead of hoping you'd have noticed at the time.