Systems Admin

PowerShell Execution Policies: Six Modes, Five Scopes, and the Errors You Will Hit

The PowerShell execution policy is the first thing that gets in the way of running a freshly downloaded script and the first thing every Windows administrator learns to bend. It is also one of the most consistently misunderstood pieces of PowerShell — treated either as a bulletproof security boundary (which it is not) or as pure friction to be flipped to Bypass and forgotten (which leaves the door open longer than necessary). This post is a working reference: what each policy mode does, how the five scopes resolve when they conflict, what the default is on client versus server, the exact errors you will see when a policy blocks a script, and the production gotchas that come with each.

What an execution policy actually is

An execution policy is a per-scope setting that controls whether .ps1 script files (and other PowerShell artefacts — .psm1 modules, .ps1xml formatting files) are allowed to load and run, and whether they need a valid digital signature first. It applies to script files only — commands typed interactively at the prompt are never policy-restricted, regardless of what the policy is set to. That is the most important detail to internalise: execution policy is not a sandbox, not a permission system, and not a security boundary. Microsoft documents it explicitly as a “safety control to prevent the user from unintentionally running scripts.” Anyone with shell access can trivially get past it, and several of the bypasses are documented and supported as part of the API.

What it does do well is stop a user double-clicking a script that arrived attached to an email, or accidentally executing a file from a Downloads folder without realising what it does. That is the threat model. For real adversary protection, you need Application Control (WDAC) or AppLocker; the execution policy sits one layer above that as a guard rail.

The six policies, in order of strictness

Restricted

Blocks every .ps1, .psm1, and .ps1xml file. The interactive console works fine; nothing scripted does. This is the default on Windows Server, where the assumption is that any script that needs to run gets there through a deliberate administrator action rather than a casual double-click. A blocked script returns the canonical “running scripts is disabled on this system” error covered later in this post.

AllSigned

Allows scripts only when they carry a valid Authenticode signature from a publisher in the trusted publishers store (or a signature the user explicitly accepts at the first-run prompt). The first time you run a signed-but-not-yet-trusted script, PowerShell prompts: Do you want to run software from this untrusted publisher? with options to allow once, always trust the publisher, or refuse. Choosing “Always run” adds the publisher’s certificate to the local trusted publishers store; subsequent runs from that publisher are silent. Right for environments where every internal script is signed by a corporate code-signing CA and any unsigned script in the directory is by definition someone else’s problem.

RemoteSigned

The default on Windows client SKUs (Windows 10/11). Local scripts run without signatures. Remote scripts — identified by the NTFS Mark-of-the-Web alternate data stream that Internet Explorer, Edge, Outlook, and most browsers attach to downloaded files — require a valid signature, just like AllSigned. The distinction matters because most administrative scripts are written locally and never carry signatures; RemoteSigned lets them run while still gating anything that came from outside the machine. The catch: removing Mark-of-the-Web (with Unblock-File or by ticking the Unblock checkbox in the file’s Properties dialog) re-classifies a downloaded script as local. Not great as a security primitive, but it is by design.

Unrestricted

Scripts run regardless of source or signature. Remote scripts produce a one-time warning prompt before the first run, but local scripts run silently. This is the default on non-Windows PowerShell installations (Linux, macOS) where Mark-of-the-Web does not exist and signing is not the prevailing security model. On Windows it is rarely the right choice — if you need to run anything, RemoteSigned is the better starting point.

Bypass

No checks, no warnings, no prompts. The most permissive setting and the right one for automated execution contexts — CI/CD pipelines, scheduled tasks, software deployment platforms (SCCM, Intune, Ansible), bootstrap scripts that run as part of provisioning. Almost never the right setting at machine scope; almost always the right setting when invoked per-process via powershell -ExecutionPolicy Bypass -File .... The narrow scope is the trick — Bypass for one invocation is fine; Bypass at the machine level removes the safety control entirely.

Undefined

Not a policy in its own right but the absence of one. When a scope reports Undefined, PowerShell looks at the next scope in the precedence chain. If every scope is Undefined, the effective policy is Restricted on Windows clients and Restricted on servers. You see Undefined a lot when you check freshly installed systems before anyone has set anything explicitly.

