Group Policy (MachinePolicy) always wins over LocalMachine and CurrentUser. This is why Set-ExecutionPolicy RemoteSigned may have no visible effect in enterprise environments—a GP is overriding it. Check Get-ExecutionPolicy -List to see every scope's value. The effective policy is the most restrictive non-Undefined scope from top to bottom.
Scripts and Execution Policy
Structure production-grade .ps1 scripts with #Requires guards and param blocks, understand all execution policy levels and scopes, configure policy securely for different environments, and schedule scripts with Windows Task Scheduler.
🧒 Simple Explanation (ELI5)
A PowerShell script is your recipe saved to a file so you can run it again tomorrow. The execution policy is the kitchen's safety rule board: "only chefs with a certificate are allowed to cook here." It controls which saved recipes are allowed to run. Different kitchens (your laptop, a server, a CI/CD agent) can have different rules. The key is knowing which rule applies where, so your recipe runs where you intended and not where it should not.
🔧 Why Do We Need It?
- Reproducibility: scripts in source control run identically for every engineer and every pipeline agent—no manual steps.
- Security: execution policy prevents attackers from running unsigned scripts downloaded from the internet.
- Self-documentation: #Requires and param() blocks make a script's requirements and interface explicit without reading the full code.
- Scheduling: Task Scheduler runs maintenance scripts on exact schedules without manual intervention or cron equivalents.
⚙️ Technical Explanation
Execution Policy levels (least to most restrictive): Unrestricted (run anything), Bypass (no warnings, no blocking—typically used in pipelines), RemoteSigned (local scripts free, downloaded scripts require signature), AllSigned (all scripts must be signed), Restricted (no scripts at all—Windows default on clean installs).
Policies apply at scopes: MachinePolicy (Group Policy), UserPolicy (GP, user), Process (current session only, resets on close), CurrentUser, LocalMachine. The effective policy is the most restrictive active scope.
#Requires statements guard scripts against running in the wrong environment: wrong PowerShell version, missing modules, insufficient privilege.
#Requires -Version 7.0 stops the script with a clear error if run on PowerShell 5.x. #Requires -Modules Az stops it if the Az module is not imported. #Requires -RunAsAdministrator stops it if not elevated. These are far more reliable than checking inside the script body, because they fire before any code runs.
📊 Visual Representation
⌨️ Commands / Syntax
#!/usr/bin/env pwsh
#Requires -Version 7.0
#Requires -Modules Az.Accounts, Az.Resources
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Deploys resources to a target Azure environment.
.DESCRIPTION
Provisions an Azure App Service and associated resources.
Requires Az module and an active Azure login.
.PARAMETER Environment
Target environment: dev, staging, or prod.
.PARAMETER AppName
Name of the application to deploy.
.EXAMPLE
.\Deploy-App.ps1 -Environment staging -AppName skilly-api
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory)]
[ValidateSet('dev', 'staging', 'prod')]
[string]$Environment,
[Parameter(Mandatory)]
[string]$AppName
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
Write-Verbose "Deploying $AppName to $Environment"
# ... deployment logic ...
# ===== EXECUTION POLICY =====
# View policy at all scopes
Get-ExecutionPolicy -List
# Set for current machine (requires admin)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine
# Set for current user only (no admin needed)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
# Bypass for a single script run (does not change machine policy)
powershell -ExecutionPolicy Bypass -File .\deploy.ps1
# ===== TASK SCHEDULER =====
# Create a scheduled task that runs a script daily at 2 AM
$action = New-ScheduledTaskAction -Execute "pwsh.exe" `
-Argument '-NonInteractive -ExecutionPolicy Bypass -File "C:\Scripts\cleanup.ps1"'
$trigger = New-ScheduledTaskTrigger -Daily -At "02:00"
$settings = New-ScheduledTaskSettingsSet -RunOnlyIfNetworkAvailable
Register-ScheduledTask -TaskName "DailyCleanup" -Action $action `
-Trigger $trigger -Settings $settings -RunLevel Highest
💼 Example (Real-world Use Case)
An enterprise team deploys scripts to 200 servers managed by Group Policy. GP sets MachinePolicy to AllSigned. All production scripts are signed with an internal PKI certificate. CI/CD agents run with -ExecutionPolicy Bypass at the process scope to avoid needing signed scripts in the build pipeline. This separates the security boundary: production servers enforce signing; build agents bypass it safely within the pipeline's own trust boundary.
🧪 Hands-on
- Run
Get-ExecutionPolicy -Listto see the current state at all scopes on your machine. - Add
#Requires -Version 5.0to a test script and run it in PowerShell 7. Note it still works because 7 > 5. - Add
#Requires -RunAsAdministratorto a script and run it without elevation to see the error. - Add a proper comment-based help block (.SYNOPSIS, .DESCRIPTION, .PARAMETER, .EXAMPLE) to a function and verify it with
Get-Help. - Examine a Task Scheduler trigger:
Get-ScheduledTask | Select-Object -First 5 | Format-List *.
Create a well-structured .ps1 script with: a #Requires line, a comment-based help block, a [CmdletBinding()] param block with a Mandatory Environment parameter and a ValidateSet, Set-StrictMode -Version Latest at the top, and $ErrorActionPreference = 'Stop'. Then test with -WhatIf, -Verbose, and a missing parameter to see how the guards work.
🐛 Debugging Scenario
Problem: Set-ExecutionPolicy RemoteSigned seems to work, but scripts still fail with "cannot be loaded because running scripts is disabled on this system."
- Cause: a Group Policy is setting
MachinePolicytoRestricted, which overrides yourLocalMachinesetting. GP policies win over Set-ExecutionPolicy. - Diagnose: run
Get-ExecutionPolicy -Listand check ifMachinePolicyorUserPolicyis set to Restricted. If so, the GP is enforcing the restriction. - Fix: for automation and CI/CD, use
powershell -ExecutionPolicy Bypass -File script.ps1at the process scope—this bypasses the restriction for that one run without fighting Group Policy. For persistent change, work with IT to update the GPO or use AllSigned with a code-signing certificate.
🎯 Interview Questions
Beginner
Execution policy controls which PowerShell scripts are allowed to run on a system. It is not a security boundary that prevents all malicious code, but it protects against accidentally running unsigned scripts downloaded from the internet. Levels range from Restricted (no scripts) to Unrestricted (any script).
RemoteSigned allows locally-written scripts to run without a signature, but requires scripts downloaded from the internet (or received by email) to be signed by a trusted publisher. It is the recommended setting for most Windows servers used for automation.
It causes the script to terminate immediately with a clear error if not running with elevated (Administrator) privileges. It fires before any code in the script body runs, ensuring you never get halfway through an admin operation before discovering the script lacks permission.
Scenario-based
Use -ExecutionPolicy Bypass at the process scope for the specific invocation: powershell -ExecutionPolicy Bypass -File script.ps1. This overrides the restriction for that one session without touching the machine or GP policy. For production environments, the better long-term approach is to sign scripts with an internal code-signing certificate and request the GP to allow AllSigned, which maintains a security boundary while allowing authorized scripts.
Add #Requires guards at the top (version, module, admin rights). Add comment-based help (.SYNOPSIS, .PARAMETER, .EXAMPLE). Use a [CmdletBinding(SupportsShouldProcess)] with [ValidateSet] on the Environment parameter. Set $ErrorActionPreference = 'Stop' at the top. Use -WhatIf support for destructive operations. This means Get-Help works, wrong environments are blocked at parameter validation, and the caller can preview before committing.
🌐 Real-world Usage
Well-structured scripts are the first requirement for sharing automation across a team. Platform engineers sign frequently-used scripts with an internal code-signing cert so they pass AllSigned Group Policy on production servers. Scheduled Task based maintenance scripts use -ExecutionPolicy Bypass at the process scope so they work independently of machine policy changes.
📝 Summary
Scripts gain reliability from #Requires guards, comment-based help, strict mode, and strong error action preferences. Execution policy has 5 levels and 5 scopes—Group Policy overrides everything. RemoteSigned is the safe default for servers. Bypass the policy at process scope for pipelines. Use Task Scheduler to run maintenance scripts on schedules without manual intervention.