The Subtle Function

Time for my big epiphany of the week: Why PowerShell functions are so gosh-darned weird. Here?s the deal: Normally, in most programming languages, in order to return a value form a function you have to use a special value. Like, ?Return.? Consider the following example:

 

Function MyFunction() {
  $f = 1
  Return $f
}

 

In fact, this is a PowerShell function ? and it works just fine. Do this:

 

$val = MyFunction

 

And $val will equal 1, the value that was returned from the function. Straightforward enough. In fact, if you call the Return keyword, the function exits immediately. Consider this modification:

 

Function MyFunction() {
  $f = 1
  Return $f
  Write “Hello!”
}

 

?Hello? will never be output, because after Return executes, the function exits. All straightforward enough, but that?s not the weird part. The weird part is this: Anything output from a function becomes part of its result. Consider:

 

Function MyFunction() {
  $f = 1
  $g = 2
  $f
  $g
}

 

No Return keyword ? yet the function returns 1 and 2. And therein lies the catch! At first, this seems like insanity: Why would merely outputting something make it part of the return value? Ah, this is subtle but cool. It?s important to realize that MyFunction is not returning 12 (the concatenation of 1 and 2), but rather, it is returning a collection of two objects. The first objects is 1, and the second object is 2. You could do this:

 

$var = MyFunction
Foreach ($value in $var) {
  Write $value
}

 

And you?d be enumerating the two values in the collection. And this is why ?outputting? something from a function adds the output ?value? to the function?s return: You?re not outputting values, you?re outputting objects ? like everything else in PowerShell! The Return keyword can only return a single value ? but the technique I?ve just shown allows a function to return a whole gosh-darn collection!

 

This becomes incredibly important in a filtering function. This is a function that accepts a collection of objects, and can execute some given block of script ? conveniently called a Scriptblock ? for each object in the collection. Consider:

 

Function Where-OldSP {
  PROCESS {
    If ($_.ServicePackMajorVersion ?lt 2) {
      $_
    }
  }
}

 

This function will accept a collection of objects ? I?m planning for them to be WMI objects of the Win32_OperatingSystem type. For each object, the PROCESS block will execute once. Within that block, the $_ variable represents the current object, and if its ServicePackMajorVersion property is less than 2, then the object will be output from the function. If the property isn?t less than 2, then the object won?t be output ? effectively, this is a literal filter, since it?s taking a collection of objects and potentially returning a smaller, filtered collection of objects.

 

And this is why merely ?outputting? a ?value? ? actually the entire $_ object, in this case ? is so valuable. With the Return keyword, I?d only be returning ONE object. This way, I can easily examine each object and just output the ones I want. I don?t need to build a collection and then output that; PowerShell simply appends everything I?ve ?output? into a collection, automatically, so my script can be shorter. That means less work for me ? and subtle or not, that?s a good thing.