The five scopes and how they resolve

An execution policy is set per scope, and PowerShell evaluates the scopes in a fixed order. The first scope that has a non-Undefined setting wins:

  1. MachinePolicy — set by Group Policy at the computer level. Highest precedence; cannot be overridden by anything below it.
  2. UserPolicy — set by Group Policy at the user level. Below MachinePolicy, above everything else.
  3. Process — set for the current PowerShell session only. Lives in the $PSExecutionPolicyPreference variable, vanishes when the session closes. Cannot be modified by Group Policy.
  4. CurrentUser — per-user persistent setting. Stored in HKCU:\Software\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell.
  5. LocalMachine — system-wide persistent setting. Stored in HKLM:\Software\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell. Default scope when Set-ExecutionPolicy is invoked without -Scope.

The practical implication: if Group Policy sets a MachinePolicy of AllSigned, no amount of Set-ExecutionPolicy at any other scope will help — the GPO wins. The error message in that case (covered below) is unambiguous about which scope is overriding you.

What the defaults actually are

OS Default policy Why
Windows 10 / 11 (client) RemoteSigned Local admin scripting works out of the box; downloaded scripts get checked.
Windows Server 2016/2019/2022/2025 Restricted Production servers should not be running unattended scripts — the admin opts in deliberately.
PowerShell 7+ on Linux / macOS Unrestricted No Mark-of-the-Web; signing is not the prevailing model on these platforms.
Any scope, no setting Undefined → falls through to Restricted Fail-closed default.

Confirm what is actually in effect with:

Get-ExecutionPolicy -List

That returns one row per scope and the resolved policy at each — useful when the effective policy is the result of a Group Policy override and the per-user setting is being ignored.

Changing the policy — the right one-liners

Most policy changes use the Set-ExecutionPolicy cmdlet. Three common cases cover almost everything:

Set RemoteSigned for the whole machine (requires admin elevation):

Set-ExecutionPolicy -Scope LocalMachine -ExecutionPolicy RemoteSigned -Force

The -Force flag suppresses the “Are you sure?” confirmation prompt — useful in scripts and automation, optional interactively.

Set Unrestricted for just your user (no admin elevation needed):

Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Unrestricted

Set Bypass for the current session only (for ad-hoc scripting work; vanishes when you close the window):

Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass

After any change, verify with Get-ExecutionPolicy -List. The Process scope change does not persist; LocalMachine and CurrentUser changes survive reboots.

The errors you will hit, and how to read them

“Running scripts is disabled on this system”

The headline error, hit on every fresh Windows Server install:

.\script.ps1 : File C:\script.ps1 cannot be loaded because running scripts is disabled on this system.

Cause: the effective policy is Restricted. Three options, in order of preference:

  1. Loosen the policy permanently if this machine routinely runs scripts: Set-ExecutionPolicy -Scope LocalMachine -ExecutionPolicy RemoteSigned -Force (admin required).
  2. Bypass once for an ad-hoc invocation without changing global state: powershell -ExecutionPolicy Bypass -File .\script.ps1. This is the right choice for one-off operations and for tools like SCCM that already pass -ExecutionPolicy Bypass at runtime.
  3. Loosen for the session only if you have several scripts to run before closing the window: Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass.

“Cannot set execution policy due to a Group Policy”

Hit on domain-joined machines where MachinePolicy or UserPolicy is locked down by GPO. The error is explicit:

Set-ExecutionPolicy : Windows PowerShell updated your execution policy successfully, but the setting is overridden by a policy defined at a more specific scope.

The change at LocalMachine or CurrentUser actually succeeds — it just gets overridden every time the policy resolves. There is no local fix; either:

  • Get the GPO updated by whoever owns it (the right answer in production).
  • Use -Scope Process to set Bypass for the session, which sidesteps the GPO because Group Policy never sets the Process scope.
  • Invoke per-script with powershell -ExecutionPolicy Bypass -File ..., which similarly bypasses the GPO for that single execution.

“Script is not digitally signed”

