On a Windows server, running a .ps1 file fails with "running scripts is disabled on this system". What is PowerShell's execution policy doing, and why is it not treated as a security boundary?
answer
- it blocks files, not typed commands
- five scopes, policy outranks the user
- safety feature, not a boundary
- the user can turn it off themselves
- real enforcement is application control
basics
~20 sPowerShell's execution policy is refusing to load the script file — Windows clients default to Restricted, servers to RemoteSigned. It guards against accidentally running an untrusted file, not against a determined user: anyone who can start PowerShell can bypass it.
solid answer
~50 sThat message is the execution policy, a per-scope setting that decides whether PowerShell will load a `.ps1` *file*. Windows clients default to `Restricted` (no script files at all) and Windows Server to `RemoteSigned` (local files run, downloaded ones need a signature — "downloaded" being decided by the mark-of-the-web tag on the file). Other values are `AllSigned`, `Unrestricted`, `Bypass` and `Undefined`, resolved across the MachinePolicy, UserPolicy, Process, CurrentUser and LocalMachine scopes in that order, which `Get-ExecutionPolicy -List` shows. Crucially it is not a security control: it stops mistakes, not attackers. Anyone who can start PowerShell can launch with `-ExecutionPolicy Bypass`, pipe the script's text into the interpreter, or set the Process scope for their own session — no elevation required. Real enforcement is AppLocker or Windows Defender Application Control, which constrain what PowerShell will run rather than politely asking.
code
powershell · 5 linesGet-ExecutionPolicy -List
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
powershell.exe -ExecutionPolicy Bypass -File .\deploy.ps1go deeper
Recognise the message as execution policy rather than a file permission problem, and know the safe fix for your own machine: set RemoteSigned in the CurrentUser scope instead of turning the setting off everywhere.
Explain the values and the scope precedence, including that Group Policy scopes outrank a user's own choice, and that the policy applies to script files rather than typed input. Say plainly that it prevents accidents, not attacks.
Demonstrate the judgment: name concrete bypasses available to an unelevated user, then point to AppLocker or WDAC with constrained language mode and script block logging as what actually enforces and records code execution on a fleet.
Own the policy decision — whether the organisation runs application control at all, who signs internal scripts, and how exceptions are granted. Weigh the operational drag of signing everything against the real exposure of a shell setting anyone can override.
## What the error actually means The message — "File C:\...\deploy.ps1 cannot be loaded because running scripts is disabled on this system" — comes from PowerShell itself, before any of your code runs. It is not an NTFS permission problem, not User Account Control, and not antivirus. PowerShell checked its **execution policy** and declined to load the script *file*. Note the word *file*. Execution policy applies to loading `.ps1` files, module files and configuration files. It says nothing about commands typed or pasted into an interactive session: those run regardless of policy. That asymmetry is the first hint about what it is really for. ## The policy values - **Restricted** — no script files load. The default on Windows client editions. - **AllSigned** — every script file must carry a valid Authenticode signature from a publisher you trust, including scripts you wrote yourself. - **RemoteSigned** — locally created files run unsigned; files that arrived from elsewhere must be signed. The default on Windows Server editions. - **Unrestricted** — everything runs; files from another machine produce a warning prompt. - **Bypass** — everything runs, nothing is blocked and nothing warns. - **Undefined** — no policy set in this scope, so the next scope decides. "From elsewhere" is not magic: when a browser or mail client saves a file, it tags it with a zone marker (the mark of the web). `RemoteSigned` reads that tag. Clearing it with `Unblock-File` makes the same bytes count as local — which tells you how much weight the distinction can carry. ## Scopes and precedence The effective policy is resolved across five scopes, highest precedence first: **MachinePolicy**, **UserPolicy** (both set by Group Policy), **Process**, **CurrentUser**, **LocalMachine**. ```powershell Get-ExecutionPolicy -List Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass ``` The Process scope lives only in the current PowerShell process and its environment, so it evaporates when the window closes. That makes it the right scope for a one-off — and, not coincidentally, the reason the setting cannot restrain anybody. Group Policy is the only scope an administrator can actually pin: a policy-set value outranks whatever a user chooses for themselves, and `Set-ExecutionPolicy` will report that it could not take effect. ## Why it is not a security boundary Microsoft documents it as a safety feature, and the reasoning is straightforward: **everything it blocks is available by another route to the same user, at the same privilege level.** All of these are ordinary, non-elevated actions: ```powershell powershell.exe -ExecutionPolicy Bypass -File .\deploy.ps1 Get-Content .\deploy.ps1 | powershell.exe -Command - Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass ``` And beyond the shell, a program can host the PowerShell engine through its .NET API and never consult the policy at all. A control that the attacker can turn off, from the same account, without asking anyone, is not a control — it is a speed bump against the honest mistake of double-clicking something from an email. That framing is the point of the interview question. Candidates who answer "we set AllSigned, so untrusted scripts can't run here" have made a security claim their configuration does not support, and that is the misconception being probed. ## What does enforce script policy If the requirement is genuinely "unapproved code must not execute on this server," the answer is application control, not execution policy: - **AppLocker** or **Windows Defender Application Control (WDAC)** define which executables and scripts may run, enforced by the system rather than by the shell's own preference. - Under those policies PowerShell drops untrusted code into **constrained language mode**, which removes the ability to call arbitrary .NET types and Win32 APIs — the very techniques a bypass relies on. You can read the current mode from `$ExecutionContext.SessionState.LanguageMode`. - **Script block logging** (Group Policy) records what was executed to the `Microsoft-Windows-PowerShell/Operational` event log, so even permitted code is attributable. - Signing scripts with an internal code-signing certificate still has value under those regimes — it is what lets application control identify code as approved, rather than being the enforcement itself. ## Answering the original symptom Operationally, the fix depends on the fleet's policy. On your own workstation, `Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned` is the usual setting. In an automation context — a scheduled task, a build agent — invoke the interpreter with `-ExecutionPolicy Bypass` explicitly rather than loosening the machine's setting, so the exception is visible in the job definition instead of silently applying to everything on the box. If Group Policy pinned the value, none of that helps and the correct move is to get the script signed or approved by the process the organisation already runs.
- How would you allow one automation job to run its script without loosening the whole machine?Launch the interpreter for that job with `-ExecutionPolicy Bypass`, or set the Process scope inside that session. Both are confined to the one process, so the exception is visible in the scheduled task or pipeline definition rather than becoming a machine-wide setting nobody remembers changing.
- If execution policy cannot enforce anything, is signing scripts pointless?No — signing keeps its value under application control. AppLocker and WDAC rules can trust a publisher certificate, so a signature is how approved code is identified. What changes is the enforcement authority: the system decides, instead of a setting the running user can override.
- An administrator ran Set-ExecutionPolicy and it reported that the change did not take effect. What happened?A Group Policy-set scope — MachinePolicy or UserPolicy — outranks CurrentUser and LocalMachine, so the local change is recorded but never wins. `Get-ExecutionPolicy -List` shows which scope is supplying the effective value.
- Why does the policy block a .ps1 file but not the same code pasted into the console?Execution policy governs loading script, module and configuration files; interactive input is never covered. That is precisely why it is described as protection against running an untrusted file by accident rather than as a restriction on what a user may do.
saying these in an interview costs you the question
- "AllSigned means untrusted scripts cannot run here"
- "It is a security boundary against malicious scripts"
- "Bypassing it requires local administrator rights"
- "It blocks commands typed into the console too"
- "Restricted is the default on Windows Server"