PowerShell Execution Policy and Script Signing — A Practical Guide to Graduating From "Papering Over It With Bypass"

· · PowerShell, Windows, Execution Policy, Code Signing, Security, Script, Operational Improvement, Automation

“On a new PC, a script won’t run and just says ‘running scripts is disabled on this system…’” “Adding -ExecutionPolicy Bypass made it work, so now every single task has that written into it” “A .ps1 file placed on a shared folder throws a signature error, but only on one person’s machine” — PowerShell’s execution policy is a wall you almost certainly run into once you start automating things internally. And at a lot of organizations, the fix that keeps piling up, without anyone really understanding the mechanism, is “paper over it with Bypass.”

What makes this tricky is that it’s easy to misunderstand what the execution policy is actually protecting. Treat it as a security feature and lock it down too tightly, and work grinds to a halt. Or go the other way — decide “it’s pointless anyway” and set everything to Bypass, stripping away the last safety feature that prevents accidental mistakes. Both mistakes are avoidable once you understand the mechanism.

This article is aimed at IT staff at small and midsize companies, and anyone using PowerShell to automate routine internal work. It lays out, backed by the official documentation, what the execution policy actually is, how scope precedence works, its relationship to Zone.Identifier (the so-called Mark of the Web), and how to run a distribution operation built around script signing.

1. The Bottom Line First

  • The execution policy is a safety feature, not a security boundary. The official documentation states plainly that “the execution policy is not a security system that restricts user actions” and that “it can be trivially bypassed by typing a script’s contents into the command line.” Its purpose is to set basic rules and prevent unintended execution — accidental mistakes.1
  • The execution policy only affects running scripts — you can always run interactive commands regardless. Windows PowerShell 5.1 defaults to Restricted (no script execution) on client OSes and RemoteSigned on Windows Server. PowerShell 7 defaults to RemoteSigned.21
  • The policy has five scopes, with precedence MachinePolicy > UserPolicy > Process > CurrentUser > LocalMachine. MachinePolicy and UserPolicy are Group-Policy-only; you cannot change them with Set-ExecutionPolicy, and no command-line specification can override them either.13
  • RemoteSigned only requires a signature for scripts that came “from the internet.” That origin is judged by the Zone.Identifier alternate data stream attached to the file (the Mark of the Web); once you’ve reviewed the contents, removing it with Unblock-File lets you run the script without ever changing the policy.14
  • AllSigned requires a trusted publisher’s signature on every script, including locally created ones. You apply the signature with Set-AuthenticodeSignature, which embeds it as a # SIG # comment block at the end of the file.15
  • Always attach a timestamp (-TimestampServer) to a signature. With a timestamp, the script keeps working even after the signing certificate expires. Since most code-signing certificates are valid for a year, skipping this turns into a time bomb that goes off every year.65
  • A self-signed certificate is for testing only. A script signed with a self-signed certificate won’t run on any other computer. For organizational distribution, use a code-signing certificate issued by a certification authority (an internal CA or a commercial CA).57
  • If you want organization-wide control, manage it centrally through Group Policy’s “Turn on Script Execution” setting. This setting takes precedence over every scope setting on the PowerShell side.1

2. What the Execution Policy Actually Is — a “Safety Feature,” Not a “Security Boundary”

Let’s confirm the premise first. The official documentation (about_Execution_Policies) is blunt about this. The execution policy is a “safety feature” that controls the conditions under which PowerShell loads configuration files and runs scripts — it is “not a security system that restricts user actions.” That’s because even a user who can’t run a script can still carry out the same processing by pasting the script’s contents into the command line. It’s explicitly stated that the execution policy’s role is to set basic rules and prevent unintentional violation of them.1 The getting-started documentation repeats the same point: “it is not a security boundary; it cannot stop a user who deliberately intends to run a script.”2

Once you grasp this positioning, your operational design policy falls into place. Both “tightening the execution policy will prevent attacks” and “it’s pointless anyway since it can be bypassed” are wrong. Countering attacks is the job of a different layer (application control, least privilege, audit logging); the execution policy’s job is preventing accidents.8