Hit under AllSigned or when the policy is RemoteSigned and the script carries Mark-of-the-Web. The error names the file and explains the signature requirement. Three responses:

  1. Sign the script properly using Set-AuthenticodeSignature and a code-signing certificate from your internal CA (the right answer in any environment that has a code-signing PKI).
  2. Unblock the file if the script is trustworthy and only flagged because it was downloaded: Unblock-File -Path .\script.ps1 removes the Mark-of-the-Web stream and re-classifies the script as local.
  3. Bypass per invocation for one-off needs: powershell -ExecutionPolicy Bypass -File .\script.ps1.

Things that bite people in production

Execution policy is not a security boundary

It is documented that way, but worth repeating because the misconception is so widespread. Anyone with shell access has at least four documented ways past it: pipe the script into powershell -, base64-encode it and pass -EncodedCommand, set -ExecutionPolicy Bypass on the command line, or read the file with Get-Content and pipe into Invoke-Expression. None of these are exploits; they are intentional escape hatches. Treat the policy as a usability guard rail, not a defence against an adversary already executing code on the box.

Group Policy beats everything below it — verify before troubleshooting

If a script that should be allowed by your CurrentUser policy still fails, run Get-ExecutionPolicy -List first. Looking at any one scope in isolation will mislead you. The MachinePolicy or UserPolicy line is what the rest of the chain has to live with.

RemoteSigned and Mark-of-the-Web have edge cases

Files moved between filesystems sometimes lose the Mark-of-the-Web stream (FAT/exFAT does not support alternate data streams, so any script that takes a detour through a USB stick formatted that way comes out unblocked). Files extracted from a ZIP archive may or may not inherit the parent ZIP’s Mark-of-the-Web depending on the extraction tool. The practical result: RemoteSigned can pass a downloaded script that the underlying threat model assumed it would catch. If the threat is meaningful, sign your scripts and use AllSigned instead.

Bypass at machine scope leaves a permanent hole

The temptation when nothing is running and a deadline looms is to set Bypass at LocalMachine and move on. Don’t. The change persists across reboots, applies to every user on the box, and leaves the safety control off for the whole server. Use Process scope for the session, or per-invocation Bypass for the specific scripts. If multiple users on a server genuinely need permissive script execution, RemoteSigned at LocalMachine is the right balance.

The policy applies to .psm1 modules too

Often forgotten: importing a module also goes through the execution policy. A custom internal module dropped into $env:PSModulePath as an unsigned .psm1 file fails to import under AllSigned with the same kind of error you get from a script. If your environment uses AllSigned, every internal module needs the same code-signing treatment as your scripts.

Scope precedence resets on the registry, not on the heuristic you remember

Process > CurrentUser > LocalMachine is the part most admins remember; MachinePolicy > UserPolicy > Process is the part that surprises people. If a GPO sets MachinePolicy to AllSigned, even a Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass looks like it succeeded but does nothing — the resolved policy stays AllSigned because MachinePolicy outranks Process. The exception that does work in that scenario is the per-invocation powershell -ExecutionPolicy Bypass command-line switch, which is checked before the policy chain resolves.

A working playbook

For most administrative environments, the right standing configuration is:

  • Client workstationsRemoteSigned at LocalMachine (the OS default). Local scripts run; downloaded scripts get checked. No GPO override unless you have a specific reason.
  • Server infrastructureRemoteSigned at LocalMachine if the server hosts admin scripts; Restricted (the OS default) if it does not. Use a GPO scoped to the server OU to enforce whichever you choose, so a manual change later does not silently shift a server’s posture.
  • High-security environmentsAllSigned machine-wide via GPO, paired with an internal code-signing CA, plus a Group-Policy-distributed publisher trust for the CA’s certificate. Every internal script and module gets signed as part of release.
  • Automation contexts — per-invocation powershell -ExecutionPolicy Bypass -File ... rather than touching the machine policy. Keeps the standing posture intact and limits the loosening to the single invocation.

Where this fits

The execution policy is the entry point to PowerShell security; the next layers are Application Control (WDAC) for actual integrity-of-execution enforcement, code-signing certificates issued from AD Certificate Services for the AllSigned model, and constrained-language mode plus Just-Enough-Administration (JEA) for delegated PowerShell endpoints. The Windows Server administration pathway covers the rest of the surface area — this post is the “why is my script not running” reference, not the “is my server secure” reference.

Leave a Reply