Saturday, December 15, 2018

Calling a PowerShell Function like a Method is a Bad Idea

As a frequent contributor to Spiceworks’s PowerShell forum (come visit at: https://community.spiceworks.com/programming/powershell), from time to time I see a post that contains bad practice and sometimes worse.  One thing I see more often than I’d like, is post that calls a PowerShell function like a .NET method. This is, as the subject to this article suggests, is a very bad idea. Let me explain why.

First, let’s create a really simple function Add, like this:

Function ADD {
   Param ($A, $B)
   $A + $B
}

This function just adds two values and returns the results. Now, go and use the function both as a proper function call, then as a method call:

Psh[C:\foo]> Add 1 2
3

Psh[C:\foo]> Add(1,2)
1
2

As Iyou can see, the two uses of the Add function return decidedly different results. But why? At first sight, it appears quite illogical. To work out what is going on, under the covers,  create a richer Add function that describes what is being passed to and from the function, like this:

Function WhatAdd {
   Param ($A, $B)
   # Set to avoid error if GetType fails
   $ErrorActionPreference = 'SilentlyContinue'
   # Display the type and value of the first parameter
   "A is type   : [$($A.GetType())]"
   "A has value : [$A]"
   # Display the type and value of the second parameter
   "B is type   : [$($B.GetType())]"
   "B has value : [$B]"
   # Now calculate and describe the sum:
   $Result = $A + $B
   "Result has type  : [$($Result.GetType())]"
   "Result has value : [$Result]"
   # Return the result
   Return $Result
}

This function performs the same calculations and returns a results. It also displays the type and value of each input parameter and the result.  First try it out with calling a function like a cmdlet:

Psh[Cookham24:C:\foo]> WhatAdd 1 2
A is type   : [int]
A has value : [1]
B is type   : [int]
B has value : [2]
Result has type  : [int]
Result has value : [3]
3

Next, try calling Add like a method:

Psh[Cookham24:C:\foo]> WhatAdd(1,2)
A is type   : [System.Object[]]
A has value : [1 2]
B is type   : []
B has value : []
Result has type  : [System.Object[]]
Result has value : [1 2 ]
1
2

As you can see, the results of calling a function like a method are not what you might have expected.  What is happening is that when you call the Add function like a method, PowerShell assumes that the stuff inside the parentheses is an array of values that is passed to the first parameter of the function, not two separate parameter values. This means PowerShell passes an untyped array of two numbers as the value of the first parameter, and Null as the value of the second. The result of Adding Null to an array of values is an just an array of values (the input values) and not the addition of anything.

Of course, had you indicated in the function’s parameter block of the exact type of the parameters, PowerShell would have indicated the mismatch (passing an array into an parameter with a type of Int.

So two best practices in a single article.

  • First, avoid calling functions like a method. The results can be very much not what you were expecting or wanting.  As a user of someone else’s function, you don;t know how well, or badly they wrote it. Just call a function like a cmdlet.
  • Second, if you are writing functions, assume ALL user input is evil until you prove other wise and that your user will pass you data you were not expecting (like an array not a single integer). At a minimum, ensure that ALL parameters in your functions are explicitly typed. And consider ensuring that what you return is properly typed.  It all cuts down on bad input ruining your day.

No comments: