Execution Policy & Script Security Basics

PowerShell Fundamentals

Chapter 10 · Execution Policy & Script Security Basics

Chapter 7 already flagged this moment: the first time you try to run a local .ps1 file, PowerShell may refuse outright with a message about scripts being disabled on the system. That's execution policy — and the single most important thing to understand about it, stated plainly by Microsoft's own documentation, is also the most counterintuitive: it is not a security boundary. It's a safety rail against running something by accident, not a lock that keeps a determined attacker out.

The Policy Levels

PolicyWhat it allows
RestrictedNo scripts at all — only interactive commands typed directly. The factory default on client Windows.
AllSignedEvery script must carry a valid digital signature — even ones you wrote yourself, on your own machine.
RemoteSignedLocally created scripts run freely; scripts that came from the internet must be signed. The default on Windows Server.
UnrestrictedEverything runs, but a downloaded script shows a one-time confirmation prompt before executing.
BypassNothing is blocked, no warnings shown at all — typically used deliberately for a single automated task, not as a standing setting.

Get-ExecutionPolicy / Set-ExecutionPolicy & Scope

Get-ExecutionPolicy -List # Scope ExecutionPolicy # ----- --------------- # MachinePolicy Undefined # UserPolicy Undefined # Process Undefined # CurrentUser Undefined # LocalMachine Restricted <- the actual effective policy here, since nothing narrower is set Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

Multiple scopes can hold a policy at once — MachinePolicy and UserPolicy (set by Group Policy, taking precedence over everything else), then Process, CurrentUser, and LocalMachine in that order. The narrowest scope with a real (non-Undefined) value wins.

A Temporary Bypass, Scoped to One Session

Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process .\trusted-script.ps1

-Scope Process only affects the current PowerShell window — it evaporates the moment that window closes, leaving LocalMachine's own policy completely untouched. This is the right tool for "let this one trusted script run right now" without permanently loosening anything.

Mark of the Web: What "Remote" in RemoteSigned Actually Means

RemoteSigned's distinction between "local" and "downloaded" isn't a guess based on file location — Windows itself tags any file that arrived via a browser, email client, or similar with a hidden NTFS marker called the Mark of the Web (technically a Zone.Identifier alternate data stream). A script you typed and saved yourself never gets this tag; one you downloaded does, which is exactly what RemoteSigned checks for.

# Removes the Mark of the Web from a script you've reviewed and trust Unblock-File -Path .\downloaded-script.ps1
Execution policy is not a security boundary
Microsoft's own documentation states this directly, in those words. It's trivially bypassed by anyone who actually wants to — pasting a script's contents straight into an interactive prompt, running powershell.exe -EncodedCommand, or piping text into Invoke-Expression all skip execution policy entirely, none of them counting as "running a script file." Execution policy exists to stop accidental execution — double-clicking an unfamiliar .ps1, or a script quietly shipped inside something else running without you noticing — not to stop someone determined to run code on a machine they already have access to.
The central fact this chapter is built on
Execution policy answers "did I mean to run this?" — a speed bump requiring deliberate action before a script fires. It does not answer "is this actually safe?", and was never designed to. Real protection against malicious code comes from script signing with a genuinely trusted certificate, OS-level access controls, and — in a locked-down enterprise environment — tools like Constrained Language Mode and AppLocker, all beyond this course's own fundamentals scope. Treat execution policy as a helpful habit-forming guardrail, not a substitute for actually reading a script before running it.

A Practical Default

For a personal development machine, RemoteSigned is the sane, commonly recommended setting: your own scripts run without friction, while anything that arrived from the internet still needs either a real signature or a conscious Unblock-File first — a reasonable balance between convenience and the "did I mean to run this" habit this whole chapter is really about.

A first practical habit
When a script refuses to run, check Get-ExecutionPolicy -List before assuming why — it's easy to fix the wrong scope (loosening LocalMachine when a stricter UserPolicy set by Group Policy is the actual blocker) if you don't first see which scope is genuinely in control.

Hands-On Exercises

Exercise 1

Explain why RemoteSigned lets a script you wrote yourself run freely while blocking one you downloaded, even if both files sit in the same folder. What specific mechanism (named in this chapter) is actually responsible for a file counting as "remote"?

📄 View solution
Exercise 2

Explain why Microsoft describes execution policy as "not a security boundary." Name one concrete way, from this chapter, that someone could run PowerShell code while completely bypassing it.

📄 View solution
Exercise 3

You need to run a single trusted script once without permanently changing your machine's execution policy. Write the command(s) you'd use, and explain why -Scope Process is the right choice here rather than -Scope LocalMachine.

📄 View solution

Chapter 10 Quick Reference

  • Restricted — no scripts at all (client Windows default); AllSigned — every script needs a signature; RemoteSigned — local scripts free, remote ones need a signature (Windows Server default); Unrestricted — everything runs with a warning on remote scripts; Bypass — nothing blocked
  • Get-ExecutionPolicy -List — shows every scope; the narrowest defined one wins
  • Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass — a temporary, session-only override
  • Mark of the Web / Zone.Identifier — the hidden NTFS tag that actually makes a file count as "remote" for RemoteSigned
  • Unblock-File — removes the Mark of the Web from a script you've reviewed and trust
  • Execution policy is not a security boundary — trivially bypassed via -EncodedCommand, Invoke-Expression, or pasting directly into the console; it prevents accidental execution only
  • Practical default: RemoteSigned for a personal dev machine
  • Next chapter: Modules, Aliases & the PowerShell Gallery