07 Feb

Signing your PowerShell Scripts

At some point or another, we have all been in the situation, usually on a new machine, we get the familiar PSSecurityException.

.\xxx.ps1 : File C:\xxx.ps1 cannot be loaded. The file C:\xxx.ps1 is not digitally signed. You cannot run this
script on the current system. For more information about running scripts and setting execution policy, see
about_Execution_Policies at http://go.microsoft.com/fwlink/?LinkID=135170.
At line:1 char:1
+ .\xxx.ps1
+ ~~~~~~~~~~
    + CategoryInfo          : SecurityError: (:) [], PSSecurityException
    + FullyQualifiedErrorId : UnauthorizedAccess

Of course we know this is down to PowerShell Policy. The behaviour of PowerShell when it executes a script can be configured to one of the following:

  • Restricted – This is the most secure but the least versatile. It does not allow any scripts to be run.
  • AllSigned – This allows scripts that have been digitally signed by a certificate that the computer trusts to run.
  • Unrestricted – This is the least secure. It allows all scripts regardless of origin to run.
  • RemoteSigned – This is a step up from Unrestricted. Scripts that have been downloaded are not allowed to run unless they are digitally signed by a certificate that the computer trusts.

The default policy depends on a number of things including group policy and which operating system you are using. A lot of people get around this by setting the execution policy to Unrestricted.

Set-ExecutionPolicy Unrestricted

I however do not like this as it leaves your system open to abuse.

RemoteSigned should be the minimum you ever set this policy to. Even with RemoteSigned you may find you still get the above error when your script is located on a network drive. In this case rather than changing the execution policy to Unrestricted you should change your internet options so the server that the file share is hosted on is trusted. To do this you should go to Control Panel and Internet Options to open the Internet Options dialog:

eecef1e1-3f9f-47fa-b0d5-c61b61b910cf_01

Under Internet Options select the Security tab and then the Local Intranet zone. Once selected click Sites then Advanced. Add the server to the list in the form file://<servername> (untick Require server verification). Once this has been updated restart any PowerShell windows and try executing your script again on a network share and you should find they run.

However I think the best approach is to sign your scripts. The argument against this is that it’s a “complicated” process involving certificates which people seem to find hard anyway.

It is true that you will need to get a code signing certificate and if your IT Support has set things up correctly you should be able to get a code signing certificate in about 30 seconds (see Create a Code Signing Certificate in 30 Seconds). Sometimes your IT Support will not have configured your infrastructure for maximum usability and security and so you may need to expend a bit more effort, however this is an infrequent process with big advantages for security.

If you have a code signing certificate then signing your scripts is incredibly easy. Just substitute the certificate thumbprint with your own:

$cert = Get-ChildItem cert:\CurrentUser\My\FACE9812CAFE7634BABE54561A2B3C4D5E6FDEAD
Set-AuthenticodeSignature -Certificate $cert -FilePath X:\path_to_script.ps1

This will add a block like the following to the end of your script:

# SIG # Begin signature block
# MIIInAYJKoZIhvcNAQcCoIIIjTCCCIkCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
......................
# NNDblcNY9HDwe+tXuDRtu3YLoBWBq4xasQTml46HHtEd1z6L+qTDi1gwbv/dFmIB
# SIG # End signature block

This is the digital signature and will be what PowerShell verifies when trying to run a script. See how easy that was?

Top tip

My home directory is hosted on a network server and so my PowerShell profile file is hosted on the network share. This means by default, without signing my PowerShell profile, every time I load PowerShell I will get a message:

. : File \\domain.com\FILES\USERS\my.username\My Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
cannot be loaded. The file \\domain.com\FILES\USERS\my.username\My
Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1 is not digitally signed. You cannot run this script on
the current system. For more information about running scripts and setting execution policy, see
about_Execution_Policies at http://go.microsoft.com/fwlink/?LinkID=135170.
At line:1 char:3
+ . '\\domain.com\FILES\USERS\my.username\My Documents\WindowsPowerShell\Micr ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : SecurityError: (:) [], PSSecurityException
    + FullyQualifiedErrorId : UnauthorizedAccess

It was easy enough to sign my profile file:

