Introduction

Windows PowerShell script execution policy blocked errors occur when PowerShell prevents script execution due to security policies designed to prevent malicious scripts from running. The execution policy is a safety feature that controls the conditions under which PowerShell loads configuration files and runs scripts, but it often blocks legitimate automation scripts in enterprise environments. Common causes include execution policy set to Restricted (default on many systems), RemoteSigned policy requiring signed scripts from external sources, AllSigned policy requiring all scripts to be signed regardless of origin, Group Policy enforcing execution policy across domain-joined systems, per-scope policy conflicts (MachinePolicy vs UserPolicy vs Process), PowerShell 7+ policies separate from Windows PowerShell, script running from network/UNC path triggering stricter checks, certificate not trusted for script signing, and execution policy bypassed but re-applied by constrained language mode. The fix requires understanding execution policy hierarchy, scope precedence, Group Policy management, script signing requirements, and safe bypass techniques. This guide provides production-proven troubleshooting for PowerShell execution policy issues across Windows PowerShell 5.1, PowerShell 7+, and enterprise environments with Group Policy enforcement.

Symptoms

  • "cannot be loaded because running scripts is disabled on this system"
  • "The file is not digitally signed"
  • "Execution policy blocked execution of script"
  • Script runs interactively but fails when called from scheduler
  • Script works in PowerShell 5.x but blocked in PowerShell 7
  • Group Policy error when changing execution policy
  • "UnauthorizedAccess" error when loading profile
  • Script runs from local path but blocked from network share
  • Execution policy change doesn't persist after restart
  • Different behavior when running as different users

Common Causes

  • Default execution policy is Restricted
  • RemoteSigned requires signed external scripts
  • AllSigned requires all scripts to be signed
  • Group Policy enforces machine-level policy
  • Scope conflict (MachinePolicy > UserPolicy > Process)
  • PowerShell 7 has separate policy from Windows PowerShell
  • Network/UNC path triggers stricter validation
  • Script signing certificate not trusted
  • Constrained Language Mode blocks execution
  • AppLocker or WDAC policies overriding execution policy

Step-by-Step Fix

### 1. Diagnose execution policy state

Check current execution policy:

```powershell # Check all scope policies Get-ExecutionPolicy -List

# Output shows hierarchy: # Scope ExecutionPolicy # ----- --------------- # MachinePolicy Undefined # Set by Group Policy (Computer) # UserPolicy Undefined # Set by Group Policy (User) # Process Undefined # Current session only # CurrentUser Undefined # User-specific setting # LocalMachine Restricted # Machine-wide default

# Check effective policy Get-ExecutionPolicy

# Check for specific scope Get-ExecutionPolicy -Scope LocalMachine Get-ExecutionPolicy -Scope CurrentUser Get-ExecutionPolicy -Scope Process ```

Understand execution policy levels:

```powershell # Policy levels (least to most restrictive): # - Unrestricted: No restrictions (not recommended) # - RemoteSigned: Local scripts run, remote scripts need signature # - AllSigned: All scripts need signature regardless of source # - Bypass: No restrictions, no warnings (for automation) # - Undefined: No policy set (falls back to parent scope) # - Restricted: No scripts allowed (default on servers)

# Remote vs local detection $scriptPath = "C:\Scripts\myscript.ps1" $uri = [System.Uri]::new($scriptPath) $isRemote = -not $uri.IsUnc -and $uri.IsAbsoluteUri -and ($uri.Scheme -eq 'http' -or $uri.Scheme -eq 'https' -or $uri.UNC -or $uri.Host -ne [System.Net.Dns]::GetHostName())

# Zone identifier check (downloaded files) $zone = Get-Content "$scriptPath:Zone.Identifier" -ErrorAction SilentlyContinue if ($zone) { Write-Host "Script downloaded from internet - requires signature with RemoteSigned" } ```

Check PowerShell version and edition:

```powershell # Check PowerShell version $PSVersionTable.PSVersion $PSVersionTable.PSEdition # Desktop = Windows PowerShell, Core = PowerShell 7+

# Windows PowerShell and PowerShell 7 have separate policies # Check both powershell.exe -Command "Get-ExecutionPolicy -List" # Windows PowerShell pwsh.exe -Command "Get-ExecutionPolicy -List" # PowerShell 7 ```

### 2. Change execution policy (local machine)

Set execution policy for current session:

