You try to run a script and PowerShell refuses:
File C:\Scripts\demo.ps1 cannot be loaded because running scripts is disabled
on this system. For more information, see about_Execution_Policies at
https:/go.microsoft.com/fwlink/?LinkID=135170.
+ CategoryInfo : SecurityError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : UnauthorizedAccess
Or this one, which is a different problem with the same cause:
File C:\Scripts\demo.ps1 cannot be loaded. The file C:\Scripts\demo.ps1 is not
digitally signed. You cannot run this script on the current system.
Every answer you will find says to run Set-ExecutionPolicy. That is usually right and often does not work, because there are three separate things that produce these messages and only one of them is fixed that way.
First, what execution policy is not
It is not a security boundary. Microsoft say so themselves in the documentation. Anyone who can run PowerShell can bypass it in about four keystrokes, and I will show you how further down.
It exists to stop you double-clicking something by accident. Treat it as a seatbelt, not a lock.
The five scopes, and which one is winning
This is the part that catches people. There is not one execution policy, there are five, and they override each other in a fixed order:
MachinePolicy -> UserPolicy -> Process -> CurrentUser -> LocalMachine
Highest wins. So Get-ExecutionPolicy on its own tells you the effective answer but not which scope produced it. Always ask for the list:
Get-ExecutionPolicy -List
Here is my own machine:
Scope ExecutionPolicy
----- ---------------
MachinePolicy Undefined
UserPolicy Undefined
Process Bypass
CurrentUser Undefined
LocalMachine RemoteSigned
The effective policy is Bypass, not RemoteSigned. Something set it at Process scope for this session, and Process beats LocalMachine.
The important thing to note is that MachinePolicy and UserPolicy come from Group Policy. If either of those is set, nothing you type will change anything, and you will spend an afternoon finding out. Check those two first.
Fix 1 – set it for yourself only
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
-Scope CurrentUser does not need administrator rights, and it does not change the machine for anybody else. On a shared or managed machine that matters.
RemoteSigned is the sensible setting: scripts you wrote locally run, scripts that came from somewhere else need a signature. It is the default on Windows Server for that reason.
Fix 2 – bypass it for one command
If you cannot change the policy, or do not want to:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Scripts\demo.ps1
This changes nothing permanently. It applies to that one process and then it is gone.
I ran the same blocked script three ways to show the difference:
-ExecutionPolicy Restricted "running scripts is disabled on this system"
-ExecutionPolicy AllSigned "is not digitally signed"
-ExecutionPolicy Bypass the script ran
Which is also the demonstration that execution policy is not security. Four extra words on the command line and the “blocked” script runs.
Fix 3 – the one nobody mentions
Here is the case that makes people think the policy is broken.
Your policy is RemoteSigned. You wrote a script locally, it runs fine. A colleague emails you one, or you download it, and it refuses – with the “not digitally signed” message rather than the “disabled on this system” one.
The policy is correct. The file is marked.
Windows adds a hidden alternate data stream to anything that arrives from the internet or an email client. You can see it:
Get-Item .\demo.ps1 -Stream *
Stream Length
------ ------
:$DATA 30
Zone.Identifier 26
That Zone.Identifier stream is the mark of the web, and RemoteSigned is doing exactly what it says – treating the file as remote.
Remove it:
Unblock-File -Path .\demo.ps1
Same script, same policy, straight afterwards:
the script ran
Note that Unblock-File only removes the mark. It does not weaken your policy and it does not affect any other file. It is the right fix for this case, and changing the execution policy to work around it is the wrong one.
For a folder of scripts:
Get-ChildItem .\Scripts\*.ps1 -Recurse | Unblock-File
Fix 4 – you fixed the wrong PowerShell
This one cost me time, so it is worth knowing.
Windows PowerShell 5.1 and PowerShell 7 keep their execution policies in completely separate places. Setting it in one does nothing for the other.
They are different registry keys:
Windows PowerShell 5.1 HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell
PowerShell 7 (Core) HKLM\SOFTWARE\Microsoft\PowerShellCore\ShellIds\Microsoft.PowerShell
On my machine 5.1 has RemoteSigned set at both LocalMachine and CurrentUser, and the PowerShell 7 keys do not exist at all.
So if you opened the blue window, ran Set-ExecutionPolicy, and then went back to the black window and got the same error – that is why. Run $PSVersionTable.PSVersion in whichever one is refusing, and fix that one.
The order I would check them in
Get-ExecutionPolicy -List– find which scope is actually winning- If MachinePolicy or UserPolicy is set, stop. That is Group Policy and it is not your decision
Get-Item .\script.ps1 -Stream *– if there is a Zone.Identifier, Unblock-File is your fix- Check which PowerShell you are in
- Then, and only then,
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
Four of those five cost you nothing and change nothing. Reaching for Set-ExecutionPolicy first is how people end up with an unrestricted machine and a script that still does not run 🙂