PowerShell: Simplify Complex Function Calls

Simplify the handling of complex functions or cmdlets using ScriptBlocks and Closures to capture parameter subsets.

powershell-logoA major component of DevOps is automation and for any automation work in Azure the recommended tool for the job is PowerShell.  Microsoft provides a large number of PowerShell cmdlets for managing all aspects of Azure including the classic Service API and ARM (Azure Resource Manager).  But one of the frustrations I’ve had with PowerShell is the tendency for very long parameter sets when invoking cmdlets or functions.  The result is usually that the invocation is a very long line or is wrapped across a few lines, with either of these affecting the readability of the code.  There are a number of ways to work around this:  wrapping data and code in an object (now much easier in PowerShell 5); passing a config object to a wrapper function; or what I’ll be covering in this post, using script blocks and closures.

First some background.  In PowerShell (2.0 or later), a ScriptBlock is a list of statements enclosed in curly braces that can be passed around and executed.  A script block can even accept parameters.  This is effectively a Lambda (or anonymous) function, though unfortunately due to the PowerShell syntax, invocation is slightly different from typical functions or cmdlets.  A Closure (or sometime Lexical Closure) is a function (or lambda function) that captures the state (i.e. shared variables) from its enclosing (lexical) scope.  Don’t worry if that doesn’t make sense yet, hopefully it will by the end of this post.

The Problem and Solution

At Lixar for deployments on a project I was working on, we needed to be able to send various types of deployment emails including pre-deployment warning, deployment start and completion emails, as well as various warning and error emails when issues are encountered.  A colleague helped by writing a separate script using the System.Net.Mail.SmtpClient object.  I won’t go into details of the script here, but the resulting invocation included a very long set of parameters and was used many times throughout the parent deployment script:

Notifications/DeploymentEmail.ps1 -EmailTemplate initialemail_template.cshtml -Project $($ProjectName) -Version "${BaseVersion}.${BuildNumber}" -Environment $($EnvDisplayName) -ExpectedDeploymentTime $($ExpectedDeployment) -Type $($DevType) -Name $($DeployerName) -Attachments $($ReleaseNotesPath) -RecipientEmailList $($EmailGroup) -YourEmail $($SenderAccount) -Password $($AccountPassword) -additionalMsg $($appsMsg)'

Sure, not all parameters are required all the time and the script could be refactored, but sometimes you just have to work with what you are given. With a function like above you may want to come up with 3 or more simpler functions (e.g. status emails, error emails, warning emails, etc.) that have the common parameters baked in.
This example is a bit too long and was just for illustration purposes, so let’s continue with a simpler example illustrating how to handle this problem. Consider this simpler, though contrived example:

function Get-MessageObject
{
   param
   (
      [String]$MsgType,
      [String]$Message,
      [String]$CurrentDayOfWeek
   )
   $result = New-Object -TypeName psobject -Property @{
                       'MessageType' = $MsgType
                       'Message'    = $Message
                       'DayOfWeek'  = $CurrentDayOfWeek
                       }
   $result
}

Here we have a function that takes 3 parameters, but we may imagine 3 common usages for message types: Info, Warning, and Error. Similar, unless this is a long-running script we might want to avoid determining the CurrentDayOfWeek every time we need to call this function.
In functional programming there is a common technique called Partial Application which is used to apply only a subset of the function parameters and get back another function which only requires the remaining parameters.  A way of doing this in languages which don’t have built-in <em>Partial Application</em> is to wrap the function in a Lambda function.  As alluded to earlier, here is where PowerShell’s <em>ScriptBlocks</em> become useful:

$MsgType = 'Info'
$currentDay = Get-Date -UFormat '%A'
$getInfoObject = {
   param
   (
      [String]$Message
   )
   Get-MessageObject -MsgType $MsgType -Message $Message -CurrentDayOfWeek $currentDay
}
$myInfo = &amp;amp;amp;amp; $getInfoObject -Message 'Have a nice day!'
$myInfo

Message          MessageType DayOfWeek
-------          ----------  ---------
Have a nice day! Info        Wednesday

This ScriptBlock is an invocable object that takes a single parameter (Message). The other parameter values ($MsgType and $currentDay) are inherited from the parent scope. The ScriptBlock is assigned to the variable $getInfoObject which can then be invoked (with the & operator) using different Message parameters.
But we won’t stop there. As mentioned, the “Baked In” parameters are getting their values from the parent scope, but they aren’t really baked in yet. Changes in the parent scope may change the behaviour of our script block. Furthermore, what if we were wrapping a script or a function imported from a module where the path to that entity may be redefined and reloaded (e.g. loading different implementations) — we most likely want dependencies like functions or scripts to be baked in when we define the script block.
Consider the following, final function:

function New-MessageObjectGeneratorForToday
{
   param
   (
      [String]$MsgType
   )
   $currentDay = (Get-Date -UFormat '%A')
   {
      param
      (
         [String]$Message
      )
      Get-MessageObject -MsgType $MsgType -Message $Message -CurrentDayOfWeek $currentDay
   }.GetNewClosure()
}
$getErrorMsg = New-MessageObjectGeneratorForToday -MsgType 'Error'
&amp;amp;amp; $getErrorMsg -Message 'No error here.  Move along...'

Message                       MessageType DayOfWeek
-------                       ----------  ---------
No error here.  Move along... Error       Wednesday

Here we are converting our ScriptBlock into a Closure using the ScriptBlock’s GetNewClosure() method. This will bake in the $MsgType and $currentDay values. Additionally, we wrap this in a function which takes the single MsgType parameter, plus dynamically determines the current day, both of which are baked into the closure. The function’s return value is the closure. (Note that we don’t need to use the return keyword in PowerShell objects accumulated from statements within the function are automatically returned.)

There is a lot more that you can do with ScriptBlocks and Closures in PowerShell. ScriptBlocks are also useful in working with PowerShell jobs and workflows. Closures can be used to define Continuations or Callbacks. The underlying functional techniques can be used in a lot of languages so they may be worth learning even if PowerShell is not your thing. In many other languages dealing with Lambdas, Partial Application and Closures are often much easier.

Wrapping this up we can look back at that initial example of an email script referenced many times within a parent deployment script.  This technique of generating a closure can be easily adapted to simplify reuse of the complex email script.  The resulting closure(s) can be passed around to other functions so they can easily generate email messages as needed.  Note that there are some exceptions here:  if you are using Start-Job, or PowerShell Workflows, or some other technique which spawns a new PowerShell session or process, the enclosed scope variables will not be passed along unless you assign the variables with the Using keyword.

Update:

I just discovered how to convert the Closures, as created above, into functions:

$function:global:getErrorMsg = New-MessageObjectGeneratorForToday -MsgType 'Error'
getErrorMsg -Message 'This is now a function.'

Message                 MessageType DayOfWeek
-------                 ---------- ---------
This is now a function. Error      Wednesday

getErrorMsg now acts like any other normally defined function, however you cannot include a “-” character in the name so these functions cannot comply with the common <Verb>-<Noun> naming scheme of functions and cmdlets in PowerShell.

Advertisement

Author: jkirkham1965

I specialize in designing and implementing DevOps automation solutions, particular with the Azure Cloud platform. Additionally, I am interested in and explore functional programming in languages like F#, Haskell and ELM,; plus have had a long time interest in data science.

One thought on “PowerShell: Simplify Complex Function Calls”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: