Why does Powershell silently convert a string array with one item to a string

Consider the following Powershell script, which searches for folders in C:\ with a 'og' in their name:

PS C:\> (ls | %{$_.Name} | ?{$_.Contains("og")})
PerfLogs
Program Files
setup.log

Now I narrow down the search to get only one item:

PS C:\> (ls | %{$_.Name} | ?{$_.Contains("Prog")})
Program Files

The strange thing is that the first operation yields an array, whereas the second operation (which is IMHO semantically the same operation, so it should yield the same type of result) yields a string. This can be seen in the following result:

PS C:\> (ls | %{$_.Name} | ?{$_.Contains("og")}).Length
3
PS C:\> (ls | %{$_.Name} | ?{$_.Contains("Prog")}).Length
13

This can be very irritating, since apparently there are less folders which match 'og' than those who match 'Prog'.

Evidently, PowerShell implicitly 'unboxes' a single-item array to a single object, and we never get an array of length 1. It seems that every time I want to count the results coming over the pipeline, I have to check if I'm dealing with an array or not.

How can I prevent this from happening? How do you deal with this?


Solution 1:

Evidently, PowerShell implicitly 'unboxes' a single-item array to a single object,

And zero item results to $null.

How can I prevent this from happening?

You can't.

How do you deal with this?

Use the array constructor (@(...)) to force a collection (possibly with zero or one elements) return:

$res = @(ls | %{$_.Name} | ?{$_.Contains("Prog")})

Solution 2:

Note the difference between these two results:

PS C:\> ConvertTo-Json -InputObject @(1)
[
    1
]
PS C:\> @(1)|ConvertTo-Json
1
PS C:\>

The point is that the 'unboxing' is being done by the pipe operation. ConvertTo-Json still sees the object as an array if we use InputObject rather than piping.

Solution 3:

An alternative to this problem is to explicitly set the variable type to be an array like this:

[array]$res = (ls | %{$_.Name} | ?{$_.Contains("og")})

Solution 4:

This has been resolved in PowerShell v3:

http://blogs.microsoft.co.il/blogs/scriptfanatic/archive/2012/03/19/Counting-objects-in-PowerShell-3.0.aspx

On a side note, you can find if a name contains something using a wildcard:

PS> ls *og*