Pseudo-Interfaces in Powershell

on Monday, January 28, 2019

In the last post I griped a bit about classes in Powershell. The strongest case to actually have classes in Powershell is that they can serve as interfaces which developers can use to extend modules and improve unit testing. Interfaces are a powerful part of DI/IoC and that pattern is not well supported in most command line scripting languages.

Here’s a quick run down of some of the techniques that can be used to provide a Pseudo-Interface in Powershell:

  • Powershell Classes
    • Example Use Case: I can’t really think of any off the top of my head.
    • Benefits: I’m not a fan of this approach.
    • Downsides: Since Powershell classes can’t be dynamically updated in the runtime, you have to continually reload your runtime environment whenever you want to make a change to one. This breaks one of the best benefits of using Powershell, and makes the language less dynamic.
    • How it works: This is built into powershell.
  • Function Overrides in Limited Scopes
    • Example Use Case: Pester
    • Benefits: This allows for the creation of Mock objects and limited scope overrides to common functions. This makes it possible to do unit testing of your Powershell modules that similar (but not exactly like) using Dependency Injection. This also keeps all of the coding to implement the pattern within powershell, so it can be dynamically updated.
    • Downsides: Since it’s building a Mock functions using Powershell dynamic creation behavior, the mock objects can be passed around as if they were created by a Dependency Injection system; but you can only have one definition for a function in each scope. You can’t have two functions which implement a common interface available within the same scope.
    • How it works: I don’t know. Pester is magic.
  • Function Aliases
    • Example Use Case: PoShDynDnsApi
    • Benefits: This allows for all your code to be rewritten against a standard alias, which will always work as expected; but still be flexible enough to handle difference of runtime environments (Windows vs Linux). This also keeps all of the coding to implement the pattern within powershell, so it can be dynamically updated.
    • Downsides: Similar to Function Overrides, you can only have a single definition for the Alias at a time. You can’t have two functions which implement a common interface available at the same time.
    • How it works: During a module’s load (the execution of the .psm1 file), the module can test the capabilities of the environment and determine what concrete implementation the alias should be set to.
  • Submodules
    • Example Use Case: Below
    • Benefits: Allows for multiple implementations of an interfaces to be available at the same time. This also keeps all of the coding to implement the pattern within powershell, so it can be dynamically updated.
    • Downsides: Your code will be unable to use object piping when calling functions using this pattern. And, it’s a lot more work to implement since intellisense won’t be able to help you.
    • How it works: It uses Invoke-Expression to dynamically call different implementations of a function. Example below.

Submodule Pseudo-Interface Pattern

A module which uses submodules might look something like this:

image

In the picture above, the Notifications module contains multiple submodules, but the 3 that are expanded in the directory structure are NSConsole, NSEmail, and NSSlack. Each of these modules expose two public functions which the parent module can use: Send-NotificationToSubscription and Initialize-Module.

They all implement the same function definition for Send-NotificationToSubscription. For example, here’s NSConsole’s:

The submodule, NSConsole, is called from Notifications’ Send-Notification function. The Send-Notification calls the submodule dynamically, which makes it act very similarly to an interface:

You can see within Send-Notification code that the pattern is actually used 3 times:

  • "$($global:Notifications.AMModule)\Save-Notification": This calls the Auditing Manager for the system to record the notification within the audit log. There are implementations for the production code and for local development. This code also be implemented using Function Overrides or Aliases.
  • "$($global:Notifications.CMModule)\Select-MatchingSubscription": This calls the Configuration Manager subsystem to retrieve subscription which would match the notification. There are implementations for the production code and for local development. This code also be implemented using Function Overrides or Aliases.
  • "$senderModule\Send-NotificationToSubscription": This can dynamically call different implementations of the Notifications Senders (Console, Email, or Slack). Since there are multiple implementations of the same interface it can only be done using the submodule pattern.

One final note. You should load the submodules which you intend to use at the time the top-level module (Notifications) is loaded. You can dynamically load them later, but it’s much easier to just load them when the top-level module is loaded.

