By Adrian Beasley 

Document Changes

The original version of this article was based on PowerShell RC1. Since then RC2 and the final v1.0 have been released. As a consequence, the article had become inaccurate in a few details, which would certainly irritate anyone using it as a guide to the released product. Accordingly this version 2 is issued. The specific changes are:

  • PowerShell is now part of the OS and installs within the Windows directory structure. Accordingly it is no longer necessary to import the publisher's certificate.
  • As a consequence of the above, it runs immediately on install, so I now recommend setting the (script) execution policy from within PowerShell itself, rather than by registry edit.
  • The locations of the configuration scripts have changed.
  • The 64-bit version exhibits certain quirks which need to be highlighted.

General

PowerShell (I preferred the name Monad) is the new scripting environment for Vista/Longhorn, also available for Windows Server 2003 and XP. It is an excellent product, but still a little rough at the edges in some respects, and the documentation could, as always, be better.

This article explains how to install PowerShell. There is more than one way to get it up and running, but, after installing it on several machines and kicking it around and trying to learn from my mistakes, this is how I think it should be done. PowerShell is a secure product, unless you deliberately configure it insecurely. The following explains how to install and configure it to run with optimum security, in particular that all scripts are digitally-signed. Most of the following is in the documentation somewhere, but nowhere have I seen it laid out as a series of steps – follow this and you'll be okay.

Preliminaries – Set Up Your PKI First

To be able to take advantage of code signing, you need an internal Public Key Infrastructure. (Everybody needs an internal Public Key Infrastructure!) I take it as read that you have this set up already, with one or more Enterprise Certificate Servers, (so that the PKI is integrated with Active Directory,) running on W2K3 Enterprise Edition, (so that version 2, configurable, certificate templates are available). If you're not familiar with PKI, refer to my several articles on the subject, which are explicitly targeted at newcomers, so they don't have to go through all the grief that I did when learning it.

Create a v2 code signing certificate template by duplicating the v1 template Code Signing. This has the necessary application policy extension Code Signing. Edit the v2 template to allow the private key to be exported. This is not the case by default, and is the reason why, in my opinion, a v2 template is necessary for this purpose - it allows the same key to be used for signing scripts on multiple machines, otherwise you would be forced to restrict code signing to one particular machine, or face the nuisance of undesirable proliferation of code signing certificates.

Create a new user or select an existing one who is to perform code signing, ideally only PowerShell script development and signing and the only one to do so. Enroll a certificate to the v2 code signing template for this user. Finally, export the certificate, twice, the first (*.cer file) containing the certificate only, and the second (*.pfx file) containing the private key also. Save these files on floppy disk (several copies) and delete from filestore, and store the floppies in different secure locations. The *.cer file is to allow the certificate (only) to be imported to the Trusted Publishers certificate store on all machines running PowerShell. The *.pfx file is to allow the certificate and private key to be imported to (the user certificates store of) other machines to allow script development and signing to be performed there.

Powershell – the Installation

  1. Install .NET v2.
  2. Install the PowerShell software.
  3. Import the enterprise code signing certificate.
  4. Configure PowerShell to insist on digitally-signed scripts.

And that's it. Specifically:

  1. PowerShell is dependent on .NET v2, so this must be installed first, if not present already. Get it from the Microsoft Download Centre if necessary.
  2. Install the PowerShell software – the 32-bit installation files are: WindowsXP-KB926139-x86-ENU.exe for XP and WindowsServer2003-KB926139-x86-ENU.exe for W2K3; the 64-bit one is WindowsServer2003.WindowsXP-KB926139-x64-ENU.exe for both XP and W2K3. (There is also an Itanium version - WindowsServer2003-KB926139-ia64-ENU.exe – if anybody actually uses that.) These are also available from theMicrosoft Download Centre.
  3. Import the enterprise code signing certificate. Using the MMC certificates (local machine) snap in, select the Certificates (local computer)/Trusted Publishers and import there. In fact the certificate will be imported to the current user's Trusted Publishers certificate store as well, but by importing to the local machine store we ensure that the certificate is available to all users who work on the machine. (Of course, you may wish to be more restrictive, in which case just import to the relevant user(s)). This starts the Certificate Import Wizard, and you're importing from a file – the *.cer file you created in the preliminaries.
  4. Start up a PowerShell session. Issue the command
    set-executionpolicy allsigned
    to set the appropriate registry location to the required value.

