If you sign your PowerShell scripts you ensure their authenticity and integrity.
Latest posts by Timothy Warner (see all)

Digitally signing your PowerShell scripts with a Class 3 code-signing certificate increases security in two important ways:

  • Authentication: People who use your scripts are confident that you authored the scripts.
  • Integrity: People who use your scripts know they haven’t been tampered with, because they are digitally signed.

By the conclusion of this article, you'll understand how to obtain a code-signing certificate and apply it to your PowerShell scripts. Let's get started!

Script execution policies

As you probably know, PowerShell has some built-in safety features regarding script execution. First, by default, the .ps1 file type associates with Notepad. This prevents a user from inadvertently executing a PowerShell script by double clicking, say, an e-mail file attachment.

Second, depending on the Windows OS version, script execution is limited by default. Here's the rundown:

  • Windows 8.1, Windows 10, and Windows Server 2012 RTM all restrict PowerShell script execution globally (Restricted execution policy).
  • Windows Server 2012 R2 and Windows Server 2016 TP4 allow locally created scripts, but require digital signatures on all downloaded scripts (RemoteSigned execution policy).

We can use Set-ExecutionPolicy or Group Policy (among other methods) to change the script execution policy on a local or remote system. Besides Restricted and RemoteSigned, the other options are:

  • AllSigned: Any script, locally created or downloaded, must be digitally signed to run.
  • Unrestricted: Allows unsigned scripts to run, but prompts the user for confirmation first.
  • Bypass: Allows unsigned scripts to run and does not prompt the user.

We can set different script execution policies on a single computer within five separate scopes:

  • Process: Affects only the current PowerShell session.
  • CurrentUser: Affects only the current user.
  • LocalMachine: Affects all users on that computer.

For example, the following command sets the execution policy to Bypass temporarily, only for the current PowerShell runspace:

Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process

To set script execution policy via Group Policy, open the Group Policy Management Console and navigate to the following path:

Computer Configuration\Policies\Administrative Templates\Windows Components\Windows PowerShell

As you can see in the following image, you enable the Turn on Script Execution policy, specifying one of the following options:

  • Allow only signed scripts: This is the AllSigned execution policy.
  • Allow local scripts and remote signed scripts: This is the RemoteSigned execution policy.
  • Allow all scripts: This is the Unrestricted execution policy.
We can set PowerShell script execution policy in Group Policy

We can set PowerShell script execution policy in Group Policy

Please understand that PowerShell's default file association and script execution policy aren't secure in themselves by any means. Instead, they represent easy ways to avoid accidental script execution and are a part of a larger IT security plan.

Obtain an Authenticode certificate

How you obtain your code-signing certificate depends upon a number of factors, including:

  • Who's going to run your code? If you have a global audience, then you'll need a certificate signed by a globally trusted certificate authority (CA).
  • Do you have an internal public key infrastructure (PKI)? If you're doing PowerShell scripting for your company and you already have a local PKI such as Active Directory Certificate Services (AD CS), you can request a corporate-trusted certificate from there.
  • How much money do you have to spend? You'll find that purchasing a code-signing certificate from a globally trusted CA will run you several hundred dollars per year. It's no small investment!
  • Are you just testing? I'll show you how to create a self-signed certificate that is trusted by your own computer and is suitable for testing and local development.

Authenticode is Microsoft's implementation of industry standard X.509 digital certificates. Thus, when you're shopping for a public cert, you'll want to search for Authenticode code-signing certs.

As of late March 2016, here are some one-year price quotes from some of the leading public CAs:

  • DigiCert: $178
  • Entrust: $299
  • GlobalSign: $219
  • GoDaddy: $169
  • Symantec: $499
  • Thawte: $299
  • WoSign: $445

For our test, let's create a self-signed certificate. Windows PowerShell includes the New-SelfSignedCertificate cmdlet, but instead we'll use the makecert.exe utility.

To install makecert.exe, download the Windows 10 Software Development Kit (SDK) platform installer. Make sure to install only the .NET Framework 4.6 Software Development Kit component, as shown in the following image:

Having to download 80 MB of software

Having to download 80 MB of software

Before we create the certificate, let's review PowerShell's output when we violate script execution policy. Open an elevated PowerShell console and run the following command to see how your computer is set up:

Get-ExecutionPolicy -List

Use your favorite text editor to create a simple script called myscript.ps1, saving it in the location of your choice. The point here isn't the script itself, so here's some toy code to include:

Write-Host 'Script is running!' -BackgroundColor Black -ForegroundColor Yellow

We'll start by setting the execution policy to Restricted and then running the script. Here's the partial output from my Windows Server 2012 R2 development server:

Set-ExecutionPolicy -ExecutionPolicy Restricted -Force
.\myscript.ps1

.\myscript.ps1 : File C:\myscript.ps1 cannot be loaded because running scripts is disabled on this system.

Okay. Now we'll do the same thing to test the AllSigned execution policy:

