Have you ever written a program or a script where there’s a setting which has to be set by the user the first time the app is run? With applications, we just show the configuration UI when it’s run for the first time, but how about with PowerShell scripts? Well, in PowerShell 2 (CTP2), there is a way…

Back Story (or, An Example)

I’ve written a Authenticode.psm1 script module for PowerShell which is primarily a wrapper which causes the Set-AuthenticodeSignature and Get-AuthenticodeSignature cmdlets to work the way I think they should. Basically, they take files from the pipeline without complaining, and if they accidentally hit a folder, they throw an error but keep on processing other pipeline elements (the built-in cmdlets throw an exception and die in such an ugly way that they stop the whole pipeline).

Anyway, I also added a couple of features: a Test-Signature filter which returns true if a cert has a valid hash-match — even if the certificate can’t be traced to a valid CA(Certificate Authority), and a Select-Signed filter which has about 7 different switches to let you pick out scripts and files which are signed by you, by other, signed but broken, signed by trusted certs, signed by untrusted certs, etc. I use that filter to re-sign the scripts in my AutoModules directory when I’ve touched a bunch of files, so I can re-sign just the ones that I’ve previously signed, but which are currently broken.


ls $ProfileDir\AutoModules -recurse | Select-Signed -mine -broken | Set-AuthenticodeSignature
 

Of course, on my computer, I don’t want to have to specify the certificate path every time, so in my wrapper scripts, I created a $DefaultCertificate variable, which I need each user to to set when they take my script to their computer. In the previous version I just set it as a variable, and considered that good enough, but it occurred to me that if people forgot to change it, they would see scripts signed by me as “valid” — which was not my intention.

Localizable Scripts

In version 2 (CTP2), PowerShell introduced the concept of a subset of the languages called the PowerShell Data language. Basically, it’s just like PowerShell script, except you’re only allowed to use a couple of cmdlets, and non-expanding variables. Essentially, this means you can set some variables to predefined values, and not a whole lot else.

To make this data language localizable, there are a couple of data cmdlets: ConvertFrom-StringData and Import-LocalizedData … I’ll leave it up to you to read the help on those (use “Get-Help” in the PowerShell command line) and also the About_Script_Internationalization and About_Data_Section ...

The simplest way to explain it is this: if your script uses Import-LocalizedData, PowerShell attempts to load a data file with the same name as your script, from a subfolder named the same as the output of (Get-UICulture).Name (for US English, that is “en-US”), or (Get-UICulture).Parent.Name (again, for US English, that is “en”) ... if no file can be found, then it writes an error explaining where the file should have been located (but doesn’t throw an exception).

We can use this to create translatable text messages in our scripts, but we can also abuse it to require a settings file to be created, although we’ll need to explain to the users what should go into that file.

Wrapping it up

In my Authenticode script module, I ended up forcing Import-LocalizedData to throw an error by setting it’s ErrorAction to “Stop” and then trapping the error to write a more verbose description of what the setting(s) are that the script needs. I’m pretty sure there’s a better way to do this using the PSD1 data files, I just don’t know what it is.


   trap {
      Write-Host "The authenticode script module requires a configuration file to function fully!"
      Write-Host
      Write-Host "You must put the path to your default signing certificate in the configuration"`
                 "file before you can use the module's Set-Authenticode cmdlet or to the 'mine'"`
                 "feature of the Select-Signed or Test-Signature. To set it up, you can do this:"
      Write-Host
      Write-Host "MkDir $(Join-Path $PsScriptRoot $(Get-Culture)) |"
      Write-Host "   Join-Path -Path {`$_} 'Authenticode.psd1'    |"
      Write-Host "   New-Item  -Path {`$_} -Type File -Value '`"ThePathToYourCertificate`"'"
      Write-Host
      Write-Host "If you load your certificate into your 'CurrentUser\My' store, or put the .pfx file"`
                 "into the folder with the Authenthenticode module script, you can just specify it's"`
                 "thumprint or filename, respectively. Otherwise, it should be a full path."
      return
   }  
   Import-LocalizedData -bindingVariable CertificatePath -EA "STOP"
 

As a postscript, I should mention: Although I only had a single value I wanted in the settings file, it’s simple to put more, just read those help files I mentioned earlier and you’ll see a lot of examples of it. It’s basically a matter of using the ConvertFrom-StringData cmdlet with a sort of hashtable …

One Response to “(Mis)using “Localizable Data” to force users to set variables”

  • Jason Archer says:

    As an enhancement, why not prompt the user for the certificate path and then write it to the configuration file for them?