```powershell # Temporary change (current session only) Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass

# Or use bypass parameter when running script powershell.exe -ExecutionPolicy Bypass -File "C:\Scripts\myscript.ps1"

# Run script with unrestricted policy pwsh.exe -ExecutionPolicy Unrestricted -Command "& 'C:\Scripts\myscript.ps1'" ```

Set execution policy permanently:

```powershell # Set for current user (no admin required) Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned

# Set for local machine (requires admin) Start-Process powershell -Verb RunAs -ArgumentList "Set-ExecutionPolicy -Scope LocalMachine -ExecutionPolicy RemoteSigned"

# Recommended for development workstations Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned -Force

# Recommended for servers running signed scripts Set-ExecutionPolicy -Scope LocalMachine -ExecutionPolicy AllSigned -Force ```

Execution policy recommendations:

```powershell # Development workstation # Allows local scripts, requires signature for downloaded scripts Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned

# Production server with signed scripts # Requires all scripts to be signed Set-ExecutionPolicy -Scope LocalMachine -ExecutionPolicy AllSigned

# Automation server (test environment only) # No restrictions - use with caution Set-ExecutionPolicy -Scope LocalMachine -ExecutionPolicy Unrestricted

# CI/CD pipeline agent # Bypass for session, scripts are trusted Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass ```

### 3. Fix Group Policy enforced policies

Check Group Policy enforcement:

```powershell # Check if policy is set by Group Policy Get-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell" -ErrorAction SilentlyContinue Get-ItemProperty -Path "HKCU:\SOFTWARE\Policies\Microsoft\Windows\PowerShell" -ErrorAction SilentlyContinue

# Check specific policy keys Get-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell" | Select-Object ExecutionPolicy

# If set via GPO, local changes will fail with "UnauthorizedAccess" ```

Override Group Policy (requires domain admin):

```powershell # Edit Group Policy on domain controller # gpmc.msc > Group Policy Management # Create/Edit GPO: # Computer Configuration > Policies > Administrative Templates > # Windows Components > Windows PowerShell > Turn on Script Execution

# Or configure via PowerShell on domain controller New-GPO -Name "PowerShell Execution Policy" Set-GPRegistryValue -Name "PowerShell Execution Policy" -Key "HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell" -ValueName "ExecutionPolicy" -Type String -Value "RemoteSigned"

# Link GPO to OU New-GPLink -Name "PowerShell Execution Policy" -Target "OU=Servers,DC=domain,DC=com" ```

Remove local Group Policy enforcement:

powershell # On local machine, remove policy if set locally Remove-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell" -Name "ExecutionPolicy" -ErrorAction SilentlyContinue Remove-ItemProperty -Path "HKCU:\SOFTWARE\Policies\Microsoft\Windows\PowerShell" ` -Name "ExecutionPolicy" -ErrorAction SilentlyContinue

# Refresh Group Policy gpupdate /force

# Verify removed Get-ExecutionPolicy -List ```

### 4. Sign PowerShell scripts

Generate code signing certificate:

powershell # Create self-signed certificate (for internal use) $cert = New-SelfSignedCertificate -DnsName "Script Signing" -CertStoreLocation "Cert:\CurrentUser\My" -Type CodeSigningCert -KeyUsage DigitalSignature -KeyLength 2048 -KeyAlgorithm RSA

# Export certificate Export-Certificate -Cert $cert -FilePath "C:\Certs\ScriptSigning.cer"

# For production, use enterprise CA # Request from AD CS: certsrv.msc ```

Sign script with certificate:

```powershell # Sign script $cert = Get-ChildItem -Path "Cert:\CurrentUser\My" -CodeSigningCert | Select-Object -First 1 Set-AuthenticodeSignature -FilePath "C:\Scripts\myscript.ps1" -Certificate $cert

# Verify signature Get-AuthenticodeSignature -FilePath "C:\Scripts\myscript.ps1"

# Output: # Status : Valid # or NotSigned, HashMismatch, NotTrusted # SignerCertificate : [Certificate details] # TimeStamped : False # add -TimestampServer for timestamp

# Sign with timestamp (recommended) $timestampServer = "http://timestamp.digicert.com" Set-AuthenticodeSignature -FilePath "C:\Scripts\myscript.ps1" -Certificate $cert -TimestampServer $timestampServer ```

Trust code signing certificate:

