Letting mere mortals run Windows PowerShell scripts
While playing with Raku and
rakubrew
on Windows recently, I
encountered more than one stumbling block. One stuck out in particular. Did
you know that, by default, Windows PowerShell doesn’t allow normal users to
run scripts? Coming from a Unix background, that surprised me. You might
come across this issue when setting up rakubrew
in Windows PowerShell,
hence I thought I’d share my solution.
An unexpected error
I’ll admit it: the issue I describe here doesn’t have all that much to do
with Raku. It has much more to do with me stumbling over default Windows
policies. It’s something I noticed after installing rakubrew
in Windows
PowerShell and hence could affect others. This might stop them from
installing rakubrew
and they may well avoid learning Raku. That would be
bad.
The issue appeared after installing rakubrew
in PowerShell on
Windows:
# install rakubrew
. {iwr -useb https://rakubrew.org/install-on-powershell.ps1 } | iex
# add rakubrew initialisation code to PowerShell startup script
New-Item -Path (Split-Path $PROFILE) -ItemType "Directory" -Force
Add-Content -Force -Path $PROFILE -Value '. "C:\rakubrew\bin\rakubrew.exe" init PowerShell | Out-String | Invoke-Expression'
I started a fresh shell to initialise the rakubrew
environment, yet I
received this error message instead:
. : File C:\Users\cochrane\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.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.
At line:1 char:3
+ . 'C:\Users\cochrane\Documents\WindowsPowerShell\Microsoft.PowerShell ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : SecurityError: (:) [], PSSecurityException
+ FullyQualifiedErrorId : UnauthorizedAccess
This surprised me. I’d expected the shell to start and provide me with the
rakubrew
command so I could get on with my day. Why did the installation
and setup instructions (seemingly) not work? The reason had to do with
Windows PowerShell execution policies.
A shortcut to a solution
The default execution policy for Windows clients is
Restricted
,
forbidding users from running scripts. The documentation states that this
setting:
- Permits individual commands, but does not allow scripts.
- Prevents running of all script files, including formatting and configuration files (
.ps1xml
), module script files (.psm1
), and PowerShell profiles (.ps1
).
The solution is to allow the current user to run scripts. Giving users the
RemoteSigned
permission1 is sufficient for that
purpose. To set this policy, run:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Now a mere mortalnormal user can run script files. Also, the rakubrew
initialisation now runs without error when Windows PowerShell starts. Yay!
\o/
Many details
Of course, the situation is much more nuanced than the terse solution described above. For those so inclined, I provide more background information than you thought you wanted. Details are often critical when dealing with computing systems, hence why I mention them here. And, as usual, when I have the opportunity to investigate something like this, I’m thorough!
Being direct gets you what you want
Did you notice that we run a script as part of the rakubrew
installation,
i.e.
. {iwr -useb https://rakubrew.org/install-on-powershell.ps1 } | iex
but it doesn’t cause an error? Doesn’t that seem a bit weird to you? Well,
technically the script isn’t run by Windows PowerShell; its contents are
passed to iex
, which executes them. Since we’re running these commands by
hand, we’re allowed to run
them2.
This explains why we don’t see an error when installing rakubrew
.
Fear its presence
The remaining rakubrew
installation steps create the Windows PowerShell
user directory
New-Item -Path (Split-Path $PROFILE) -ItemType "Directory" -Force
and then the Windows PowerShell profile file
Add-Content -Force -Path $PROFILE -Value '. "C:\rakubrew\bin\rakubrew.exe" init PowerShell | Out-String | Invoke-Expression'
filling it with the content
. "C:\rakubrew\bin\rakubrew.exe" init PowerShell | Out-String | Invoke-Expression
The content isn’t important for the execution policy error to occur: it’s the presence of the profile file itself.
You can try this at home. Enable the default execution policy like so:
Set-ExecutionPolicy -ExecutionPolicy Default -Scope CurrentUser
This sets the CurrentUser
’s policy to Restricted
, which the
Get-ExecutionPolicy -List
command shows:
PS C:\Users\cochrane> Get-ExecutionPolicy -List
Scope ExecutionPolicy
----- ---------------
MachinePolicy Undefined
UserPolicy Undefined
Process Undefined
CurrentUser Restricted
LocalMachine Undefined
Windows PowerShell expects its profile file to be at the location in the
$PROFILE
environment variable. You can find out the value of this
environment variable by typing its name into the shell:
PS C:\Users\cochrane> $PROFILE
C:\Users\cochrane\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
Now create an empty file at this location e.g. via the New-Item
command:
PS C:\Users\cochrane> New-Item $PROFILE
Directory: C:\Users\cochrane\Documents\WindowsPowerShell
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 26/03/2024 17:21 0 Microsoft.PowerShell_profile.ps1
Starting a fresh Windows PowerShell session greets us with our now familiar error message:
. : File
C:\Users\cochrane\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.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.
At line:1 char:3
+ . 'C:\Users\cochrane\Documents\WindowsPowerShell\Microsoft.PowerShell ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : SecurityError: (:) [], PSSecurityException
+ FullyQualifiedErrorId : UnauthorizedAccess
Thus only the presence of this file causes the error when a user is
Restricted
.
A shell by any other version
One suspicion I had for why this problem appeared was that Windows PowerShell was too old, or perhaps there was a problem with my system.
Checking my Windows PowerShell version metadata showed that it might be a bit old:
PS C:\Users\cochrane> $PSVersionTable
Name Value
---- -----
PSVersion 5.1.19041.4170
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.19041.4170
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
I’m running Windows 10 and the system is completely up-to-date, so things should be ok.
Microsoft also mentions that there are few differences in the PowerShell language between Windows PowerShell and PowerShell. Thus it’s unlikely that the Windows PowerShell version is causing this issue or is in any way special.
It’s also possible that this is just a case of PEBKAC and I changed some setting ages ago that I now can’t recall. Despite that, I rarely use PowerShell and consider this explanation unlikely.
From the information I was able to find, my version of Windows PowerShell seems to be fine.
Different PowerShell, different behaviour
Of course, as soon as I started wondering if the problem was due to an outdated version, I considered upgrading. Because they are two different projects, one can install PowerShell version 7 in parallel to Windows PowerShell, which I then did.
We have to be careful when using the name “PowerShell” because we need to differentiate between the two shells. Due to the similar names, I use “PowerShell” to refer to the new shell and “Windows PowerShell” to refer to the legacy program.3
Get this though: PowerShell has a different default execution policy to Windows PowerShell 🙄.
Here is PowerShell’s default execution policy list:
PS C:\Users\cochrane> Get-ExecutionPolicy -List
Scope ExecutionPolicy
----- ---------------
MachinePolicy Undefined
UserPolicy Undefined
Process Undefined
CurrentUser Undefined
LocalMachine RemoteSigned
And here is Windows PowerShell’s default execution policy list:
PS C:\Users\cochrane> Get-ExecutionPolicy -List
Scope ExecutionPolicy
----- ---------------
MachinePolicy Undefined
UserPolicy Undefined
Process Undefined
CurrentUser Restricted
LocalMachine Undefined
Note how PowerShell has the policy RemoteSigned
in the LocalMachine
scope. This means anyone on the local machine can run scripts. I discovered
this after a fresh install of PowerShell. The behaviour thus directly
contradicts the PowerShell
documentation!
So, after installing rakubrew
and adding its startup code to the profile
file, PowerShell sessions start without a problem. It’s what any new
rakubrew
user would want.
Grokking the docs (or trying to)
Computer systems are complex, intricate beasts and it can be hard to find relevant information in the documentation. For instance, although the Windows Execution Policy documentation states that
The execution policy for a particular session is stored only in memory and is lost when the session is closed.
the execution policy I set in Windows PowerShell was persistent; even across reboots. I don’t know what the docs are trying to tell me here. Call me confused.
So what is the default Windows PowerShell execution policy? The introductory (long description) section of the PowerShell documentation states that
On non-Windows computers, the default execution policy is
Unrestricted
and cannot be changed.
But this section doesn’t mention what the default execution policy is for Windows computers. Where is that information hiding?
One has to dig a bit harder to find it:
Default
- Sets the default execution policy.
Restricted
for Windows clients.RemoteSigned
for Windows servers.
Thus, one can run scripts on Windows servers (RemoteSigned
) but not as a
normal user on Windows client computers (Restricted
).
Further digging uncovered more nuggets of information:
Restricted
- The default execution policy for Windows client computers.
including:
If no execution policy is set in any scope, the effective execution policy is
Restricted
, which is the default for Windows clients.
So, the default execution policy on Windows clients (presumably for the
CurrentUser
or the LocalMachine
) is Restricted
. I think it would be
helpful to mention this in the introductory description. Even so, taking the
time to peruse the documentation did help me form a solution to my
permissions problem.
Defaulting on a promise
Setting the Default
policy without setting an explicit
scope4 gave this error
PS C:\Users\cochrane> Set-ExecutionPolicy -ExecutionPolicy Default
Execution Policy Change
The execution policy helps protect you from scripts that you do not trust. Changing the execution policy might expose
you to the security risks described in the about_Execution_Policies help topic at
https:/go.microsoft.com/fwlink/?LinkID=135170. Do you want to change the execution policy?
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "N"): Y
Set-ExecutionPolicy : Access to the registry key
'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell' is denied. To change the execution
policy for the default (LocalMachine) scope, start Windows PowerShell with the "Run as administrator" option. To
change the execution policy for the current user, run "Set-ExecutionPolicy -Scope CurrentUser".
At line:1 char:1
+ Set-ExecutionPolicy -ExecutionPolicy Default
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : PermissionDenied: (:) [Set-ExecutionPolicy], UnauthorizedAccessException
+ FullyQualifiedErrorId : System.UnauthorizedAccessException,Microsoft.PowerShell.Commands.SetExecutionPolicyCommand
Even so, it’s possible to set the execution policy for the current user:
PS C:\Users\cochrane> Set-ExecutionPolicy -ExecutionPolicy Default -Scope CurrentUser
Execution Policy Change
The execution policy helps protect you from scripts that you do not trust.
Changing the execution policy might expose you to the security risks described in the about_Execution_Policies help topic at
https:/go.microsoft.com/fwlink/?LinkID=135170. Do you want to change the execution policy?
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "N"): Y
Checking the list of execution policies gives the value we expect from the documentation:
PS C:\Users\cochrane> Get-ExecutionPolicy -List
Scope ExecutionPolicy
----- ---------------
MachinePolicy Undefined
UserPolicy Undefined
Process Undefined
CurrentUser Restricted
LocalMachine Undefined
Making this change disallowed script execution (as expected) and gave the
error that led me down this particular garden path. This leads me to believe
that this could have been the setting causing my initial woes. Note also
that setting this value to Undefined
leads to the same error. With that
set, the default execution policy kicks in, disallowing the user from
running any scripts.
Finding use in a jumble of trifles
So, what did we learn?
-
rakubrew
installations on Windows PowerShell can raise unexpected errors, - the execution policy for Windows PowerShell must allow users to run
scripts for
rakubrew
to work as expected, - Windows PowerShell and PowerShell are two separate things, seemingly with different default behaviours,
- documentation doesn’t always match reality, and
- the devil is always in the details.
I hope this information was useful. Happy hacking!
-
I find this execution policy name rather opaque. It doesn’t explain its purpose clearly: that of allowing scripts to run. After a bit of digging in the documentation, I guess it means “be able to run stuff like on a Windows server”. A Windows server is usually a remote machine and in that environment, one would need to run scripts. It’s a tenuous theory at best, though. ↩
-
The default execution policy permits users to run individual commands. ↩
-
I.e. bundled as part of Windows 10 and only available up to version 5.x. ↩
-
Setting the
Default
policy for theLocalMachine
scope failed as it must be run as the administrator. Now I have this vague feeling that allExecutionPolicy
scopes had the valueUndefined
when I started down this path. Oh well. ↩
Support
If you liked this post and want to see more like this, please buy me a coffee!