1

I'm working on a function that accepts [Single]$DurationMS as an optional parameter. This is supposed to be float value. So in my function I have the following code to check if its been provided to the function. if it is provided I want to add the value to an object nested in another object.

if ($DurationMS -ne $null) {
$MyObject.attributes | Add-Member -MemberType NoteProperty -Name 'duration.ms' -Value $DurationMS
}

All looks fine except when I test it I get 0 in the duration and I can't figure out why.

duration.ms
-----------
          0

so my condition is evaluating to true but I don't understand why.

Nima
  • 39
  • 6
  • 3
    you have the `$Null` on the _right_ and that coerces the `$Null` to the type on the _left_. you are supposed to put the type you want to test against on the _left_. ///// also, you can do the test a bit more neatly by using just `if ($DurationMs)`. that will be false ONLY if the value is blank or $Null. ///// also also ... putting any "unusual" char in a property name is iffy. i would remove that dot in `duration.ms` and use something like `DurationMs` or `Duration_Ms`. – Lee_Dailey Apr 17 '22 at 03:09
  • 1
    alternatively, you can check if a parameter (`DurationMS` in this case) was used, using `$PSBoundParameters.ContainsKey('DurationMS')` as a condition – Santiago Squarzon Apr 17 '22 at 03:14
  • Thanks @Lee_Dailey I think this means I need to do some reading since your neat approach seems to have done the trick very easily. thanks! I agree about the unusual char, felt icky doing it but this will all be sent to another service that expects a duration.ms How do I mark your reply as the answer? – Nima Apr 17 '22 at 03:17
  • @Nima - a comment cannot be marked as an Answer. [*grin*] you can up-arrow it, but cannot make it into an Answer. do you think that my info is worth an Answer? the one by mklement0 seems to cover that and the various gotchas involved. ///// here is an article that may help with the `$Null on the left` idea >>> Everything you wanted to know about $null - PowerShell | Microsoft Docs — https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-null?view=powershell-7.2 – Lee_Dailey Apr 17 '22 at 04:32
  • @Lee_Dailey, a quick summary: placing a scalar operand, especially `$null`, on the LHS makes sense in general, but here it doesn't help, because `$DurationMs`, as a `[single]` instance, can never be `$null`. `if ($DurationMs)` will also be false if `0` was explicitly passed, so this shortcut for checking if a value was passed is only an option if (a) `0` isn't a valid value and (b) `0` should be allowed as an explicit way of signaling "no value was passed". – mklement0 Apr 17 '22 at 13:48

1 Answers1

3
  • [single] is a .NET value type, and instances of such types can never be $null.

    • [single].IsValueType returning $true tells you that it is a value type.
    • $null only applies to .NET reference types and tells you that is a reference to no object.
  • It is therefore pointless to test your [single]-typed $DurationMS parameter variable for being $null:

    • A [single] instance's default value is 0, so that your $DurationMS -ne $null conditional is effectively 0 -ne $null by default, which is $true.
  • The robust way to check if an argument was passed to a given (non-mandatory) parameter in a given invocation is to consult the automatic $PSBoundParameters variable, as Santiago Squarzon suggests.

    • This variable contains a dictionary that has entries for all explicitly passed arguments, keyed by their parameter names (sans prefix -); e.g., if your function is invoked with -DurationMS 1.2, $PSBoundParameters['DurationMS'] returns 1.2, and $PSBoundParameters.ContainsKey('DurationMS') indicates $true

Therefore:

# Was an argument passed to -DurationMS?
if ($PSBoundParameters.ContainsKey('DurationMS')) {
  $MyObject.attributes | 
    Add-Member -MemberType NoteProperty -Name 'duration.ms' -Value $DurationMS
}

The following aspects are incidental:

  • if ($DurationMs) would only work if you also wanted to consider an explicit argument of 0 to signal "no value was provided", because with a [single]-typed $DurationMs, if ($DurationMs) is the same as if ($DurationMs -ne 0)

    • PowerShell allows you to use an expression of any type in a Boolean context; with numeric types, 0 maps to $false, and any nonzero value to $true.
    • While this implicit to-Boolean conversion behavior is generally convenient, it has its pitfalls - see the bottom section of this answer for a summary of the rules.
  • Given that many PowerShell operators can implicitly operate on arrays (collections) as the LHS - in which case they act as filters, returning the subarray of matching items - it is generally better to place a scalar comparison operand on the LHS (in the case at we know that the non-literal operand is by definition also a scalar - a [single] instance - so that doesn't matter).

    • Placing the scalar on the LHS avoids false positives / negatives, such as in the following example:

      $arr = 0, $null
      # !! -> 'null', because (0, $null) -ne $null filters the
      # !!    array to @(0), and [bool] @() - perhaps surprisingly - is $false
      if ($arr -ne $null) { 'not null' } else { 'null' }
      
      # OK, with $null on the LHS
      # -> 'not null'
      if ($null -ne $arr) { 'not null' } else { 'null' } 
      
    • However, even on the LHS $null can exhibit unexpected behavior, namely with the
      -lt, -le, -gt, and -ge operators, as discussed in this answer; e.g.:

       $null -lt 0 # !! -> $true - even though [int] $null yields 0
      
    • If PowerShell offered a dedicated test for $null, these pitfalls could be avoided; implementing such a test - in the form $var -is $null or $var -isnull - was the subject of GitHub PR #10704; unfortunately, that PR was abandoned by its creator, and no one has picked up the work since, which is why no such test exists as of PowerShell 7.2.2.

  • As Lee Dailey points out, a property name such as duration.ms can be problematic, given that it contains ., which normally suggests a nested property access, given that an (unquoted) . serves as the member-access operator.

mklement0
  • 382,024
  • 64
  • 607
  • 775