PowerShells Execution Policy is such an essential setting, yet it seems many users don’t fully understand what it is for, how it works, and what the best setting is. So here is an effort to clean this up a bit, and you are cordially invited to add comments for anything missing.
Execution Policy – what it is designed to do
PowerShells execution policy aims to protect inexperienced users from running potentially harmful code.
That’s a noble cause, but how do you know whether code is harmful? Execution policy does not look at script content. It is solely trust-based, and uses two indicators of trust:
- Territory (Origin): Whenever a script file is located on a local drive, or a network drive within your domain, or a network drive that is part of your trusted network locations (Internet Explorer Zones), it is considered trustworthy. Whenever a script is located anywhere else, or downloaded from the Internet, or received by email, it is considered coming from an untrusted source. Confusing: execution policy labels untrusted origin as “Remote”. So a script file stored on a file share inside your domain is not “Remote”, whereas the same file on a file server outside your domain would be considered “Remote”. Limitations: Downloaded scripts are marked with a hidden NTFS stream (unless you decide to store the file on a FAT32 USB stick in which case it becomes trusted since it is located on a local drive).
- Trusted Authors: Whenever a script file contains a valid digital signature, it is considered trustworthy regardless of where it is stored. Execution policy calls this “Signed”. Digital signatures thus can be used to override territory constraints.
Deciphering Execution Policies
You can always see your effective execution policy by running Get-ExecutionPolicy.
There are seven execution policies to choose from, and from what you just learned, you can now decipher the meaning:
|All Signed||Any code (regardless of origin) needs to carry a valid digital signature. Since most code does not contain valid signatures, this effectively disables all scripts and script-based modules. Don’t ever use this setting unless you personally control every single PowerShell script on a machine. Remember: PowerShell scripts are regular part of many modern software installs, and most of them do not carry a valid digital signature.|
|Bypass||Do not check territory (origin), and do not check signatures. This is the only setting that effectively turns off execution policy and the checks attached to it which can be useful if these checks cause excessive delays.|
|Default||Scripts cannot run|
|RemoteSigned||Any code from a “remote” location (untrusted source) needs to carry a valid digital signature, and since most scripts in the wild do not carry valid digital signatures, it effectively disables scripts that were downloaded from the Internet.|
|Restricted||Scripts cannot run|
|Undefined||No policy specified|
|Unrestricted||Do check territory (origin) and signatures, and display a prompt if the script origins from an untrusted source.|
Five Execution Policies
There are in fact five locations where execution policy can be defined. To determine the effective one, PowerShell traverses them from top to bottom, and the first setting that is not “Undefined” becomes the effective setting. If all settings are set to “Undefined”, PowerShell uses the setting “Restricted”.
The first two scopes, MachinePolicy and UserPolicy, can only be set by Group Policy. They must always be “Undefined”. If you see anything else here, this is a configuration error that can have severe consequences. And here is why:
- ExecutionPolicy is a preference. A user (and code) should always be able to change this setting. It is not a security boundary, nor is it a measure to fight bad guys (like a firewall could). By setting the execution policy via machine or user policy, a user has no longer the ability to change this setting. A user could still set execution policy on a lower scope, but since PowerShell reads these settings top to bottom, it would never get there.
- Some companies don’t want to accept this, and insist on using execution policy like a firewall or a software restriction policy. That’s bad for a number of reasons. First of all, execution policy never lives up to this expectation. There are numerous ways of circumventing it. Secondly, it can cause harm and grief. When code runs powershell.exe and submits the parameter -ExecutionPolicy, then this setting is set on the scope “Process”. If there are GPO settings on top of “Process”, this setting will never apply, and software may not install or run with issues.
- There seems to be a bug that hits once execution policy is set to these two scopes. It can cause load times to increase drastically, so it may take 5 seconds instead of a fraction of a second for a script to start, and >30s to import modules.
Instead, to set a sensible default execution policy like RemoteSigned, you should establish a group policy that writes the setting directly to HKEY_LOCAL_MACHINE (scope “LocalMachine”). This way, any user would start with a good default but would be able to change it whenever needed. In addition, you are not affected by the bug that can cause excessive file load times.
Delays caused by Execution Policy
Execution policy does come with a price tag. Pay day is due when you open a script or module that carries a digital signature.
Whenever this occurs, execution policy needs to evaluate whether the digital signature is valid which is an expensive procedure. It needs to do this every time it opens a script. While this typically slows down PowerShell only marginally, it is a slow down.
Summing it up
Here are my very personal guide lines for PowerShells execution policy. There are all kinds of opinions on this. Always make up your mind yourself, based on a thorough understanding.
- Never set a restrictive execution policy (Scope MachinePolicy/UserPolicy, through GPO). Execution policy must be a preference setting. Setting execution policy via GPO to the scopes Machine/UserPolicy can cause problems with scripts, and can cause extensive load times for script files.
- Never use execution policy as part of a security boundary. It is not preventing bad people to run scripts. It is designed to help good (yet inexperienced) people to not accidentally run bad scripts. This is why it is perfectly ok that execution policy can be overridden by a user, or circumvented. To prove the point that execution policy is just a user preference and no security boundary, check out the many ways available to circumvent execution policy.
- If you deal with digitally signed scripts and modules, and they take a long time to start or import (> 30s), either check your Internet access (it may be blocking access to resources needed in the process of checking digital signatures), or set execution policy to “Bypass”.
- Digital signatures in script code are ultimately useful, but it may make more sense to use Get-AuthenticodeSignature and audit all of your PowerShell script files in regular intervals as part of a security defense strategy – rather than checking signatures every time you run a file.
- As an experienced user, consider what benefit execution policy really provides for you. If you find that it provides no significant benefit, you may want to turn it off by using the setting “Bypass”.
- A good default “hurdle” for users is “RemoteSigned”: it prevents completely inexperienced users from (easily) running code downloaded from the Internet. This default should be set in scope “LocalMachine” so users can change it when they are ready for it or feel they need it. They are ready for it once they found out how to change this setting. If you don’t feel intimidated by RemoteSigned, then stick to this recommended default setting.