You can now run PowerShell without further ado. Note that the above installation process is exactly the same whether applied to a production server or to a development client machine. In the case of a development machine, the code signing certificate needs (additionally!) to be present in the Certificates – Current User/Personal/Certificates store of the user who is to perform code-signing, either by the certificate being enrolled by that user on that machine in the first place, or imported (together with the private key) from the *.pfx file you created in the preliminaries.

Post Setup Configuration and Signing Scripts

PowerShell installs to the directory %SystemRoot%\system32\windowspowershell\v1.0 for the 32-bit version, and to %SystemRoot%\sysWOW64\windowspowershell\v1.0 for the 64-bit version.

It is possible (though not essential) to run configuration scripts at the beginning of each PowerShell session.

  1. If a script named Profile.ps1 is stored in the PowerShell directory (as noted above), then this will be executed at the start of a PowerShell session for any user.
  2. If a script likewise named Profile.ps1 is stored in My Documents\ WindowsPowerShell for a user (wherever that happens to be located), then that script will be executed (after the one above) at the start of a PowerShell session for that user.

These scripts must be digitally-signed (of course)!

How scripts are signed within a PowerShell session is as follows:

$cert = get-childitem cert:\CurrentUser\My –codesigning
set-authenticodesignature <script name> $cert

(For details, refer to the PowerShell documentation.)

The way I have the configuration scripts set up is as follows:

  1. The All Users script sets the default location. I store scripts in a directory %systemdrive%\PSScripts, and this I set as the default location, thus:
    $DefaultLocation = (Get-ChildItem Env:\SystemDrive).Value + "`\PSScripts"
    Set-Location $DefaultLocation
  2. The individual user script for my development user, by whom code is signed, defines a local function for the convenient signing of scripts, thus:
    $Cert = Get-Childitem Cert:\CurrentUser\My –CodeSigning
    Function D-Sign
    {
        Param([String]$Script)
        if ($Script -eq "")
        {
            "Parameter missing!!!"
            return
        }
        Set-AuthenticodeSignature -FilePath "$DefaultLocation\$Script.ps1" `
    -Certificate $Cert
    }

The way I develop scripts is incrementally (I imagine most scripters work this way). I have the script (testscript.ps1) open in Notepad. I make a change and save it. Within PowerShell I sign it and run it:

D-Sign testscript    #(note that the .ps1 is appended by the function)

.\testscript

then amend/save, sign, run again and again and again until I'm finally satisfied with it.

64-bit Issues

As already noted, PowerShell installs to the directory %SystemRoot%\system32\windowspowershell\v1.0 for the 32-bit version, and to %SystemRoot%\sysWOW64\windowspowershell\v1.0 for the 64-bit version.

However, when the 64-bit version is installed, the 32-bit version is installed as well (don't ask me why). If you specify just powershell.exe, you get the 32-bit version. The 64-bit version has to be invoked by the full path name, or equivalent. The start menu has a Windows PowerShell 1.0 folder, wherein are Windows PowerShell and Windows PowerShell(32) options. You might reasonably suppose that these referred to the 64-bit and 32-bit versions respectively. You would be wrong. (I promise you, I'm not making this up.) By, presumably, some ridiculous oversight, they are actually the wrong way round. I recommend editing the menu shortcuts to put it right, rather than trying to remember this!

The registry settings controlling script execution are HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell\ExecutionPolicy for the 32-bit version, and HKLM\SOFTWARE\Wow6432Node\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell\ExecutionPolicy for the 64-bit version. So if you were a real lunatic, you could run both versions on the same machine with different script execution policies! These different registry locations are principally why I now recommend using the PowerShell cmdlet set-executionpolicy to configure the script policy, automatically making sure that it goes in the right location (also it is case insensitive, thus allsigned, whereas the registry is case sensitive, thus AllSigned).

Clearly, if you're using the 64-bit version, be very careful to ensure that you actually are!

The 64-bit version is also a bit flaky in some respects; I have noticed one script that works perfectly well on 32-bit but fails on 64-bit, and I don't yet understand why. (I've documented this in my second article.) Obviously, in this case I'm sticking with 32-bits even on a 64-bit machine.


Many thanks to Adrian for another contribution