PowerShell Arrays and Dynamic Typing

A computer will always do exactly what you tell it to do.  When something doesn't produce the expected output, it is invariably because you haven't told the computer what to do properly.  PowerShell is an amazing scripting language and I have been spending a ton of time in it lately.  During that time I've come across a couple fun examples of a computer doing exactly what I am asking, with the resultant frustration you'd expect.


Arrays are not always arrays, unless you tell them so.  Sounds strange at first, but basically PowerShell is a dynamically-typed scripting language like Perl or VB.  When you declare a variable, you don't need to tell PowerShell what kind of variable it is, and that variable can change its type.  This is different from strongly-typed languages like C++ where you need to declare a particular type for each variable when you create the variable.  Changing the type of the variable must be done explicitly, or else it will throw an error.  For instance, consider the following in PowerShell:

PS C:\> $a = 1
PS C:\> $a | gm
   TypeName: System.Int32
We created a variable called "a" and gave it a value of 1.  Now it is an Int32 type variable.  Now what happens when we change the value to a string?
PS C:\> $a = "one"
PS C:\> $a | gm
   TypeName: System.String
Same variable is now a string!  The magic of dynamically-typed scripting in action.  Just for fun here's something else:
PS C:\> $a = 1
PS C:\> $b = "2"
PS C:\> $b | gm 
   TypeName: System.String
PS C:\> $a + $b
3
The second variable is a string, but PowerShell saw we were trying to combine an int and a string, and it checked to see if the string could convert to an int.  It could, so it added the two and produce an int output.  Neat, huh?

Sometimes you don't want to have PowerShell make these kinds of decisions for you.  And that can happen with a declared array:
PS C:\> $a = @()
PS C:\> Get-Member -InputObject $a
   TypeName: System.Object[]
See I've declared an array, and it is just an Object array so it can hold any type of content; int, string, boolean.
PS C:\> $a = (1,"two",$false)
PS C:\> Get-Member -InputObject $a
   TypeName: System.Object[]
Now I've got three objects in my array, and I can find that out by accessing the Count or Length property (count is an alias for length).
PS C:\> $a.Length
3
PS C:\> $a.Count
3
What if I tried to populate the array using a Cmdlet call?
PS C:\> $a = Get-Process
PS C:\> $a.Length
128
Boom, I've got 128 processes running.  That's a lot, maybe I should see what is going on...later.  This is great, but what happens if my call only returns a single process?
PS C:\> $a = Get-Process | select -First 1
PS C:\> $a.length
1
PS C:\> Get-Member -InputObject $a
   TypeName: System.Diagnostics.Process
Uh-oh.  My super sweet array is no longer an array, it's a process object!  Luckily the process object has a length property, so I am still getting a value back.  But. I have found instances where the length property does not exists, and suddenly instead of getting a 1 back for the length, you get nothing.  That could really screw things up if you are checking length to determine if your array is empty, like "$a.length -gt 0" now returns false if there is no length property.  The solution?  Strong-typing like such:
PS C:\> [array]$a  = @()
PS C:\> Get-Member -InputObject $a
   TypeName: System.Object[]
PS C:\> $a = Get-Process | select -First 1
PS C:\> Get-Member -InputObject $a
   TypeName: System.Object[]
Sweetness.  We've still got our array with all its properties.  My advice, when declaring a variable, think whether the type is super important to you.  If so, make sure to strong-type it like above.

Labels: , ,