AJ Raymond / PowerShell Constrained Variables

Created Sun, 11 Aug 2024 00:00:00 +0000

A little background

PowerShell is a dynamic “weak” typed language, but what does that mean, let’s break it down.

A dynamic language conducts type checking at runtime, in contrast to a static language, which validates at compile time. If PowerShell encounters an invalid type operation during execution, it will throw a System.Management.Automation.RuntimeException. This design choice gives PowerShell flexibility and ease of use as a command-line shell and scripting language. In a static language, this behavior would result in a compiler error based on the analysis of the known types. Since types must be known, this often results in verbose declarations within the language.

Although there isn’t an exact definition of weak typing, it is generally understood the language associates type with the value as opposed to the variable as in strong typing. In PowerShell, this allows the reuse of variables regardless of the previous type and means that variables can be declared without specifying types. The combination results in faster iteration as there’s no restriction on the developer during prototyping.

PowerShell supports designating type literals during assignment creating a constrained variable⁽ⁱ⁾. An important note is that PowerShell will coerce the value and only throw when there is not an implicit conversion as System.Management.Automation.ArgumentTransformationMetadataException. These variables add a layer of safety by acting as type assertions.

What does this solve

Code readability and maintainability. When code is written clearly and explicitly, it becomes easier to understand and work with. Explicitly defining the data type of a variable communicates the intention to other developers. This is especially true through the use of interfaces, which define implementation contracts for members. PowerShell does this for input, such as function parameters, and it allows signature matching based on types.

Error and unexpected behavior prevention. Many operators and methods have different behaviors when evaluating against collections. This can cause issues, especially with how PowerShell tends to unwrap single-item collections. The traditional solution is using the array subexpression operator @()⁽ⁱ⁾ however it has the consequence of creating a new array(s) even when the contents are already collection(s). The underlying operation is similar to Array addition (+=) and has the same performance considerations.

Better editor support. IntelliSense provides more accurate auto-completion and context-specific suggestions based on the expected types of variables, parameters, and methods. Additionally, strong types enable better code navigation and refactoring support, making it easier to understand and modify codebases.

It can be weird

Aesthetics is mostly subjective, but in most cases it doesn’t look particularly different and under the right conditions, some interesting implicit castings are allowed. In this case, the class must have a public, parameterless constructor and public, settable properties ⁽ⁱ⁾.

[System.DirectoryServices.DirectorySearcher] $Searcher = @{
    Filter = '(objectSid=S-1-5-32-544)'
    ClientTimeout = [timespan]::FromSeconds(30)
    SizeLimit = 1
}

Constrained variables aren’t always enforced when invoked, such as with Invoke-Command and the call operator (&), which is something to keep in mind if relying on types. Based on a conversation with SeeminglyScience, it seems that the issue stems from the type converter attribute being specifically designed for command argument binding.

[bool] $Example = 'false'; $Example
> System.Management.Automation.ArgumentTransformationMetadataException

& { [bool] $Example = 'false'; $Example }
> True

. { [bool] $Example = 'false'; $Example }
> System.Management.Automation.ArgumentTransformationMetadataException

Final thoughts

While PowerShell’s flexibility in typing allows for quick scripting and diverse usage, it’s important to balance this with clear code readability and potential error prevention. Being mindful of these aspects can lead to more robust and maintainable PowerShell. Embracing constrained variables has been essential in my experience, specifically in deployed scripts and modules.