3 min readRishi

Fixing 'PowerShell Script Not Digitally Signed' — The Right Way

You download or write a PowerShell script, try to run it, and get:

File script.ps1 cannot be loaded. The file script.ps1 is not digitally signed.

Here is how to fix it — and when each approach is appropriate.

Understanding Execution Policies

PowerShell has five execution policies that control which scripts can run:

PolicyBehavior
RestrictedNo scripts allowed (Windows default)
AllSignedOnly signed scripts run
RemoteSignedDownloaded scripts must be signed; local scripts run freely
UnrestrictedAll scripts run (with warnings for downloaded)
BypassNo restrictions, no warnings

Check your current policy:

Get-ExecutionPolicy -List

This shows policies at every scope (Machine, User, Process).

For most development machines, RemoteSigned is the right balance:

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

This allows locally-created scripts to run freely while requiring signatures on scripts downloaded from the internet.

Fix 2: Unblock a Specific Downloaded Script

If the script is trusted but was downloaded (and thus marked with a web "Zone Identifier"):

Unblock-File -Path .\script.ps1

This removes the Zone.Identifier alternate data stream that Windows attaches to downloaded files. The script will then run under RemoteSigned policy.

Check if a file is blocked:

Get-Item .\script.ps1 -Stream Zone.Identifier -ErrorAction SilentlyContinue

Fix 3: Bypass for a Single Session

Run a script without permanently changing your policy:

powershell -ExecutionPolicy Bypass -File .\script.ps1

Or in an existing session:

Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process

The Process scope only affects the current session and resets when you close the terminal.

Fix 4: Actually Sign Your Script

For production scripts, CI/CD pipelines, or anything running on shared infrastructure, sign properly:

# Get a code signing certificate (from your CA or self-signed for dev)
$cert = Get-ChildItem -Path Cert:\CurrentUser\My -CodeSigningCert

# Sign the script
Set-AuthenticodeSignature -FilePath .\script.ps1 -Certificate $cert -TimestampServer "http://timestamp.digicert.com"

# Verify
Get-AuthenticodeSignature -FilePath .\script.ps1

For self-signed certificates (development only):

$cert = New-SelfSignedCertificate -Type CodeSigningCert `
  -Subject "CN=Dev Signing" `
  -CertStoreLocation Cert:\CurrentUser\My

What NOT to Do

  • Don't set Unrestricted or Bypass at Machine scope on production servers
  • Don't blindly run Set-ExecutionPolicy Unrestricted from Stack Overflow answers
  • Don't disable execution policies in Group Policy for entire domains

These "fixes" work but eliminate an important security layer. In regulated environments, auditors will flag it.

CI/CD Pipelines

In Azure DevOps or GitHub Actions, scripts typically run under Bypass policy by default. If they don't:

# GitHub Actions
- name: Run script
  shell: pwsh
  run: |
    Set-ExecutionPolicy Bypass -Scope Process -Force
    .\deploy.ps1
# Azure DevOps
- task: PowerShell@2
  inputs:
    filePath: 'deploy.ps1'
    # Azure DevOps PowerShell tasks use Unrestricted by default

Key Takeaway

Use RemoteSigned for development machines. Use Unblock-File for trusted downloads. Use proper code signing for production. And never set Bypass at machine scope on a server — future you will thank present you.

Keep reading

Newsletter

New posts, straight to your inbox

One email per post. No spam, no tracking pixels, unsubscribe anytime.

Comments

No comments yet. Be the first.