PowerShell Script Signing Script

Everyone knows that PowerShell is Microsoft’s “new” scripting language. It’s here to stay and frankly it is very powerful!

With the new features of remote execution, persistence through reboots, etc. I thought it was about time to get to grips with it and understand what it can do.

Execution Policies

This article on TechNet explains the different policies available. By default PowerShell scripting is disabled – this is a good thing! Executing random scripts is not a good idea!

So which one to use?

If you’re an IT Pro building scripts then realistically the only option is Unrestricted. However on end user machines you need control! If you don’t have a PKI infrastructure then I’d highly recommend setting it up (future blog article coming up?) In a domain environment I’d recommend using a GPO to set the execution policy to AllSigned. This way you get control over what scripts can run – no randomly downloaded files can run (unless they’re signed by a certificate you trust – unlikely).

Signing a Script

To sign a script is quite straight forward if you have a certificate that allows Code Signing:

certificate

To sign a script you’ll need to run the following:

$Cert = (dir cert:currentuser\my\ -CodeSigningCert) 
Set-AuthenticodeSignature <Filename> $Cert -TimestampServer http://timestamp.comodoca.com/authenticode

And this will give you a PowerShell file with a signature block at the end. This file can then be executed on any client that trusts the certificate (i.e. the certificate is in the Trusted Publishers container) and that the execution policy is set to at least AllSigned.

What if you’ve got many files to sign? What if you’ve got multiple Certificates?

There are many blogs showing you how to use Get-ChildItem/Get-Item etc. but what you just want a quick solution? This leads me to the main point of this post!

I got fed up having to manually sign each file and remember the exact syntax needed to select the right certificate to sign my script so I created a script to sign my scripts. The script requires a very basic folder structure:

<Root Folder>
    CodeSign.ps1
    <Folder called ToSign>
    <Folder called Signed>

So what does it do?

The script enumerates all the files in the ToSign folder, asks which one you want to sign (you can select All), finds an appropriate certificate in the Current User’s personal certificate store (if there is more than 1 appropriate certificate it will prompt you for which one to use), signs the script and moves it to the Signed folder. Simple as that.

#Get all files within the subfolder: ToSign
$Files = Get-ChildItem -Path ".\ToSign\*.ps1"
#Create Var to hold Certificate for signing PS files
$global:MYCERT = $null
If ($files.Count -gt 0)
{
    #Show user all files that have been found
    Write-Host ($files.Count) "script files have been found."
    $i = 1
    foreach ($file in $files)
    {
       Write-Host `t $i - $file.Name
       $i += 1
    }
    #Ask user which script to sign
    $ScriptToSign = Read-Host "Which script would you like to sign? Type 0 if none, A if all or C to Cancel"

    #Load VB assembly to get some useful functions 
    [reflection.assembly]::LoadWithPartialName("'Microsoft.VisualBasic")
    #Is the input numeric?
    If ([Microsoft.VisualBasic.Information]::isnumeric($ScriptToSign))
    {
        $fName = $Files[($ScriptToSign-1)].Name
        #Ask user which Certificate to user
        SelectCertificate
        #Sign the script file
        SignScript ($fName)
    }
    #Does the user want all scripts signed?
    elseif ($ScriptToSign.ToLower() -eq "a")
    {
        #Ask user which Certificate to user
        SelectCertificate
        #Iterate through each file in the folder
        foreach ($file in $files)
        {
            #Sign the script file
            SignScript ($file.Name)
            Write-Host "------------"
        }
    }
    #Something else entered that is not catered for!
    else {
        Write-Host "No script will be signed"
    }
}
#This function actually signs the script file
function SignScript ([string]$fileName){
    #Tell user which file is being signed
    Write-Host "About to sign " $fileName
    #Sign Script file
    Set-AuthenticodeSignature ".\ToSign\$fileName" $global:MYCERT -TimestampServer http://timestamp.comodoca.com/authenticode
    #Move to signed folder
    Move-Item ".\ToSign\$fileName" ".\Signed"
    Write-Host "Script signed and moved to Signed folder"
}

#Allows the user to select the certificate to sign with
function SelectCertificate {
    #Obtain all certicates in the user's personal certificates folder
    $certList=(dir cert:currentuser\my\ -CodeSigningCert)
    If ($certList -is [array])
    {
        #More than one suitable certificate has been found. List all the certificates to the user
        Write-Host ""        $i = 1
        foreach ($cert in $certList)
        {
            Write-Host `t $i - $cert.Subject "CERT"
            $i += 1
        }
        $CertToUse = Read-Host "Which Certificate would you like to use?"
        #Check the user has actually entered a number and is within range
        If ([Microsoft.VisualBasic.Information]::isnumeric($CertToUse) -and ($CertToUse -gt 0) -and ($CertToUse -lt $i)){
            $global:MYCERT = $certList[($CertToUse-1)]
        }
        else{
            Write-Host "Invalid input entered. Exiting Scipting signing script!"
            Exit
        }
    }
    #No suitable certificates have been found
    elseif (($certList -eq $null) -or ($certList.Length -eq 0))
    {
        Write-Host "ERROR: No code signing certificates have been found"
        Exit
    }
    #Only one suitable certificate has been found - will use this by default
    else{
        $global:MYCERT = $certList
    }
}

Further Applications of Signed Scripts

So what else can you do with a signed script? System Center Configuration Manger 2012 (inc. SP1) allows you to use PowerShell scripts for a variety of purposes including application deployment. So for example if you wanted to deploy Windows 8 RSAT tools and install them using a PowerShell script you could create your script, sign it and use it as the execution program for your PowerShell install (program line would be: powershell -File <filename.ps1>)

If you need to run a PowerShell script to determine whether or not an application is installed (for example to determine if a specific Windows feature has been installed as the result of a script execution) you could create your script file, use the script above to sign the script and then copy and paste the contents of the signed file into ConfigMgr.

If you’re using System Center Updates Publisher to sign updates you can use the same certificate (if you’ve got the private key)/template to sign PowerShell files.

Two things to remember:

  1. Manually sign the CodeSign.ps1 file (the snippet above shows you how)
  2. Distribute the certificate you use to sign the script files

Download Script from SkyDrive

Advertisements

Posted on 7 May, 2013, in PowerShell and tagged , , . Bookmark the permalink. 2 Comments.

Anything to add? Let me know

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: