Letting mere mortals run Windows PowerShell scripts

9 minute read

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.

Windows PowerShell logo with error icon
Image credits: Wikimedia Commons.

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. :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!

  1. 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. 

  2. The default execution policy permits users to run individual commands. 

  3. I.e. bundled as part of Windows 10 and only available up to version 5.x. 

  4. Setting the Default policy for the LocalMachine scope failed as it must be run as the administrator. Now I have this vague feeling that all ExecutionPolicy scopes had the value Undefined 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!

buy me a coffee logo