Careful with Arrays

by Nov 29, 2022

Careful with Arrays

With PowerShell you never know whether a cmdlet returns an array or a single object. That’s because PowerShell automatically wraps results in an array once a command returns more than one item:

# no array:
$test = Get-Service -Name Spooler
$test -is [Array]

# array:
$test = Get-Service -Name S*
$test -is [Array]

It’s critical to understand this because it means that runtime conditions can determine the nature of a variable. And this can cause bad things. Here is just one example to illustrate the problem:

Below code returns all service names that start with a “c”, then grabs the first one. That’s possible because there is more than just one service starting with “c”, so PowerShell returns an array in $servicenames, and you can then use a numeric index into this array to pick specific elements:

$Name = 'c*'

# get service names
$servicenames = Get-Service -Name $Name | Select-Object -ExpandProperty Name

# get first service name
$servicenames[0]

You cannot assume that $servicenames is always an array, though. If during runtime there is just exactly one service matching your request, the result is no longer an array but instead it would be the service name directly.

Why (and when) does that matter? It starts to matter the moment your code employs specific array functionality because that functionality may not exist in certain scenarios or behave differently.

To illustrate this, below code lists all services starting with “cry” now. Just one service matches the request. Therefore, $servicenames no longer is an array. It is now a string. When you use an index on a string, you get back a particular letter from that string.

The very same code now produces a single character instead of a service name:

$Name = 'cry*'

# get service names
$servicenames = Get-Service -Name $Name | Select-Object -ExpandProperty Name

# get first service name
$servicenames[0]

These examples may seem a bit constructed but you can find the underlying issue at the core of many hard-to-find script errors. That’s why it is important to always ensure that you actually get an array when your code uses array features.

One simple way of ensuring that you get an array is the construct @(): anything in the parenthesis is returned as an array. That’s why below code works regardless of whether the command returns one or more results:

$Name = 'cry*'

# get service names
$servicenames = @(Get-Service -Name $Name | Select-Object -ExpandProperty Name)

# get first service name
$servicenames[0]

To add a digital signature to a PowerShell script file (or any other file that is capable of carrying a digital signature for this matter), use Set-AuthenticodeSignature. Run the demo code below (adjust paths to file and certificate as needed):

$Name = 'cry*'

# get service names
[array]$servicenames = Get-Service -Name $Name | Select-Object -ExpandProperty Name

# get first service name
$servicenames[0]

When you run this code, the script file specified in $Path opens and shows the digital signature that was added to the bottom of your script:

Hello World!

$Name = 'cry*'

# get service names
[string[]]$servicenames = Get-Service -Name $Name | Select-Object -ExpandProperty Name

# get first service name
$servicenames[0]

However, [array] is much easier to use because it always works regardless of data type, and [array] is also easier to understand to users not familiar with types.

 


Twitter This Tip! ReTweet this Tip!