Here’s a summary of the differences between the main policies.1

Policy Script execution Signature requirement Positioning
Restricted Not allowed (individual commands only) Windows PowerShell 5.1’s default on client OSes2
AllSigned Allowed Required for all scripts and configuration files. Prompts for confirmation before running anything from an unclassified publisher For organizations that can run a signing operation
RemoteSigned Allowed Required only for scripts that came from the internet. Not required for locally created ones The practical standard. PowerShell 7’s default1
Unrestricted Allowed None (a warning outside the intranet zone) The default on non-Windows platforms (cannot be changed)1
Bypass Allowed None. No warnings, no prompts For embedding in applications built on PowerShell that have their own security model1

It’s easy to overlook, but Restricted stops more than just your own business scripts — it also blocks loading profiles (.ps1), modules (.psm1), and formatting configuration files (.ps1xml).1 “The profile isn’t loading on the new machine” turning out to be caused by the execution policy is a classic recurring consultation.

3. Scopes and Precedence — What’s Really Behind “I Changed It, But Nothing Changed”

The execution policy isn’t a single value — it can be set independently at five different scopes, and the highest-precedence one becomes the effective value.1

Scope How it’s set Where it’s stored Precedence
MachinePolicy Group Policy (Computer Configuration) GPO 1 (highest)
UserPolicy Group Policy (User Configuration) GPO 2
Process The -ExecutionPolicy startup parameter / -Scope Process The $Env:PSExecutionPolicyPreference environment variable (disappears when the session ends) 3
CurrentUser Set-ExecutionPolicy -Scope CurrentUser The user’s configuration 4
LocalMachine Set-ExecutionPolicy (the default scope, requires administrator) Configuration shared by all users 5

The standard way to diagnose “I set it, but nothing changed” is to look at the full list, not just the effective value.

# Always check which scope is actually in effect, not just the effective policy
Get-ExecutionPolicy -List

# Example: even with LocalMachine set to AllSigned, CurrentUser's RemoteSigned wins
#         Scope ExecutionPolicy
#         ----- ---------------
# MachinePolicy       Undefined
#    UserPolicy       Undefined
#       Process       Undefined
#   CurrentUser    RemoteSigned
#  LocalMachine       AllSigned

# To change just your own environment, the CurrentUser scope is easy since it needs no admin rights
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

Because CurrentUser outranks LocalMachine, the effective policy in the example above is RemoteSigned.1 Set-ExecutionPolicy writes to the LocalMachine scope by default, which requires administrator rights, but at the CurrentUser scope even a regular user can change it.3

And the real tool for organizational management is Group Policy. The “Turn on Script Execution” policy takes precedence over every scope set on the PowerShell side. Disabling it is equivalent to Restricted; enabling it lets you choose between “Allow all scripts” (Unrestricted), “Allow local scripts and remote signed scripts” (RemoteSigned), and “Allow only signed scripts” (AllSigned), and it lives under the Windows Components\Windows PowerShell administrative template. Computer Configuration takes precedence over User Configuration.1 In an environment managed by GPO, Set-ExecutionPolicy will store the setting but it won’t actually take effect, and it displays a message explaining the conflict.3

There’s one important consequence here. If GPO is managing the policy, an -ExecutionPolicy Bypass written into a task or shortcut has no effect. It’s explicitly documented that a Process-scope specification beats the LocalMachine/CurrentUser configuration, but it cannot beat Group Policy.1 Put the other way around, “just write Bypass and it’ll work out” only holds true in environments where the organization isn’t managing the execution policy at all.

4. Zone.Identifier (Mark of the Web) and Unblock-File

RemoteSigned’s notion of “remote (from the internet)” is judged not by where the file sits, but by a mark on the file. Programs like browsers attach an alternate data stream to a file they download, marking it as “a file that came from the internet.”1 That stream is Zone.Identifier, and it holds the value 3, indicating the internet zone. This is the so-called Mark of the Web.4

