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.
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
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:
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:
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 : 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 : 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 220.127.116.11.18.104.22.168.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 22.214.171.124.126.96.36.199.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.
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
SignerCertificate Status Path
----------------- ------ ----
26E57A2CC30A2370B6896821DB26B0F28CB79BB7 Valid myscript.ps1
Look at the following screenshot. Digitally signed scripts include a signature block at the end.
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:
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!
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.
- PowerShell conceptual help: about_signing
- PowerShell conceptual help: about_execution_policies
- Export your code-signing certificate
- Sign PowerShell scripts with an AD CS PKI