Sunday, December 22, 2013

Powershell: Namespacing your test variables

Note: Since this post, I've significantly improved upon this script.  You can find the new version here.

Powershell lets you run any command a script can run as a single line in the CLI.  However, scripts get their own scope, and the CLI just uses the global scope.  On plenty of occasions, I've wanted a way to namespace my test variables away from all the default global variables.  I initially arrived at a solution involving a function that wrapped new-variable and prefixed the variable name supplied with the name of the namespace.  It was crude, but it worked.

Just now though, I wrote a better solution using a psobject.  It has add, remove, and clear methods for managing the namespace, and no other members.  I placed the code that sets this up in my profile, and added a bit of extra code so that if I dot-source my profile it updates the methods without touching the properties I've added.

This post would be little more than a long-winded way of bragging if I didn't post any code, so here you go, code.  In this code, my namespace is $XT, you can of course rename that to whatever you want.  Don't forget to change the variable:XT on the third line as well!
# namespace for testing # powershell, y u no have psdrive provider for this if( test-path variable:XT ) { # if the object exists, update the methods but don't mess with the values $XT.psobject.members.remove( 'Add' ) $XT.psobject.members.remove( 'Remove' ) $XT.psobject.members.remove( 'Clear' ) } else { # if the object doesn't exist, create it $XT = new-object psobject } # we need a way to add variables to this namespace # I prefer exit status, even though EXCEPTIONS EVERYWHERE is the .NET way... $XT | add-member -type scriptmethod -name Add -value { param( [string]$name, $value ) # test to see if we already have a member with this name if( $this.psobject.members | where-object { $_.name -eq $name } ) { # we do return $false } else { # no member with this name exists, create and assign $this | add-member -type noteproperty -name $name -value $value return $true } } # we also need a way to remove variables from this namespace $XT | add-member -type scriptmethod -name Remove -value { param( [string]$name ) # make sure the member we're trying to remove exists if( $this.psobject.members | where-object { $_.name -eq $name } ) { # see if we're trying to remove either of the methods for managing the namespace if( @( 'add', 'remove', 'clear' ) -notcontains $name ) { # nope, go ahead and remove the member so long as it's a NoteProperty if( ( $this.psobject.members | where-object { $_.name -eq $name } ).membertype -eq 'NoteProperty' ) { $this.psobject.members.remove( $name ) return $true } else { # member was not a NoteProperty return $false } } else { # don't let the add(), remove(), and clear() methods be removed return $false } } else { # trying to remove that which does not exist? return $false } } # I know, I know, mass-deletion only leads to mass-sadness, but this is a namespace for test variables we're talking about here. $XT | add-member -type scriptmethod -name Clear -value { param() # delete everything from the namespace $this.psobject.members | where-object { $_.membertype -eq 'NoteProperty' } | foreach-object { $this.psobject.members.remove( $_.name ) } }
The above approach would be much better done as a full-on class that you could instantiate, which would involve writing it in C# and importing it with add-type.  Then you could make as many namespaces as you wanted.

I personally think the best solution would come in the form of a provider for a PSDrive.  It would essentially act like the Variable provider with a couple of exceptions: it wouldn't contain the default global variables and, and it wouldn't pollute the built-in Variable: PSDrive like what happens if you create a second PSDrive with the variable provider.  Yes, I've tried that before and was massively disappointed.  By using a provider, your management commands would be the regular Powershell cmdlets you get for navigating directory structures and working with files!

Edit (2014-01-07): I apparently derped when I added the clear() method and forgot to prevent it from being removed.  FIXED.

Edit (2015-03-22): I improved upon the script significantly enough to warrant a new post.  That new post is linked to at the very top of this post for convenience.