foreach ($i in $null)

It took me a long time to warm up to PowerShell. In the early days my team was writing scripts that had to run on Windows 2003, Windows 2003 R2, Windows 2008, and WinPE 2.x. Our tools of choice followed my Scripture of the Least Common Denominator:

  • Thou shalt write Batch.
  • Thou may write C# when warranted, but never VB.Net, and never for WinPE.
  • Thou may write VBscript when Batch is proveably insufficient and C# is too much.
  • Thou may use AutoIT as the tool of last resort.

We wrote a lot of Batch. A little C# here and there. A few VBscripts to handle complex logic. And I think we weaned ourselves down to one or two AutoIT scripts to deal with “silent installs” that weren’t entirely silent. But eventually I needed to do something that only PowerShell could do: manipulate a bunch of VMware VMs in order to completely automate our system image creation process. With a couple weeks development effort I took a process that was a painful burden for my team and made it something that anyone could kick off with a single command and have results within hours. Thus began my love of PowerShell. I left that team and came back. Went to another company, and another still. PowerShell has been a key tool along the way. What has pained me for years is that, despite the fact that PowerShell now runs everywhere, including WinPE, and while Microsoft has heavily invested in supporting PS automation, to the point that some configurations don’t even have GUIs… PowerShell has still not achieved Least Common Denominator status because feature support is so fragmented. PowerShell v3 introduced lots of great features, but dropped Windows 2003 support. At the time there was a lot of Windows 2003 still out there and even now I still have some in my current environment. PowerShell v4 dropped Windows 2008 pre-R2. And now PowerShell v5 is coming with no Windows 2008 R2 support. Know what I have very little of? Yeah, Windows 2012. I love it, don’t get me wrong, but the corporate world moves slowly. Windows 2008 R2 is the workhorse of my current environment. And sadly, many of those servers still don’t even have PowerShell v3. I try to carefully avoid writing code on my workstation that uses v3+ features that aren’t available on my servers but there are many Gotchas waiting to get you. Which brings me to the PowerShell “bug” that I encountered today in v2. For very technical reasons it is actually a “feature”, but the behavior was eventually modified in v3. What would you expect this code to do:

foreach ($i in $null) { Write-Host ($i -eq $NULL) }

If you said “Nothing”, you would be right in PowerShell v3+. In PowerShell prior to v3, however, the script will output True. This isn’t necessarily a problem in this contrived example, but many things you do in PowerShell are leveraging the .NET FrameWork to provide the underlying functionality, and many of those things will throw an exception if you pass a $null argument. The technical reason for this happening is that the PowerShell programmers wanted this behavior to work:

foreach ($i in "Hello World") { Write-Host $i }

In many languages this would throw an error because the value being passed is not an array, collection, or other enumerable type. When you consider this in the context of how PowerShell uses pipelines, throwing an error in this circumstance is probably not desirable, so the PS foreach implementation treats a single value as an array containing a single item. Now consider this code:

foreach ($i in @($null)) { Write-Host ($i -eq $null) }

That code outputs True in any version of PowerShell. foreach isn’t being passed $null, but rather an array whose only item is $null, and that’s a perfectly reasonable thing to do. So to wrap this up, in early versions PowerShell, foreach would treat single values as an array with a single value in order to match expectations. This created an edge case with $null, since PowerShell treats $null just like any other value. In PowerShell v3, foreach was modified to check for singular $null values and skip the loop. Circling back to how I tripped on this Gotcha, consider this code:

function n { }
$o=n
Write-Host ( $o -eq $null )
Write-Host ( n -eq $null )
Write-Host ( n -ne $null )
Write-Host ( (n) -eq $null )

In any version of PowerShell, the middle two Write-Host lines will not output anything, but the other lines will output True. What’s happening here is that n doesn’t return anything. If you assign that “nothing” to a variable, PowerShell’s assignment operator converts it to $null because nothing doesn’t really exist in PowerShell. In the case of (n) you’ve effectively assigned n‘s output to a temporary variable so the same thing happens. And that’s how PowerShell tripped me up today. One of my calls didn’t return anything because a non-fatal error occurred, which caused a variable assignment to be converted to $null, which triggered the unexpected behavior of foreach in PowerShell v2. Luckily I was aware of this change in behavior between versions and as soon as I saw the error I knew what had gone wrong.

Advertisements

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 )

Google+ photo

You are commenting using your Google+ 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 )

w

Connecting to %s