```powershell # Import certificate to Trusted Publishers (current user) $cert = Get-ChildItem -FilePath "C:\Certs\ScriptSigning.cer" Import-Certificate -FilePath $cert.CertPath -CertStoreLocation "Cert:\CurrentUser\TrustedPublisher"

# For enterprise, deploy via Group Policy # Computer Configuration > Policies > Windows Settings > # Security Settings > Public Key Policies > Trusted Publishers

# Or import to Trusted Root (all users) Import-Certificate -FilePath $cert.CertPath -CertStoreLocation "Cert:\LocalMachine\Root"

# Verify certificate chain Test-Path -Path "Cert:\CurrentUser\TrustedPublisher\$($cert.Thumbprint)" ```

### 5. Remove zone identifier from downloaded scripts

Check zone identifier:

```powershell # Check if file has zone identifier (downloaded from internet) $scriptPath = "C:\Scripts\myscript.ps1" $zoneFile = "$scriptPath:Zone.Identifier"

if (Test-Path $zoneFile) { $zone = Get-Content $zoneFile Write-Host "Zone information found:" Write-Host $zone

# Zone IDs: # 0 = My Computer (local) # 1 = Local Intranet # 2 = Trusted Sites # 3 = Internet (requires signature with RemoteSigned) # 4 = Restricted Sites } ```

Unblock downloaded scripts:

```powershell # Unblock single file Unblock-File -Path "C:\Scripts\myscript.ps1"

# Unblock all scripts in folder Get-ChildItem -Path "C:\Scripts\*.ps1" | Unblock-File

# Or remove zone identifier manually Remove-Item -Path "$scriptPath:Zone.Identifier" -Force -ErrorAction SilentlyContinue

# PowerShell 7+ alternative $scriptPath = "C:\Scripts\myscript.ps1" $alternateStream = "$scriptPath:Zone.Identifier" if (Test-Path $alternateStream) { Remove-Item -Path $alternateStream -Force }

### 6. Handle network share scripts

Access scripts from network share:

```powershell # Scripts from UNC paths require AllSigned by default # Options:

# 1. Map network drive (treats as local) New-PSDrive -Name "S" -PSProvider FileSystem -Root "\\server\scripts" Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned & "S:\myscript.ps1"

# 2. Copy to local path first $source = "\\server\scripts\myscript.ps1" $dest = "$env:TEMP\myscript.ps1" Copy-Item $source $dest Unblock-File $dest & $dest

# 3. Sign script and trust certificate (enterprise solution) # See signing section above

# 4. Add UNC path to trusted zones (not recommended) # Use Group Policy: Computer Configuration > # Windows Settings > Security Settings > # Local Policies > Security Options > # Network access: Sharing and security model ```

Configure PowerShell remoting for network scripts:

```powershell # Run script from network via remoting Invoke-Command -ComputerName localhost -FilePath "\\server\scripts\myscript.ps1"

# Or copy and execute $session = New-PSSession -ComputerName localhost Copy-Item -Path "\\server\scripts\myscript.ps1" -ToSession $session -Destination "C:\Temp\" Invoke-Command -Session $session -ScriptBlock { & C:\Temp\myscript.ps1 } Remove-PSSession $session ```

### 7. Debug Constrained Language Mode

Check language mode:

```powershell # Check current language mode $ExecutionContext.SessionState.LanguageMode

# Language modes: # - FullLanguage: All PowerShell features available # - ConstrainedLanguage: Limited features (no .NET, restricted commands) # - NoLanguage: Only cmdlets, no script blocks # - RestrictedLanguage: Read-only, no variables

# Check if in Constrained Language Mode if ($ExecutionContext.SessionState.LanguageMode -eq "ConstrainedLanguage") { Write-Host "Running in Constrained Language Mode" Write-Host "This is typically enforced by AppLocker or WDAC" } ```

Check AppLocker policies:

```powershell # Check AppLocker status Get-AppLockerPolicy -Effective | Select-Object -ExpandProperty RuleCollections

# Check if PowerShell is restricted Get-AppLockerPolicy -Effective | Where-Object { $_.RuleCollections.RuleType -eq "Script" } | Select-Object -ExpandProperty RuleCollections

# View AppLocker events Get-WinEvent -LogName "Microsoft-Windows-AppLocker/MSI and Script" -MaxEvents 20 ```

Work with Constrained Language Mode:

```powershell # Constrained Language Mode cannot be bypassed if enforced by policy # Solutions:

# 1. Sign script with trusted certificate # AppLocker allows signed scripts

# 2. Modify AppLocker policy (requires admin) # gpmc.msc > Computer Configuration > # Windows Settings > Security Settings > # Application Control Policies > AppLocker

# 3. Use allowed cmdlets only # Constrained Language Mode allows most cmdlets # Avoid: .NET methods, script blocks, certain parameters

# 4. Run in separate process with different context # If not enforced system-wide ```