Try to run an unsigned script carrying that mark in a RemoteSigned environment, and it gets blocked with a “not digitally signed” error. The fix comes in two steps.

# 1) First check which files are blocked (a safe, read-only operation)
Get-Item -Path C:\Tools\*.ps1 -Stream Zone.Identifier -ErrorAction SilentlyContinue

# 2) Only unblock the ones you've reviewed and judged safe
#    (Unblock-File removes the Zone.Identifier stream; the execution policy itself doesn't change)
Unblock-File -Path C:\Tools\Get-InventoryReport.ps1

Unblock-File is the cmdlet that removes the Zone.Identifier alternate data stream, and it’s the same operation as the “Unblock” checkbox in the file’s Properties in File Explorer. The key point is that you can let only reviewed files through without loosening the execution policy at all.4 The official documentation also treats “check the file and where it came from, and verify that it’s safe, before using it” as a mandatory step.43

It’s worth knowing about the pitfall in the opposite direction too. Not every acquisition route attaches the Mark of the Web. It’s explicitly documented that files downloaded with curl.exe, Invoke-WebRequest, or Invoke-RestMethod may not get the internet-zone mark attached.1 In other words, RemoteSigned is not a guarantee that “every dangerous downloaded file gets stopped” — which is exactly why it’s a safety feature rather than a security boundary. There’s also a caveat that on systems configured not to distinguish UNC paths from internet paths, a script on a shared folder can end up rejected under RemoteSigned.1 If “.ps1 files on a share stop working on only some machines,” suspect the zone configuration and whether Zone.Identifier is present.

By the way, we cover the SmartScreen mechanism that causes a similar symptom on the executable-file (.exe) side in Why Windows Shows “Windows protected your PC”.

5. The Practicalities of Script Signing — Set-AuthenticodeSignature and Timestamps

Moving to an AllSigned operation, or to signing distributed files within a RemoteSigned environment, requires a code-signing certificate. There are three ways to obtain one.5

Acquisition route Scope of trust Verdict
Self-signed (New-SelfSignedCertificate) Your own computer only; won’t run on other machines Testing and verification only. Never use it for distribution57
Issued by an internal CA (certification authority) Machines within the organization configured to trust the internal CA The go-to choice for an AD domain environment. The organization controls issuing and revoking certificates
Issued by a commercial CA (paid) Windows machines in general (public CAs are already trusted) When distributing scripts outside the organization

The official documentation lays it out the same way: “a certificate issued by a certification authority is trusted on other computers as well,” while “a self-created certificate is free but is specific to your own computer, and should be limited to testing purposes.”5 If your goal is internal distribution, it’s realistic to issue a code-signing certificate from an internal CA such as Active Directory Certificate Services.

Let’s first confirm the flow with a test self-signed certificate.

# Create a test code-signing certificate (self-signed — trusted only on this machine)
$params = @{
    Subject           = 'CN=KomuraSoft Code Signing (Test)'
    Type              = 'CodeSigningCert'
    CertStoreLocation = 'Cert:\CurrentUser\My'
    HashAlgorithm     = 'sha256'
}
$cert = New-SelfSignedCertificate @params

New-SelfSignedCertificate is the cmdlet that creates a self-signed certificate for testing purposes; specifying -Type CodeSigningCert adds the code-signing extension. The default validity period is one year.7 If you want to use a self-signed certificate to test an AllSigned environment, it needs to be registered in that machine’s Trusted Root Certification Authorities store.5

The actual signing, in production, is this one line.

# Retrieve the code-signing certificate from the certificate store and sign with it
# -CodeSigningCert just narrows things down to "certificates usable for code signing,"
# so also restrict to ones with a private key that are still within their validity period,
# and in environments with more than one match, identify it further by Subject or Thumbprint
$cert = Get-ChildItem -Path Cert:\CurrentUser\My -CodeSigningCert |
    Where-Object { $_.HasPrivateKey -and $_.NotAfter -gt (Get-Date) -and
                   $_.Subject -eq 'CN=KomuraSoft Code Signing (Test)' } |
    Sort-Object NotAfter -Descending |
    Select-Object -First 1   # When renewal leaves multiple certs with the same Subject, narrow to the one expiring furthest out

