Abstracting away side-effects with Higher-Order Functions in PowerShell
Cleaner, safer PowerShell code using functional abstractions
I generally write PowerShell with a pathological aversion to state and side-effects. In reality, these undesirable patterns are often unavoidable, especially in DevOps. Unavoidable CLIs like git
and compilers require the stateful shell cursor to move to a different directory before the CLI can be invoked; mutable collections must be maintained; integration tests and infrastructure code often requires provisioning of temporary resources in the cloud — all of these operations change the state of our system and subsequently put the system in an unknown, difficult-to-manage state.
In this article, we review a few examples of leveraging higher-order functions to abstract away side-effect management from our scripts.
Invoke-InDirectory
The Problem
Often in DevOps, you will need to run a command inside a specific directory. For example, git
(unlike most PowerShell commands) does not take a directory path argument and expects you to cd
into the correct directory before executing. Similarly, application building CLIs like npm
, docker
, dotnet
, etc., generally operate best in the directory root.
We could always Push-Location
or Set-Location
into the directory we need, but then we would always have to remember to come back out when the script completes. Worse, we need to add boilerplate try
/finally
statements in case our script fails — otherwise, the person running the script will unexpectedly find their terminal in a different location than they expected.
The Solution
We can write a higher-order function named Invoke-InDirectory
to abstract away the boilerplate required to invoke a command inside a given directory without causing side-effects (the terminal is in an unexpected location if the command errors).