Security

Advanced Windows Security: Restricting PowerShell Execution Policies

Lock down PowerShell on your Windows VPS by setting execution policies per scope, enforcing them with Group Policy, and signing your own scripts so only trusted code runs.

AdvancedWindows server6 min read

A PowerShell execution policy decides which scripts are allowed to run on your Windows VPS. Setting it to AllSigned or RemoteSigned stops downloaded or unsigned .ps1 files from executing, which removes one of the easiest ways for malware to run after it lands on the server.

This guide covers the policy scopes, how to set them from the console and from Group Policy, how to sign your own scripts so your automation keeps working, and where the policy stops being a security boundary.

Before you start

You need:

  • A Windows VPS (Windows Server 2019–2025) with an Administrator account.
  • A Remote Desktop connection to 203.0.113.10:3389. See Connect to your Admin RDP from Windows, Mac and phone if you have not connected before. Port 3389 is the RDP port on Windows servers by default.
  • An elevated PowerShell session. Right-click PowerShell and choose Run as administrator.
  • A maintenance window if the server runs scheduled scripts. Changing the policy to AllSigned will break any unsigned script until you sign it.

Warning: Do not set AllSigned on a production server until every script your tasks and services call is signed. Unsigned scripts fail immediately with an error, and scheduled jobs will silently stop doing their work.

Understand scopes and policy values

Execution policy is applied in a precedence order. The most specific scope wins:

  1. MachinePolicy — set by Group Policy, cannot be overridden locally.
  2. UserPolicy — set by Group Policy for the current user.
  3. Process — applies only to the current PowerShell session.
  4. CurrentUser — stored in the user's registry hive.
  5. LocalMachine — stored in the machine registry hive.

The values you will use most:

  • Restricted — no scripts run. The default on client Windows.
  • RemoteSigned — local scripts run; scripts downloaded from the internet must be signed. A practical default for servers.
  • AllSigned — every script, local or remote, must be signed by a trusted publisher.
  • Undefined — removes the setting at that scope so a lower scope takes over.
  • Bypass — nothing is blocked. Useful for a single command, dangerous as a permanent setting.

Note: Execution policy is not a security boundary. Anyone who can run powershell.exe -ExecutionPolicy Bypass -File script.ps1 can step around it. Treat it as one layer, and pair it with AppLocker or WDAC, Microsoft Defender, and least-privilege accounts.

Check the current policy

Start by seeing what is set at each scope:

PowerShell
Get-ExecutionPolicy -List

Typical output on a fresh server shows Undefined for most scopes and Restricted or RemoteSigned for LocalMachine. To see the effective value:

PowerShell
Get-ExecutionPolicy

You can also read the registry directly:

PowerShell
Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell' |
    Select-Object ExecutionPolicy

PowerShell 7 stores its setting under a different key (HKLM:\SOFTWARE\Microsoft\PowerShellCore\ShellIds\Microsoft.PowerShell). Check the Microsoft documentation for the current path if you run both versions.

Set the policy from the console

Set RemoteSigned for the whole machine:

PowerShell
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine

Confirm the prompt with Y. For a stricter server where every script is signed:

PowerShell
Set-ExecutionPolicy -ExecutionPolicy AllSigned -Scope LocalMachine

To test a value for one session only, without touching the registry:

PowerShell
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process

To revert a scope:

PowerShell
Set-ExecutionPolicy -ExecutionPolicy Undefined -Scope LocalMachine

Tip: After changing the machine policy, open a new PowerShell window and run Get-ExecutionPolicy -List again. Existing sessions keep the value they started with.

Enforce the policy with Group Policy

Local settings can be changed by any administrator, so on a server you manage seriously, enforce the policy through Group Policy. This also keeps the setting consistent if someone edits the registry by hand.

  1. Open gpedit.msc on the server, or edit a domain GPO from a management machine.
  2. Go to Computer Configuration → Policies → Administrative Templates → Windows Components → Windows PowerShell.
  3. Open Turn on Script Execution and set it to Enabled.
  4. In Execution Policy, choose Allow only signed scripts (equivalent to AllSigned) or Allow local scripts and remote signed scripts (RemoteSigned).
  5. Apply, then run gpupdate /force and reopen PowerShell.

Verify the result:

PowerShell
Get-ExecutionPolicy -List

MachinePolicy should now show your chosen value, and it will override anything set at LocalMachine. The same GPO approach is described in Advanced Windows Server: Implementing Group Policy Objects (GPO) for security.

Sign your own scripts

If you choose AllSigned, your automation needs a code-signing certificate. For internal scripts, a self-signed certificate is enough — create one and trust it on the server:

PowerShell
$cert = New-SelfSignedCertificate -Type CodeSigningCert `
    -Subject "CN=Internal Code Signing" `
    -CertStoreLocation "Cert:\LocalMachine\My" `
    -NotAfter (Get-Date).AddYears(3)

Export-Certificate -Cert $cert -FilePath C:\certs\codesign.cer
Import-Certificate -FilePath C:\certs\codesign.cer -CertStoreLocation Cert:\LocalMachine\TrustedPublisher

Sign a script and confirm the signature:

PowerShell
Set-AuthenticodeSignature -FilePath C:\scripts\maintenance.ps1 -Certificate $cert
Get-AuthenticodeSignature -FilePath C:\scripts\maintenance.ps1

The status should read Valid. Re-sign the script every time you edit it — any change invalidates the signature. For scripts that arrive from a vendor, check their signing instructions rather than re-signing their code yourself.

Check it works

Create a test script and try to run it:

PowerShell
"Write-Output 'policy test'" | Out-File C:\scripts\test.ps1 -Encoding utf8
C:\scripts\test.ps1

With AllSigned and no signature, you should see an error like:

C:\scripts\test.ps1 : File C:\scripts\test.ps1 cannot be loaded. The file is not digitally signed.

That is the policy working. Sign the file and run it again — it should print policy test. Also confirm your real scheduled tasks still run: open Task Scheduler, run each task manually, and check its last result code.

Troubleshooting

  • "cannot be loaded because running scripts is disabled on this system" — the effective policy is Restricted. Run Get-ExecutionPolicy -List to find which scope is responsible, then set that scope to RemoteSigned or sign the script.
  • "The file is not digitally signed" — the script is unsigned or was edited after signing. Re-run Set-AuthenticodeSignature, or move the script to a trusted local path if you use RemoteSigned.
  • "A digitally signed file has been tampered with" — the file changed after signing, or the signature uses a certificate that is not trusted. Import the certificate into TrustedPublisher and re-sign from a clean copy.
  • Policy changes do not stick — a GPO is enforcing the value. Check MachinePolicy in Get-ExecutionPolicy -List and edit the GPO instead of the registry.
  • Scheduled tasks stopped after the change — the task calls an unsigned script. Sign it, or set the task action to use a signed wrapper script that you control.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.