Code Signing PowerShell in the Enterprise

[Last Reviewed: 2019-04-25]

Here’s a scenario: you just finished writing the world’s most amazing PowerShell script, and you want to deploy it to a collection of workstations in your enterprise. You run it, just to see the wall of red text telling you that unsigned scripts are not allowed.

What if we signed that script?

Not only would that let your code run on machines without changing the execution policy, it would also serve as another layer of authentication–proof that the script that’s about to run is created by you, unaltered, and not corrupt.

The good news is it’s not a lot of effort to sign a script. If you’ve got a code signing certificate from your enterprise PKI (or from a public CA), you’re just a couple PowerShell commands away from gaining these benefits!

Using PowerShell to sign PowerShell

Step 0: Preparing your environment

Before we sign your code, we’re going to need some code to sign! If you don’t have a script handy, feel free to use this one-liner. Save it to Get-Truth.ps1.

Write-Output "$env:USERNAME is amazing!"

Step 1: Signing the Script

To sign your script, we’re going to use Set-AuthenticodeSignature. To run this cmdlet, we’re going to need three things:

  • The path to your script
  • Your code signing certificate
  • A timestamp server*

*Actually, we don’t need the timestamp server. You can sign your code just fine without it, and things will work great–at first. Once your code signing certificate expires, one of two things will happen:

  • If you included a timestamp server: your script will continue to work. The timestamp server tells the system that the code signing certificate was good at the time of signing, so your script will run fine even after the certificate expires.
  • If you didn’t include a timestamp server: your script will no longer work (users will be prompted with an error that the certificate isn’t digitally signed). A timestamp server wasn’t able to confirm that the certificate was good at the time of signing, so the system assumes the worst (which it should)

There are many timestamp servers out there–use one that meets your requirements or strikes your fancy.

To get your code signing certificate, you can use a command like Get-ChildItem Cert:CurrentUser\My\

This command will list all certificates your Personal store.

Locate your certificate in the list and run $cert = (Get-ChildItem -Path Cert:CurrentUser\My\YOURCERTIFICATETHUMBPRINT)

Now we’re ready to sign your script! Run Set-AuthenticodeSignature .\Get-Truth.ps1 -Certificate $cert -TimestampServer

If the Status returns Valid, the signing was a success! You can open your script now and see a signature block at the end of your script.

Step 2: Trust your Signed Script

If you were to run this script now, you may be prompted: Do you want to run software from this untrusted publisher?

If you select Always run, your code signing certificate will be copied to the Trusted Publishers store. If you select Never run, your code signing certificate will be copied to the Untrusted Certificates store. You can see this in action by running Get-ChildItem Cert:\CurrentUser\TrustedPublisher or Get-ChildItem Cert:\CurrentUser\Disallowed.

This will work great on your machine, but other machines in your environment will still prompt about your script being untrusted. If you’d like other machines to treat you as a trusted publisher, export your certificate (no private key required). You could use Group Policy to install this certificate to the Trusted Publishers store on other machines.

Not only do you have an amazing script, now it’s signed (so it’s integrity is validated) and has your personal guarantee that it’s trusted (by any machines that have your code signing cert in their Trusted Publishers store)!


Leave a Comment