A PowerShell script is plain text — anyone can open Deploy.ps1 in Notepad and change what it does. Signing a PowerShell script with a code signing certificate does two things: it proves which publisher produced the script, and it lets Windows detect if a single byte has changed since it was signed.
Windows PowerShell 5.1 and PowerShell 7 both enforce this through the execution policy. Under the AllSigned or RemoteSigned policies, an unsigned — or tampered — script is refused. Signing your scripts is what lets them run on locked-down machines.
Signotaur signs PowerShell scripts exactly the same way it signs an .exe: there is no separate command and no PowerShell-specific option. The signing certificate's private key stays on the Signotaur server and never touches the build machine.
RemoteSigned and AllSigned policies refuse to run unsigned scripts. If your scripts run on servers or managed desktops, they almost certainly need to be signed.PowerShell signing is Authenticode signing — the same technology used for .exe and .dll files. SignotaurTool.exe sign detects these file types automatically by extension:
| Extension | File type |
|---|---|
.ps1 |
PowerShell script |
.psm1 |
PowerShell script module |
.psd1 |
PowerShell module manifest / data file |
.ps1xml |
PowerShell format and type definition data |
.cdxml |
Cmdlet definition XML (CDXML) module |
Because these are known file types, no --unsupported-file-types flag is needed — Signotaur routes them through Authenticode signing automatically.
Use the sign command with the script path. This is the same command you would use for an .exe — there are no PowerShell-specific flags:
SignotaurTool.exe sign -a <APIKey> -s <SignServer> -t <Thumbprint> --fd SHA256 --tr http://timestamp.digicert.com --td SHA256 Deploy.ps1
To sign every script in a module or repository, use wildcards:
SignotaurTool.exe sign -a <APIKey> -s <SignServer> --label production --fd SHA256 --tr http://timestamp.digicert.com --td SHA256 src\**\*.ps1 src\**\*.psm1
The other common sign options work as usual. See the sign command reference for full details. For CI/CD pipelines, prefer --label over --thumbprint so a renewed certificate is picked up automatically without editing your build scripts.
Authenticode signs a PowerShell file by appending the signature as a comment block at the very end of the file:
# SIG # Begin signature block
# MIIm... (base64-encoded PKCS#7 signature data)
# SIG # End signature block
Every line in the block starts with #, so PowerShell treats it as a comment and the script runs exactly as before — but Windows reads the block when the execution policy validates the script. Re-signing a script simply replaces the existing block; you do not need to remove it first.
Signing a script only has an effect if the machine that runs it has an execution policy that requires a signature. Check the current policy:
Get-ExecutionPolicy -List
The two policies that require signed scripts are:
Set a policy (the LocalMachine scope requires an elevated prompt):
Set-ExecutionPolicy RemoteSigned -Scope LocalMachine
Signing the script is only half the job — the machine that runs it must trust the certificate that signed it.
If the certificate chains to a commercial root (DigiCert, Sectigo, GlobalSign, …), that trust is already present on every Windows machine and no recipient action is needed.
If it chains to an internal or self-signed CA, import the CA certificate into the machine's Trusted Root Certification Authorities store. Under AllSigned, also import the signing certificate into Trusted Publishers — otherwise the first run prompts "Do you want to run software from this untrusted publisher?". Without trusted chain, a correctly signed script is still refused with an "is not digitally signed" error.
PowerShell has a built-in cmdlet for checking a script's signature:
Get-AuthenticodeSignature .\Deploy.ps1
A Status of Valid means the signature is intact and the publisher is trusted on this machine. A status of NotTrusted or UnknownError usually means the signing certificate's chain is not trusted on the machine doing the check — see the note above.
You can also verify with SignotaurTool, which additionally checks the certificate chain and any embedded timestamp:
SignotaurTool.exe verify Deploy.ps1
See the verify command reference for full syntax.
Always pass a timestamp server (--tr) and digest (--td) when signing — the examples above already do. Timestamping matters for scripts that need to keep working over time:
AllSigned it will no longer run at all.Unlike .rdp files — where mstsc.exe ignores the timestamp — PowerShell does honour the embedded timestamp, so a timestamped script keeps running after its certificate expires.
The signing certificate must carry the Code Signing Enhanced Key Usage (EKU). PowerShell signing uses Authenticode, so a standard RSA code-signing certificate works; an ECDSA certificate also works on Windows 8 and later. For algorithm choice, key sizes, and the CA/Browser Forum minimum key size for publicly-trusted certificates, see Code Signing Certificates.
| Symptom | Likely cause | Fix |
|---|---|---|
Deploy.ps1 cannot be loaded. The file Deploy.ps1 is not digitally signed. |
The script is unsigned, was modified after signing, or the signing certificate's chain is not trusted on this machine | Sign the script; if it is already signed, import the issuing CA into Trusted Root Certification Authorities |
... cannot be loaded because running scripts is disabled on this system |
The execution policy is Restricted |
This is not a signing problem — set an appropriate policy with Set-ExecutionPolicy |
Get-AuthenticodeSignature reports Status: NotTrusted |
The signing certificate or its chain is not trusted on the machine running the check | Import the certificate / CA chain into the trust stores; under AllSigned, add the signer to Trusted Publishers |
| A script that worked is refused months later | The signing certificate expired and the script was signed without a timestamp | Re-sign with --tr/--td; always timestamp |
Do you want to run software from this untrusted publisher? prompts on every run |
Under AllSigned, the publisher is not in Trusted Publishers |
Choose Always run, or pre-import the signing certificate into the Trusted Publishers store |