PowerShell Execution Policy Bypass

The PowerShell execution policy is a safety feature, not a security boundary — Microsoft says so explicitly in about_Execution_Policies. It gates whether configuration files (profiles) load and whether scripts must be digitally signed, but it does nothing to stop a user from running arbitrary PowerShell code: any command that works interactively works regardless of policy. That design intent is exactly why bypassing it is trivial and why defenders should never treat Restricted/AllSigned as a preventive control. Enforcement of the policies only occurs on Windows; on non-Windows PowerShell 6+ the policy is Unrestricted and can’t be changed.

Policy levels and scopes

Per about_Execution_Policies, the levels are Restricted (default on Windows clients; no scripts), AllSigned (scripts must be signed by a trusted publisher), RemoteSigned (default on Windows Server; local scripts run, downloaded scripts must be signed), Unrestricted, and Bypass (nothing blocked, no warnings). Scopes determine where the setting lives:

  • MachinePolicy / UserPolicy — set via Group Policy (win over all others)
  • LocalMachine — registry HKLM, default for Set-ExecutionPolicy, needs admin
  • CurrentUser — registry HKCU
  • Process — held only in $Env:PSExecutionPolicyPreference, dies with the session

Check effective policy and per-scope values:

Get-ExecutionPolicy
Get-ExecutionPolicy -List

Bypass techniques

One-shot flag-ExecutionPolicy Bypass (abbreviated -ex bypass or -ep bypass) applies only to the launched process:

powershell -ex bypass -File .\script.ps1

Note the flag is not needed when executing code directly with -Command (-c) — the policy only governs script files, not interactive commands. That asymmetry is the entire reason bypass is always available.

Process-scope switch — same effect from inside a running shell, no admin required:

Set-ExecutionPolicy Bypass -Scope Process -Force

Machine-scope change (requires admin, persistent):

Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope LocalMachine

Child-shell inheritance — set the environment variable the Process scope reads so any subsequently spawned PowerShell inherits the bypass:

$env:PSExecutionPolicyPreference = "bypass"

Older NetSPI write-ups catalog a dozen more variants (download-and-IEX cradles, reading the script into a variable and Invoke-Expression-ing it, swapping powershell.exe for a renamed copy, etc.) — MITRE ATT&CK cites that list directly under mitigation M1026 with the caveat that policy restriction “can be bypassed depending on environment configuration.”

What bypassing does not defeat

Execution policy bypass changes nothing about the controls that actually matter:

Microsoft’s actual preventive recommendations (MITRE M1045/M1038) are code-signing enforcement and application control (Constrained Language via WDAC/AppLocker), with JEA to sandbox remote admin sessions — none of which an -ex bypass flag touches.

Detection

Execution-policy tampering shows up in process command lines and PowerShell logging:

  • Sysmon EID 1 / Security EID 4688: powershell.exe with -ep bypass, -executionpolicy bypass, -enc, -nop, -w hidden
  • PowerShell Script Block Logging (EID 4104) catches Set-ExecutionPolicy invocations and the $env:PSExecutionPolicyPreference assignment
  • Registry auditing on HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell (ExecutionPolicy value) for persistent changes

Sources

Sources