### 8. Configure scheduled task for scripts

Create scheduled task with execution policy:

powershell # Create scheduled task action with execution policy $action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-ExecutionPolicy Bypass -File C:\Scripts\myscript.ps1"

# Or with PowerShell 7 $action = New-ScheduledTaskAction -Execute "pwsh.exe" ` -Argument "-ExecutionPolicy Bypass -File C:\Scripts\myscript.ps1"

# Configure task settings $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit (New-TimeSpan -Hours 2) -RestartCount 3 ` -RestartInterval (New-TimeSpan -Minutes 1)

# Register task Register-ScheduledTask -TaskName "MyScript" -Action $action -Settings $settings -Principal (New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount) -Trigger (New-ScheduledTaskTrigger -Daily -At 2am) ```

Use PowerShell module for scheduled tasks:

```powershell # Alternative using PowerShell module $action = @{ Execute = "powershell.exe" Arguments = "-NoProfile -ExecutionPolicy Bypass -Command & { C:\Scripts\myscript.ps1 }" } $trigger = New-ScheduledTaskTrigger -Daily -At 2am $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount

Register-ScheduledTask -TaskName "MyScript" -Action $action -Trigger $trigger -Principal $principal -Description "Runs myscript.ps1 daily at 2am" ```

### 9. Handle PowerShell profiles

Fix profile loading errors:

```powershell # Check profile paths $PROFILE.CurrentUserAllHosts $PROFILE.CurrentUserCurrentHost $PROFILE.AllUsersAllHosts $PROFILE.AllUsersCurrentHost

# Check if profiles exist Test-Path $PROFILE.CurrentUserAllHosts Test-Path $PROFILE.AllUsersCurrentHost

# If profile blocked, check execution policy for CurrentUser scope Get-ExecutionPolicy -Scope CurrentUser

# Set less restrictive policy for profiles Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned -Force

# Or remove problematic profile Remove-Item -Path $PROFILE.CurrentUserAllHosts -Force ```

Create signed profile:

```powershell # Create profile script @" # PowerShell Profile Write-Host "Loading profile..." -ForegroundColor Green Set-Location C:\Scripts "@ | Out-File -FilePath $PROFILE.CurrentUserAllHosts -Encoding utf8

# Sign profile $cert = Get-ChildItem -Path "Cert:\CurrentUser\My" -CodeSigningCert | Select-Object -First 1 Set-AuthenticodeSignature -FilePath $PROFILE.CurrentUserAllHosts -Certificate $cert

# Verify Get-AuthenticodeSignature -FilePath $PROFILE.CurrentUserAllHosts ```

### 10. Enterprise deployment strategies

Deploy execution policy via DSC:

```powershell # Desired State Configuration for execution policy Configuration PowerShellExecutionPolicy { Import-DscResource -ModuleName PSDesiredStateConfiguration

Node "localhost" { Registry ExecutionPolicy { Key = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell" ValueName = "ExecutionPolicy" ValueData = "RemoteSigned" ValueType = "String" } } }

# Compile and apply PowerShellExecutionPolicy Start-DscConfiguration -Path .\PowerShellExecutionPolicy -Wait -Verbose ```

Deploy via Intune:

```powershell # PowerShell script for Intune deployment # Detection script $policy = Get-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell" -ErrorAction SilentlyContinue if ($policy.ExecutionPolicy -eq "RemoteSigned") { Write-Host "Compliant" exit 0 } else { Write-Host "Non-compliant" exit 1 }

# Remediation script Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell" -Name "ExecutionPolicy" -Value "RemoteSigned" -Type String -Force

Prevention

  • Document execution policy requirements for each automation project
  • Use code signing certificates from enterprise CA for production scripts
  • Deploy execution policy via Group Policy for consistency
  • Test scripts with RemoteSigned policy before deployment
  • Include execution policy in CI/CD pipeline configuration
  • Monitor AppLocker/WDAC events for script blocking
  • Use signed PowerShell modules for shared libraries
  • Document certificate deployment and renewal procedures
  • **UnauthorizedAccess**: Execution policy change blocked by Group Policy
  • **NotSigned**: Script requires signature under AllSigned/RemoteSigned
  • **HashMismatch**: Script modified after signing
  • **NotTrusted**: Signing certificate not in Trusted Publishers
  • **ConstrainedLanguageMode**: AppLocker/WDAC restricting PowerShell features