{"id":651,"date":"2026-08-18T23:51:19","date_gmt":"2026-08-18T23:51:19","guid":{"rendered":"https:\/\/windows-vps.org\/blog\/?p=651"},"modified":"2026-09-19T22:08:19","modified_gmt":"2026-09-19T22:08:19","slug":"powershell-execution-policy-windows-server-script-signing","status":"publish","type":"post","link":"https:\/\/windows-vps.org\/blog\/powershell-execution-policy-windows-server-script-signing\/","title":{"rendered":"PowerShell Execution Policy on Windows Server: Signing Scripts and Blocking the Right Way"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Execution policy is not a security boundary, and treating it as one is why so many Windows Server hardening projects quietly fail. An administrator can bypass any policy with a single command-line switch, so the policy exists to prevent accidental execution, not to stop a determined attacker. Used correctly, though, it is the cheapest control available for stopping a half-edited maintenance script from running on production. This is how to configure it deliberately, with real signing rather than a blanket <code>Unrestricted<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-element-heading wp-block-heading\">The Five Scopes and What They Really Allow<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Policy<\/th><th>Behaviour<\/th><th>Appropriate use<\/th><\/tr><\/thead><tbody><tr><td>Restricted<\/td><td>No scripts run at all; interactive commands only<\/td><td>Default on client Windows<\/td><\/tr><tr><td>AllSigned<\/td><td>Only scripts with a trusted signature run<\/td><td>Production servers with an internal CA<\/td><\/tr><tr><td>RemoteSigned<\/td><td>Local scripts run unsigned; downloaded scripts must be signed<\/td><td>Workstations, dev boxes<\/td><\/tr><tr><td>Unrestricted<\/td><td>Everything runs; download warnings for internet files<\/td><td>Never on a server<\/td><\/tr><tr><td>Bypass<\/td><td>Nothing is blocked or warned<\/td><td>Purpose-built automation hosts only<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Execution policy resolves by scope in a fixed precedence order: MachinePolicy, UserPolicy, Process, CurrentUser, LocalMachine. A Group Policy setting always wins over anything you set locally, which is why <code>Set-ExecutionPolicy<\/code> appears to do nothing on a domain-joined server. Check the effective value and its source before editing anything:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Get-ExecutionPolicy -List<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If <code>MachinePolicy<\/code> shows a value, stop and change the GPO. Local edits are wasted effort.<\/p>\n\n\n\n<h2 class=\"wp-element-heading wp-block-heading\">Build an Internal Code-Signing Certificate<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">For a production <code>AllSigned<\/code> policy you need a code-signing certificate that every server trusts. An internal CA is the right answer; a self-signed certificate works for a closed lab. Create one on the management host and distribute the public part through Group Policy trust:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$cert = New-SelfSignedCertificate `\n  -Subject &quot;CN=Contoso Code Signing&quot; `\n  -Type CodeSigningCert `\n  -KeyUsage DigitalSignature `\n  -CertStoreLocation Cert:\\LocalMachine\\My `\n  -NotAfter (Get-Date).AddYears(3)\n\nExport-Certificate -Cert $cert -FilePath C:\\pki\\codesign.cer<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Deploy <code>codesign.cer<\/code> to Trusted Publishers and Trusted Root Certification Authorities on every server via GPO. Then sign each script and verify the signature chain resolves:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$signed = Set-AuthenticodeSignature -FilePath C:\\Scripts\\Rotate-IISLogs.ps1 `\n  -Certificate $cert `\n  -TimestampServer &quot;http:\/\/timestamp.digicert.com&quot;\n\n$signed.Status   # expect: Valid<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Always timestamp. Without a timestamp counter-signature, every signed script stops validating the moment the certificate expires, and you will be re-signing a whole repository at the worst possible time.<\/p>\n\n\n\n<h2 class=\"wp-element-heading wp-block-heading\">Enforcing AllSigned Without Breaking Scheduled Tasks<\/h2>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Set the policy at machine scope, then test one script.<\/strong> <code>Set-ExecutionPolicy AllSigned -Scope LocalMachine -Force<\/code>, then run a signed maintenance script manually before trusting automation.<\/li><li><strong>Watch for unsigned dot-sourcing.<\/strong> A signed wrapper that dot-sources an unsigned helper fails at runtime, not at signature verification. Sign every file in the call chain.<\/li><li><strong>Fix the Task Scheduler action.<\/strong> Tasks configured with <code>-ExecutionPolicy Bypass<\/code> will look like a policy failure in monitoring. Either sign the script or convert the task to <code>-File<\/code> with a signed path and remove the bypass.<\/li><li><strong>Handle third-party modules.<\/strong> Vendor modules that ship unsigned cannot be loaded under AllSigned. Whitelist them by trust path or request signed builds rather than downgrading the whole policy.<\/li><li><strong>Log blocked attempts.<\/strong> Enable PowerShell script block logging (Event ID 4104) and module logging so a blocked script produces an audit trail instead of a silent failure.<\/li><\/ul>\n\n\n\n<h2 class=\"wp-element-heading wp-block-heading\">Fallback: Constrained Language Mode<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Execution policy stops scripts, but it does not restrict what interactive PowerShell can do. Where you need a real boundary, combine AllSigned with AppLocker or WDAC to force Constrained Language mode, which blocks COM object creation and arbitrary .NET type invocation. That combination is what turns PowerShell from a convenience into a controlled administration surface. Verify mode with <code>$ExecutionContext.SessionState.LanguageMode<\/code> &mdash; expect <code>ConstrainedLanguage<\/code> and not <code>FullLanguage<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Servers built for automation benefit from getting this right early, before dozens of unsigned scripts accumulate. That discipline fits naturally with the deployment patterns in <a href=\"https:\/\/windows-vps.org\/\">the Windows server automation and PowerShell guides<\/a>, where signed artefacts move through the same pipeline as the applications they support.<\/p>\n\n\n\n<h2 class=\"wp-element-heading wp-block-heading\">Migrating an Existing Unsigned Script Estate<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The hard case is not a new server but a five-year-old one with two hundred loosely organised scripts and no record of which still run. Do not switch straight to AllSigned. Audit what is actually scheduled first, using Task Scheduler inventory plus a scan for script files, and export owner and last-run date for each. Retire anything nobody recognises, sign what remains, then flip the policy. Doing the flip first produces a cascade of failures across unrelated maintenance jobs at unpredictable times of day, which is how a hardening project gets quietly reverted.<\/p>\n\n\n\n<h2 class=\"wp-element-heading wp-block-heading\">Common Misconfigurations That Cause Policy Breaks<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">One persistent source of confusion is the difference between machine-scope and user-scope policy. Setting AllSigned at machine scope does not stop a user from running <code>Set-ExecutionPolicy -Scope CurrentUser RemoteSigned<\/code> unless user-scope policy is locked by Group Policy, which then takes precedence and cannot be overridden locally. Where you need a strong boundary, configure the setting in the Computer Configuration node of a GPO so the Process and CurrentUser scopes are effectively pinned. Verify the result with <code>Get-ExecutionPolicy -List<\/code> and confirm that MachinePolicy and UserPolicy are the values actually in force rather than the local ones.<\/p>\n\n\n\n<h2 class=\"wp-element-heading wp-block-heading\">Bypass Is Not the Enemy<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A final note on discipline: automation platforms legitimately need <code>-ExecutionPolicy Bypass<\/code> when they launch a signed wrapper from an agent process. The mistake is leaving the bypass switch on every scheduled task indefinitely, which quietly defeats the policy across the whole estate. Audit task actions quarterly and remove the switch wherever the script is signed and the object is to enforce the chain rather than skip it.<\/p>\n\n\n\n<h2 class=\"wp-element-heading wp-block-heading\">Deployment Checklist<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Set the policy via GPO, distribute the signing certificate to Trusted Publishers, sign with a timestamp, verify with <code>Get-AuthenticodeSignature<\/code>, and monitor Event ID 4104 for blocked executions. Test the whole chain on a disposable <a href=\"https:\/\/windows-vps.org\/\">Windows test instance<\/a> before you touch production, because a wrong policy on a live server means every scheduled task that shells out to PowerShell fails at the same moment.<\/p>\n\n","protected":false},"excerpt":{"rendered":"<p>Understand the five PowerShell execution policies, build an internal code-signing certificate in 2026, and enforce AllSigned on production servers without breaking scheduled tasks.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":2,"footnotes":""},"categories":[5],"tags":[],"class_list":["post-651","post","type-post","status-publish","format-standard","hentry","category-tutorials-guides"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v26.1 (Yoast SEO v26.1) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>PowerShell Execution Policy on Windows Server: Signing Scripts and Blocking the Right Way - Windows VPS Blog<\/title>\n<meta name=\"description\" content=\"Understand the five PowerShell execution policies, build an internal code-signing certificate in 2026, and enforce AllSigned on production servers without breaking scheduled tasks.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/windows-vps.org\/blog\/powershell-execution-policy-windows-server-script-signing\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"PowerShell Execution Policy on Windows Server: Signing Scripts and Blocking the Right Way\" \/>\n<meta property=\"og:description\" content=\"PowerShell Execution Policy on Windows Server: Signing Scripts and Blocking the Right Way\" \/>\n<meta property=\"og:url\" content=\"https:\/\/windows-vps.org\/blog\/powershell-execution-policy-windows-server-script-signing\/\" \/>\n<meta property=\"og:site_name\" content=\"Windows VPS Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-08-18T23:51:19+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-09-19T22:08:19+00:00\" \/>\n<meta name=\"author\" content=\"windows-vps\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@myxiechengxuan\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"windows-vps\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"5 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/windows-vps.org\/blog\/powershell-execution-policy-windows-server-script-signing\/\",\"url\":\"https:\/\/windows-vps.org\/blog\/powershell-execution-policy-windows-server-script-signing\/\",\"name\":\"PowerShell Execution Policy on Windows Server: Signing Scripts and Blocking the Right Way - Windows VPS Blog\",\"isPartOf\":{\"@id\":\"https:\/\/windows-vps.org\/blog\/#website\"},\"datePublished\":\"2026-08-18T23:51:19+00:00\",\"dateModified\":\"2026-09-19T22:08:19+00:00\",\"author\":{\"@id\":\"https:\/\/windows-vps.org\/blog\/#\/schema\/person\/44caceed916d0db318aa08d5623a7a58\"},\"description\":\"Understand the five PowerShell execution policies, build an internal code-signing certificate in 2026, and enforce AllSigned on production servers without breaking scheduled tasks.\",\"breadcrumb\":{\"@id\":\"https:\/\/windows-vps.org\/blog\/powershell-execution-policy-windows-server-script-signing\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/windows-vps.org\/blog\/powershell-execution-policy-windows-server-script-signing\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/windows-vps.org\/blog\/powershell-execution-policy-windows-server-script-signing\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/windows-vps.org\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"PowerShell Execution Policy on Windows Server: Signing Scripts and Blocking the Right Way\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/windows-vps.org\/blog\/#website\",\"url\":\"https:\/\/windows-vps.org\/blog\/\",\"name\":\"Windows VPS Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/windows-vps.org\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/windows-vps.org\/blog\/#\/schema\/person\/44caceed916d0db318aa08d5623a7a58\",\"name\":\"windows-vps\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/windows-vps.org\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/3f2573db5afcd1a6ab9abcc5d48fc8e42584bc87ab9d98cc156e5b2097766dd9?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/3f2573db5afcd1a6ab9abcc5d48fc8e42584bc87ab9d98cc156e5b2097766dd9?s=96&d=mm&r=g\",\"caption\":\"windows-vps\"},\"sameAs\":[\"https:\/\/windows-vps.org\/blog\",\"https:\/\/x.com\/myxiechengxuan\"],\"url\":\"https:\/\/windows-vps.org\/blog\/author\/myxiechengxuan\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"PowerShell Execution Policy on Windows Server: Signing Scripts and Blocking the Right Way - Windows VPS Blog","description":"Understand the five PowerShell execution policies, build an internal code-signing certificate in 2026, and enforce AllSigned on production servers without breaking scheduled tasks.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/windows-vps.org\/blog\/powershell-execution-policy-windows-server-script-signing\/","og_locale":"en_US","og_type":"article","og_title":"PowerShell Execution Policy on Windows Server: Signing Scripts and Blocking the Right Way","og_description":"PowerShell Execution Policy on Windows Server: Signing Scripts and Blocking the Right Way","og_url":"https:\/\/windows-vps.org\/blog\/powershell-execution-policy-windows-server-script-signing\/","og_site_name":"Windows VPS Blog","article_published_time":"2026-08-18T23:51:19+00:00","article_modified_time":"2026-09-19T22:08:19+00:00","author":"windows-vps","twitter_card":"summary_large_image","twitter_creator":"@myxiechengxuan","twitter_misc":{"Written by":"windows-vps","Est. reading time":"5 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/windows-vps.org\/blog\/powershell-execution-policy-windows-server-script-signing\/","url":"https:\/\/windows-vps.org\/blog\/powershell-execution-policy-windows-server-script-signing\/","name":"PowerShell Execution Policy on Windows Server: Signing Scripts and Blocking the Right Way - Windows VPS Blog","isPartOf":{"@id":"https:\/\/windows-vps.org\/blog\/#website"},"datePublished":"2026-08-18T23:51:19+00:00","dateModified":"2026-09-19T22:08:19+00:00","author":{"@id":"https:\/\/windows-vps.org\/blog\/#\/schema\/person\/44caceed916d0db318aa08d5623a7a58"},"description":"Understand the five PowerShell execution policies, build an internal code-signing certificate in 2026, and enforce AllSigned on production servers without breaking scheduled tasks.","breadcrumb":{"@id":"https:\/\/windows-vps.org\/blog\/powershell-execution-policy-windows-server-script-signing\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/windows-vps.org\/blog\/powershell-execution-policy-windows-server-script-signing\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/windows-vps.org\/blog\/powershell-execution-policy-windows-server-script-signing\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/windows-vps.org\/blog\/"},{"@type":"ListItem","position":2,"name":"PowerShell Execution Policy on Windows Server: Signing Scripts and Blocking the Right Way"}]},{"@type":"WebSite","@id":"https:\/\/windows-vps.org\/blog\/#website","url":"https:\/\/windows-vps.org\/blog\/","name":"Windows VPS Blog","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/windows-vps.org\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/windows-vps.org\/blog\/#\/schema\/person\/44caceed916d0db318aa08d5623a7a58","name":"windows-vps","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/windows-vps.org\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/3f2573db5afcd1a6ab9abcc5d48fc8e42584bc87ab9d98cc156e5b2097766dd9?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/3f2573db5afcd1a6ab9abcc5d48fc8e42584bc87ab9d98cc156e5b2097766dd9?s=96&d=mm&r=g","caption":"windows-vps"},"sameAs":["https:\/\/windows-vps.org\/blog","https:\/\/x.com\/myxiechengxuan"],"url":"https:\/\/windows-vps.org\/blog\/author\/myxiechengxuan\/"}]}},"_links":{"self":[{"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/posts\/651","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/comments?post=651"}],"version-history":[{"count":4,"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/posts\/651\/revisions"}],"predecessor-version":[{"id":767,"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/posts\/651\/revisions\/767"}],"wp:attachment":[{"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/media?parent=651"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/categories?post=651"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/tags?post=651"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}