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
AllSignedwill 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:
MachinePolicy— set by Group Policy, cannot be overridden locally.UserPolicy— set by Group Policy for the current user.Process— applies only to the current PowerShell session.CurrentUser— stored in the user's registry hive.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:
Get-ExecutionPolicy -ListTypical output on a fresh server shows Undefined for most scopes and Restricted or RemoteSigned for LocalMachine. To see the effective value:
Get-ExecutionPolicyYou can also read the registry directly:
Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell' |
Select-Object ExecutionPolicyPowerShell 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:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachineConfirm the prompt with Y. For a stricter server where every script is signed:
Set-ExecutionPolicy -ExecutionPolicy AllSigned -Scope LocalMachineTo test a value for one session only, without touching the registry:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope ProcessTo revert a scope:
Set-ExecutionPolicy -ExecutionPolicy Undefined -Scope LocalMachineTip: 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.
- Open
gpedit.mscon the server, or edit a domain GPO from a management machine. - Go to Computer Configuration → Policies → Administrative Templates → Windows Components → Windows PowerShell.
- Open Turn on Script Execution and set it to Enabled.
- In Execution Policy, choose Allow only signed scripts (equivalent to
AllSigned) or Allow local scripts and remote signed scripts (RemoteSigned). - Apply, then run
gpupdate /forceand reopen PowerShell.
Verify the result:
Get-ExecutionPolicy -ListMachinePolicy 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:
$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\TrustedPublisherSign a script and confirm the signature:
Set-AuthenticodeSignature -FilePath C:\scripts\maintenance.ps1 -Certificate $cert
Get-AuthenticodeSignature -FilePath C:\scripts\maintenance.ps1The 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:
"Write-Output 'policy test'" | Out-File C:\scripts\test.ps1 -Encoding utf8
C:\scripts\test.ps1With 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. RunGet-ExecutionPolicy -Listto find which scope is responsible, then set that scope toRemoteSignedor 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 useRemoteSigned. - "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
TrustedPublisherand re-sign from a clean copy. - Policy changes do not stick — a GPO is enforcing the value. Check
MachinePolicyinGet-ExecutionPolicy -Listand 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
- Restrict what can execute at all with Advanced Windows Firewall: Blocking Specific Application Executables.
- Harden the rest of the OS in Securing Windows Server: Hardening the Registry and Services.
- Keep the server patched with Install Windows updates from PowerShell with PSWindowsUpdate.
