Thursday, June 17, 2010

Signing PowerShell Scripts – A Gotcha with ISE!

In some enterprise environments, signing PowerShell scripts and setting an execution policy to only run signed scripts is a useful control mechanism. It can avoid less skilled admins ‘fixing’ a script almost correctly and can avoid untested scripts from running. Of course, the malign admin can still cut/paste the scripts into the command line and do damage – but that same admin can nuke the registry, reformat a volume, etc. Script signing is just another layer of defence.

The Scripting Guys Team (well actually superstar MVP Ragnar Harper) has written a two part blog post on the subject of how to do script signing. Part 1 is a useful tutorial on how to setup your own PKI using Windows Server’s built in Certificate Service feature (AD CS as MSFT call it). With Part 1, you learn how to get your code signing digital certificate.  Part 2 then talks you through how to use that certificate to generate a signed script.

The demo is good and the instructions work well, however there is one small gotcha. If you use PowerShell ISE to edit and save your scripts, the technique shown in Part 2 will fail. Here’s what you will see (from the ISE).


As you can see, this results in a rather less than helpful “UnknownError”. Turns out the reason is simple: By default, ISE saves scripts in Unicode BigEndian format – which Set-Authenticode does not cater for. And worse, the ISE Save-As dialog does not give you any option to save in a more friendly encoding (ie ASCII!).

There are three solutions to this:

1. Use Notepad to re-save the file as ASCII, then sign it. This is suboptimal but it works.

2.  You can get ISE to save as Unicode (not BigEndian) using a small script, unfortunately not from the menus. Just run the following bit of code:


3: You can get ISE to save as always as ANSI. As t add the following bit of code to your PowerShell ISE:

register-objectevent $psise.CurrentPowerShellTab.Files collectionchanged -action {
    # iterate ISEFile objects
    $event.sender | % {
        # set private field which holds default encoding to ASCII
        $_.gettype().getfield("encoding","nonpublic,instance").setvalue($_, [text.encoding]::ascii)

Once this fragment is executed, probably by adding it to your Profile, scripts get saved as ANSI and can be signed just fine.

Note in the above, you can both save the current file as ASCII or auto-save as Unicode. Set-AuthenticodeSignature works with both encodings, just not the ISE normal default of Unicode BigEndian (just change ::ASCII to ::UNICODE or vice versa!). Personally, I now save as ASCII. But if you have non-ANSI characters in your scripts, set the default as Unicode!

And a tip of the hat to Oisin Grehan who posted about this on the MVP list and who has posted some of the above code on his blog here.


1 comment:

Hans Dingemans said...

Note that it is a "collectionchanged" event. The job is not changing the encoding for the very first tab (I've tested this), so this is still written as a Unicode Big Endian file.