$cert = Get-ChildItem cert:\CurrentUser\My\FACE9812CAFE7634BABE54561A2B3C4D5E6FDEAD
Set-AuthenticodeSignature -Certificate $cert -FilePath $profile

The problem is that I frequently add, remove or edit things in my profile which usually leads to the following error message:

. : File \\domain.com\FILES\USERS\my.username\My Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
cannot be loaded. The contents of file \\domain.com\FILES\USERS\my.username\My
Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1 might have been changed by an unauthorized user or
process, because the hash of the file does not match the hash stored in the digital signature. The script cannot run
on the specified system. For more information, run Get-Help about_Signing..
At line:1 char:3
+ . '\\domain.com\FILES\USERS\my.username\My Documents\WindowsPowerShell\Micr ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : SecurityError: (:) [], PSSecurityException
    + FullyQualifiedErrorId : UnauthorizedAccess

This is due to the file being changed so the digital signature is no longer valid. It’s easy enough to correct by signing the file again:

$cert = Get-ChildItem cert:\CurrentUser\My\FACE9812CAFE7634BABE54561A2B3C4D5E6FDEAD
Set-AuthenticodeSignature -Certificate $cert -FilePath $profile

but I find this tedious as looking up my thumbprint and then typing the path is more effort than I’m willing to spend. I always usually have a PowerShell window open even when I am editing my profile and so to save me some time I have defined the following function in my profile:

function Update-ProfileSignature()
{
	$signingCertificateThumbprint = (Get-AuthenticodeSignature $profile).SignerCertificate.Thumbprint	
	$codeSigningCertificate = Get-ChildItem -Recurse Cert:\ | ? { $_.Thumbprint -eq $signingCertificateThumbprint -and $_.HasPrivateKey } | Select -First 1
	Set-AuthenticodeSignature -FilePath $profile -Certificate $codeSigningCertificate | Out-Null

	Write-Host "Updated signature on profile" -ForegroundColor Green
}

Everytime I edit my profile, in an already open PowerShell window, I execute the function Update-ProfileSignature which re-signs my profile file.

It’s important that a PowerShell window is already open (and therefore has the Update-ProfileSignature function loaded and defined) as once you edit your profile you cannot load your profile without error into any new PowerShell windows until it is re-signed and therefore you cannot call Update-ProfileSignature until it is re-signed, which would make it useless.

06 Feb

Create a Code Signing Certificate in 30 Seconds

Have you ever needed a code signing certificate to sign a Powershell script or other piece of software within your organisation?

One of the great things about Enterprise PKI within an Active Directory environment is the ability to generate certificates for all manor of different purposes. You might want a certificate for S/MIME email, an SSL certificate for an internal web server or just a code signing certificate for internal software or scripts. All these tasks can take as little as 30 seconds.

I am going to take you through getting a code signing certificate that can be used to sign your software or scripts within your organisation.

Open up the Microsoft Management Console (mmc.exe)1af15819-6aca-438c-9def-0cd7ff5dddf1_01

Go to File > Add/Remove Snap-in… and add the Certificates snap-in.1af15819-6aca-438c-9def-0cd7ff5dddf1_02

Open up Certificates – Current User node and then on the Personal node right click and go to All Tasks > Request New Certificate…1af15819-6aca-438c-9def-0cd7ff5dddf1_03

You will see the certificate enrollment wizard appear. Click Next1af15819-6aca-438c-9def-0cd7ff5dddf1_04

Select the Active Directory Enrollment Policy and click Next. (If the AD enrollment policy doesn’t appear then your computer isn’t in a domain environment where IT support have setup an Enterprise PKI environment and unfortunately you will need to use a different method)1af15819-6aca-438c-9def-0cd7ff5dddf1_05

Select the Code Signing enrollment policy and click Enroll. (If the code signing enrollment policy isn’t available then your system support have decided not to allow you to request code signing certificates using the Enterprise PKI and you will have to find a different method)1af15819-6aca-438c-9def-0cd7ff5dddf1_06

You should hopefully get a success message.1af15819-6aca-438c-9def-0cd7ff5dddf1_07

A new certificate should have appeared in the certificates snap-in of the Microsoft Management Console under the personal node.

You can now use this certificate to sign software or scripts.