PowerShell: Why the Count Property or += Don’t Work Sometimes

One thing that I occasionally forget (and it can take me a while to remember the fix) is some apparently strange behaviour when processing an array.  Sometimes array-based methods don’t work when there’s only one item (or no items!) to be processed.

It’s all down to PowerShell’s automatic type conversion and is pretty easy to fix.

The bugs for this come up when I write scripts to process a list of objects, normally from a CSV or the pipeline.  I write the code to process the array after it’s been created and it’s all great!  Then later on I get some bizarre behaviour when the list of items to process is empty or only has one entry.

So let’s say we had a CSV file like;

Name,EMail
Herring,herring@herringsfishbait.com

If I then import that into a variable;

$Records=Import-CSV c:\names.csv

I can then see the following;

$Records.Count -eq 0
False
$Records.Count -gt 0
False

So PowerShell returns the count of items in $Records is neither equal to 0 nor greater than 0.  Any logic in my script that uses that comparison would see some odd results, to say the least.

I get a straight error if I tried to add a new record to the variable;

PS C:\Users\Neil> $Records+=New-Object -TypeName PSObject -Property @{Name="Newuser";Email="Newuser@herringsfishbait.com
"}
Method invocation failed because [System.Management.Automation.PSObject] does not contain a method named 'op_Addition'.
At line:1 char:1
+ $Records+=New-Object -TypeName PSObject -Property @{Name="Newuser";Em ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (op_Addition:String) [], RuntimeException
    + FullyQualifiedErrorId : MethodNotFound

Both of these problems come from the fact that $Records isn’t, in fact, an array.  In the case where I’ve imported a CSV with only one record, PowerShell helpfully sets the type of $Records to an Object, rather than an array;

PS C:\Users\Neil> $Records.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    PSCustomObject                           System.Object


PS C:\Users\Neil> @("1","2").GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

So in order to stop this happening, you need to force PowerShell to set $Records to an array (cast it to an array) even if there’s only one element being imported into it.  Then everything works as you expect.

PS C:\Users\Neil> [PSObject[]]$Records=Import-csv D:\temp\test.csv
PS C:\Users\Neil> $Records.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     PSObject[]                               System.Array


PS C:\Users\Neil> $Records.Count
1

You could also do;

PS C:\Users\Neil> $Records2=@(Import-csv D:\temp\test.csv)
PS C:\Users\Neil> $Records2.Count
1

 

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 )

Google+ photo

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

Connecting to %s