Here’s what loading a module would look like:

And, Load-NotificationSender ($root is defined in the .psm1 example above):

Not a fan of Classes in Powershell

on Monday, January 21, 2019

Powershell’s most powerful ability is it’s completely dynamic runtime. The Powershell & ISE designers did an amazing job making the entire runtime able to create new functions, new variables, new modules and update them without needing to reload the runtime environment. It’s just always always modifiable all the time. Does that function not work correctly? That’s fine, just update the definition and the runtime will now use the new one. Don’t worry about all the variables and state information you have in the runtime, it’s still there. You don’t need to reload anything else, just update the one thing you want to change and start using it.

So, to go backwards a step … in PowerShell 5.0, classes were introduced. And, I understand why they were introduced. It’s not to make the scripting language more like a typed language, it’s so that the scripting language can do things that were previously only possible in typed languages. Like DI/IoC … more on that in another post.

However, classes in Powershell can only be loaded once. When they are loaded, they are compiled into dynamic libraries and loaded into the .NET CLR runtime that is hosting the Powershell runtime. And because it’s loaded into the CLR runtime, it cannot be removed/updated/altered. With enough work in Rosyln and/or MEF you can probably make the definition slightly dynamic with different versions of multiple dynamic library definitions; but that’s not the way Powershell classes work. They are only loaded into memory once and you are stuck with it until you create a new Powershell runtime. And creating a new Powershell runtime means losing all of your variable and state information. It’s a real drag.

Of course, there are some really difficult ways to alleviate these problems; but those solutions would take much greater minds than mine to implement. Two ways that I think might be possible are:

  • The .NET CLR team would need to implement an ability to Unload an assembly. I’ve read enough forum posts to understand just how insanely difficult that is to do, and that’s why I don’t have any criticism for it not being implemented. It was a design choice and it’s really really hard to actually implement.
  • The Powershell Runtime team could implement classes differently where they wouldn’t be turned into .NET assemblies. Instead they would be something natively within the Powershell runtime. Where the type system that Powershell supports would treat them just like .NET types, but they would actually be in the Powershell runtime and be completely dynamic.

Anyways, because Powershell classes don’t follow the structure that everything in Powershell should be dynamic and always modifiable within the runtime, I just can’t be a fan of using them.

Yet Another Command Line Parser for Slack/Hubot

on Monday, January 14, 2019

There are plenty of command line parsers available through npm (yargs, minimist, etc) and they all stem from a very strong POSIX root. But, we wanted something just slightly different. We wanted a parser that was specific for a slack command line structure that we were toying around with. The structure looked like this:

@botname {product-name} {action} <required-param> [optional-param]

With:

  • {product-name} being a requirement in the command syntax
  • {action} being a requirement in the command syntax
  • Parameter order would be respected, if a parameter name was not specified then it’s position should indicate which parameter it is
  • Parameter names are optional, but can be indicated by either a prefix of a single (-) or double (--) dash
  • The values for named parameters would be indicated by spaces instead of equal signs
  • And there’s a few other criteria in there that our brains just naturally figure out but we have to write into the code

Anyways, the point is that the design is just slightly different enough from a standard POSIX command line that we were going to need to build our own.

To do this we tried to figure out a simplified syntax to use in code. This syntax would be used in our hubot commands to parse the given input. Again, there are many parsers already built for this (regex comes to mind), but we built our own. The syntax looks like this:

product-name action {required-param-name} {optional-param:default value}

An example of this syntax would be:

servicepro close {ticketnumber} {memo:Verified and Closing}

If the given input was:

@botname servicepro close 5646831 “Memory usage has been lowered.”

The command line parser would need to return a javascript object that looked like this:

{
    “ticketnumber”: “5646831”,
    “memo”: “Memory usage has been lowered”,
    “success”: true,
    “errors”: {}
}

So, here’s the code with some poorly written tests commented out at the bottom:


Creative Commons License
This site uses Alex Gorbatchev's SyntaxHighlighter, and hosted by herdingcode.com's Jon Galloway.