# The timestamp is mandatory. It keeps the signature valid even after the certificate expires
# Replace the URL with the timestamp service your certificate's issuer (internal CA / vendor) provides
Set-AuthenticodeSignature -FilePath .\Invoke-NightlyBatch.ps1 -Certificate $cert `
    -HashAlgorithm SHA256 -TimestampServer 'http://timestamp.example.com'

# You can check the signature's status with Get-AuthenticodeSignature (Valid/NotSigned/HashMismatch, etc.)
Get-AuthenticodeSignature -FilePath .\Invoke-NightlyBatch.ps1

There are three specifics worth pinning down.65

  • The signature is embedded as a # SIG # comment block at the end of the file. Any existing signature gets replaced. That means editing even a single character of the script after signing invalidates the signature — this is exactly what functions as tamper detection.
  • Specifying -TimestampServer means the script stops failing once the certificate expires. A signature stays valid either “while the signing certificate itself is valid” or “for as long as the timestamp server can verify that it was signed while the certificate was valid,” and since most code-signing certificates are valid for a year, the timestamp is the lifeline for long-term operation.65 Follow the guidance from your certificate’s issuer (the CA) for which timestamp service URL to use.
  • A script signed under Windows PowerShell 5.1, or PowerShell versions before 7.2, had to be saved as ASCII or UTF8NoBOM. PowerShell 7.2 and later support signed scripts in any encoding.5 At sites where 5.1 is still around, this is a common source of accidents where signature verification breaks over the encoding of a script containing Japanese-language comments, so watch out for it. The broader differences between 5.1 and PowerShell 7, and how to migrate, are a topic worth covering on their own.

In an AllSigned environment, if a script is signed but its publisher hasn’t yet been classified as trusted or blocked, a prompt appears at run time asking “Do you want to run software from this untrusted publisher?” Choosing “Always Run” means you won’t be asked about that publisher again.15 With an internal-CA operation, distributing the publisher certificate to each machine’s “Trusted Publishers” store as well lets you eliminate this prompt from the operation entirely.

6. Standard Practice in a Decision Table — From “Papering Over It With Bypass” to a Signing Operation

The standard way to think through governance of internal script distribution and execution is with the decision table below.

Question Options Rule of thumb
The environment’s policy Leave it Restricted / RemoteSigned / AllSigned RemoteSigned is the floor if you’re pushing automation forward. AllSigned if you have a signing setup1
Distributing the setting Everyone runs Set-ExecutionPolicy individually / Group Policy GPO is the only choice in a domain environment. It outranks every scope and blocks unauthorized changes too1
Trusting distributed files Unsigned + placed on a share / Code signing An unsigned operation can’t detect tampering. Move to signing starting with the operational scripts that run on a schedule
Certificate Self-signed / Internal CA / Commercial CA Internal CA for internal distribution. Self-signed is test-only, commercial CA for external distribution5
Handling downloaded files Loosen the policy / Review, then Unblock-File Leave the policy alone; let only reviewed files through4
Launching scheduled tasks Habitually use -ExecutionPolicy Bypass / Set up the environment policy properly + sign Habitual Bypass abandons the safety feature. It doesn’t even take effect under GPO management anyway1

A note on that last row. The problem with an operation that “papers over things with ExecutionPolicy Bypass” isn’t opening a security hole as such (since the execution policy was never a boundary to begin with). The problem is these three things.

  • Abandoning the safety feature — you permanently remove the last net that catches the accident of running the wrong script by mistake, or running a tampered script without noticing.
  • Drifting away from governance — the moment GPO starts managing the execution policy, the Bypass specification stops working, and every task that had been papering over the issue fails all at once.1 This is technical debt where “the reason it was working” turns out to have been an unmanaged loophole.
  • Confounding root-cause investigation — once Bypass is scattered around, you can no longer infer behavior from the environment’s effective policy, which makes investigating machine-to-machine differences (why does only this machine fail) much harder.

Bypass itself is a setting provided for configurations where an application built on top of PowerShell governs things with its own security model.1 Make peace with the fact that it’s not meant to be used habitually as a launch option for operational scripts a human wrote. We cover building safe, reliable scheduled execution in Task Scheduler in detail in When Task Scheduler Tasks Don’t Run or Exit with 0x1 — Isolating the Cause and Designing for Reliable Operation.

7. Summary

  • The execution policy is a safety feature, not a security boundary. Handle countering attacks at a different layer, and let the execution policy do the job of “preventing accidental mistakes.”
  • The policy has five scopes, with precedence MachinePolicy > UserPolicy > Process > CurrentUser > LocalMachine. Start any troubleshooting from Get-ExecutionPolicy -List.
  • What RemoteSigned looks at is Zone.Identifier (the Mark of the Web). Let reviewed files through with Unblock-File, without loosening the policy.
  • Sign with Set-AuthenticodeSignature, and always specify -TimestampServer. For internal distribution, use a certificate issued by an internal CA; self-signed is test-only.
  • Centralize organizational governance through Group Policy’s “Turn on Script Execution.” This setting takes precedence over every scope and every -ExecutionPolicy specification.
  • Habitually using -ExecutionPolicy Bypass abandons the safety feature, and it has no effect under GPO management anyway. The right path is to make “papering over it” unnecessary by setting up the environment policy properly and running a signing operation.

Komura Software LLC handles the design of execution-policy and signing operations for internal PowerShell scripts, evaluating deployment through Group Policy, and investigating environment-specific differences such as “this script only fails to run on certain machines.” We can also help migrate existing batch-file and script assets onto a safer operational footing.

References

  1. Microsoft Learn, about_Execution_Policies. On the execution policy being a safety feature rather than a security system that restricts users, the definitions of each policy (AllSigned/Bypass/RemoteSigned/Restricted/Unrestricted, etc.), the five scopes and their precedence, the Process scope being stored in $Env:PSExecutionPolicyPreference and unable to beat GPO, the Group Policy “Turn on Script Execution” setting taking precedence over every scope, alternate data streams being attached to downloaded files (with tools like curl.exe sometimes not attaching the mark), and the caveat regarding UNC paths.  2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

  2. Microsoft Learn, Chapter 1 - Getting started with PowerShell. On the execution policy not being a security boundary, on Windows 10/11 defaulting to Restricted and Windows Server 2016/2019/2022 defaulting to RemoteSigned, and on the execution policy only affecting scripts while interactive commands can always be run.  2 3

  3. Microsoft Learn, Set-ExecutionPolicy. On the default scope being LocalMachine and requiring administrator rights, on the MachinePolicy/UserPolicy scopes being unchangeable through this cmdlet, on being unable to override Group Policy (with a message displayed on conflict), and on an example of Unblock-File clearing a script’s block without changing the execution policy.  2 3 4

  4. Microsoft Learn, Unblock-File. On Unblock-File removing the Zone.Identifier alternate data stream that carries the value 3 for the internet zone, on this being the same operation as the “Unblock” checkbox in File Explorer’s Properties, on the need to verify the file and its source are safe before use, and on detecting files carrying Zone.Identifier with Get-Item -Stream.  2 3 4 5

  5. Microsoft Learn, about_Signing. On the file types that can be signed, the difference between a certification-authority-issued certificate and a self-signed one (self-signed is testing-only and won’t run on other computers), on the signature being appended as a # SIG # comment block, on versions before PowerShell 7.2 requiring ASCII/UTF8NoBOM encoding, on a timestamp server keeping the signature valid past the certificate’s expiration, and on the untrusted-publisher prompt.  2 3 4 5 6 7 8 9 10 11 12

  6. Microsoft Learn, Set-AuthenticodeSignature. On applying an Authenticode signature, replacing an existing signature, the -TimestampServer parameter preventing script failure after the certificate expires, and an example of retrieving a code-signing certificate with a private key using the -CodeSigningCert parameter on the Cert: drive.  2 3

  7. Microsoft Learn, New-SelfSignedCertificate. On this being the cmdlet for creating a self-signed certificate for testing purposes, on specifying a code-signing certificate with the -Type parameter, and on the default validity period being one year.  2 3

  8. Microsoft Learn, PowerShell security features. On the execution policy being positioned as one of several features that raise the security of the scripting environment, described as a safety feature that helps prevent malicious scripts from running. 

Recent articles sharing the same tags. Deepen your understanding with closely related topics.

These topic pages place the article in a broader service and decision context.

This article connects naturally to the following service pages.

Frequently Asked Questions

Common questions about the topic of this article.

Is PowerShell's execution policy a security feature?
The official documentation states plainly that "the execution policy is not a security system that restricts user actions." That's because it can be trivially bypassed by pasting a script's contents straight into the command line. The purpose of the execution policy is to set basic rules and act as a safety feature that prevents the accident of "unintentionally running a script you didn't mean to" — you must not design around it as a security boundary meant to stop an attacker. Countering attacks is the job of a different layer (application control, least privilege, and so on).
What's the difference between RemoteSigned and AllSigned?
RemoteSigned requires a trusted publisher's signature only for scripts that came from the internet (carrying the Mark of the Web); locally created scripts can run unsigned. AllSigned requires a signature on every script and configuration file, including locally created ones, and prompts for confirmation before running anything from a publisher it hasn't classified yet. AllSigned is the realistic choice if your organization can build out a signing operation (distributing code-signing certificates and a signing procedure); RemoteSigned is realistic if you don't have that kind of setup in place.
Why does a downloaded script fail to run with a "not digitally signed" error?
Files downloaded through a browser or similar tools get an alternate data stream called Zone.Identifier attached, which marks them as "a file that came from the internet." If your execution policy is RemoteSigned, an unsigned script carrying that mark gets its execution blocked. Once you've reviewed the contents and judged it safe, you can remove the Zone.Identifier stream with the Unblock-File cmdlet, or with the "Unblock" checkbox in the file's Properties in File Explorer, and run it without ever changing your execution policy.
What's a realistic way to operate internally distributed PowerShell scripts?
There are broadly two options. The first is RemoteSigned plus placing scripts on a file server, which lets you start without building a signing setup, but depending on the distribution route the Mark of the Web can get attached and block execution, and it can't detect tampering with a script either. The second is AllSigned plus a signing operation: sign scripts with Set-AuthenticodeSignature using a code-signing certificate (realistically, one issued by an internal CA), and centrally manage the policy through Group Policy. In exchange for gaining control over the execution policy and the ability to detect tampering, you take on the operational cost of distributing and renewing certificates.
Is it fine to keep specifying -ExecutionPolicy Bypass in Task Scheduler?
It will run, but it's not recommended. First, in an environment where the execution policy is managed through Group Policy, a command-line ExecutionPolicy specification can't beat Group Policy, so it doesn't even take effect. Second, habitually using Bypass amounts to removing, by your own hand, the safety feature meant to prevent accidental execution, and it becomes a cause of the gap between organizational policy and what's actually happening on the ground growing wider over time. If you want this to be a permanent operation, the correct approach is to set the environment's policy properly through Group Policy or an administrator's Set-ExecutionPolicy, and have scripts demonstrate trust through signing instead.
Why does script signing need a timestamp (-TimestampServer)?
A signature's validity is, in principle, tied to the signing certificate's expiration date, but with a timestamp, the timestamp server vouches for the fact that "this was signed while the certificate was still valid," so you can keep using the script even after the certificate expires. Most code-signing certificates are valid for around a year, so operating without a timestamp leaves you carrying the risk, every year, that "one day, out of nowhere, every signed script in the company stops working all at once." Always specify -TimestampServer when signing.

Author Profile

Profile page for the article author.

Go Komura

Representative of KomuraSoft LLC

Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.

Back to the Blog