Set-ExecutionPolicy -ExecutionPolicy AllSigned -Force
.\myscript.ps1
.\myscript.ps1 : File C:\myscript.ps1 cannot be loaded. The file C:\myscript.ps1 is not digitally signed.

Create and apply the digital certificate

For convenience, you may want to add makecert.exe to your computer's PATH environment variable. On my Windows Server 2012 R2 box, I found the executable in the following location:

C:\Program Files (x86)\Windows Kits\10\bin\x64

In your elevated PowerShell console, navigate to the makecert.exe parent folder and run the following command to create a local CA:

makecert -n "CN=PowerShell Local Certificate Root" -a sha1 -eku 1.3.6.1.5.5.7.3.3 -r -sv root.pvk root.cer -ss Root -sr localMachine

You'll be prompted to create a private key password; do so.

Next, run the following command to create your actual code-signing certificate, using the previously created CA as a signing authority:

makecert -pe -n "CN=PowerShell User" -ss MY -a sha1 -eku 1.3.6.1.5.5.7.3.3 -iv root.pvk -ic root.cer

Again, you'll be prompted for a private key password. As you can see in the next screen capture, your two new certificates now reside in the local user's certificate store.

Viewing our code-signing certificate (above) and local root

Viewing our code-signing certificate (above) and local root

Finally, we get to the good part—signing our simple myscript.ps1 script! We use Set-AuthenticodeSignature for that task. Here's the code, and then I'll explain it:

$cert = Get-ChildItem -Path Cert:\CurrentUser\My -CodeSigningCert
Set-AuthenticodeSignature -FilePath '.\myscript.ps1' -Certificate $cert

    Directory: C:\

SignerCertificate                          Status   Path
-----------------                          ------   ----
26E57A2CC30A2370B6896821DB26B0F28CB79BB7   Valid    myscript.ps1

Look at the following screenshot. Digitally signed scripts include a signature block at the end.

Our digitally signed PowerShell script

Our digitally signed PowerShell script

To test, we'll set our execution policy to AllSigned:

Set-ExecutionPolicy -ExecutionPolicy AllSigned -Force

Then we'll run the script, specifying a at the prompt to avoid future confirmation prompts:

.\myscript.ps1

Do you want to run software from this untrusted publisher?
File C:\myscript.ps1 is published by CN=PowerShell User and is not trusted on your system. Only run scripts from trusted publishers.
[V] Never run  [D] Do not run  [R] Run once  [A] Always run  [?] Help (default is "D"): a
Script is running!
PS C:\>

Next steps

We covered a great deal of ground in this article, but we've only scratched the topic's surface, as the saying goes.

For example, consider the following questions:

  • How can I share my self-signed certificate with other scripters at my company?
  • Do I need to generate a new signature every time I modify my script?

Don't worry—I'll leave you with some resources to help you move your understanding to the next level. In the meantime, I look forward to addressing your comments and questions.

Subscribe to 4sysops newsletter!

12 Comments
  1. matthew davidson 7 years ago

    Very informative!!  Just curious though; will signing your PowerShell scripts keep malware like Powersniff from running?  Keep up the great work!!

    • Author

      Hi Matthew, thanks for your kind words. Your question is complicated, but the short answer is that having a PowerShell script signing/execution policy in place in your network certainly helps. So many factors are at play with the PowerSniff malware: (1) If your users have a recent version of Office installed, then macro execution should be disabled by default; (2) If you’ve done a good job educating your users, then they won’t open unsolicited e-mail attachments; (3) If you’re doing client/user permissions correctly, then users aren’t logged onto their systems with administrative credentials. Code signing is just another layer of security to add on top of what you already have. Cheers, Tim

  2. Vadims Podans 7 years ago

    I would note that StartSSL offers code signing certs for just $60. And what is the reason to use deprecated makecert over native PowerShell cmdlet? And I don’t think that using SHA1 certificates is a good idea even for testing purposes.

  3. Vadims Podans 7 years ago

    In addition, why you didn’t talk about signature timestamping? Actually, non-timestamped signatures are not much better than non-signed  scripts, because they quickly become invalid. I realized that many bloggers who write about signing scripts in PowerShell completely miss that point.

  4. Very good overview of security in PowerShell! Keep up the good work, Tim.

  5. RaFi 7 years ago

    @Vadims: very wise comments, so +1000 from me

  6. Alex 7 years ago

    Excellent guide.  But now that the makecert utility is deprecated, would it be possible to publish an updated article of how to do this same procedure using the New-SelfSignedCertificate cmdlet please?

  7. Sven 6 years ago

    Hi,

    you say “Then we’ll run the script, specifying a at the prompt to avoid future confirmation prompts”.

    Why is that necessary if my certificate is generated with our own CA which is published to all computers in our domain?

    How do I get powershell to accept my certificate with an intact certificate chain to our CA without having to “press a” on every machine I want to run the script?

    Sven

Leave a reply

Your email address will not be published. Required fields are marked *

*

© 4sysops 2006 - 2023

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account