powershellpowershell-5.1powershell-7.3

Cannot bind argument to parameter '' because it is null


I have this strange observation and I don't know how to cope with this:

I'm reading a list of files from a directory. I pipe these through a sequence of Select-Object, Sort-Object, Select-Object calls in order to retrieve just a single property. Finally, I feed the result to a pipe.

Now, when Get-ChildItem retrieves no files, I get the error message: Cannot bind argument to parameter 'File' because it is null.

Here's an MRE:

function Test
{
  [CmdletBinding()]
  param
  (
    [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]$File
  )
  process { }
}


$arr = (Get-ChildItem -Filter '*.xxxx' |
    Select-Object -Property 'Name',
    @{
      name = 'FileObject'
      expression = { $_ }
    } |
    Sort-Object -Property 'Name').FileObject


$arr | Test

(Please note: this is just an MRE, not a meaningful piece of code. Don't focus on the Select-Object expression.)


When I change the last line to: Sort-Object -Property 'Name') by removing the .FileObject, everything runs flawlessly.

An empty array is an empty array, I guess. In both cases, $arr -eq $null is $true. So, why do I get different behaviour? How can I make this run flawlessly (and still being strict) with the FileObject property still being the return type?


Solution

  • There is a difference between $null and AutomationNull.Value. When Cmdlets, Functions and Script Blocks return nothing what you actually get is this type of null value but then by expanding on the .FileObject property when the statement returned Automation.Value you're effectively getting $null instead.

    If you try to access a property or sub property of an object that doesn't have the specified property, you get a $null value like you would for an undefined variable. It doesn't matter if the variable is $null or an actual object in this case.

    See Everything you wanted to know about $null for more details.

    $var1 = $null
    $var2 = & { }
    
    function Test {
        [CmdletBinding()]
        param
        (
            [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]$File
        )
        begin { 'begin' }
        process { 'process' }
        end { 'end' }
    }
    
    $var1 | Test # fails on `process` block
    $var2 | Test # only `begin` and `end` are called, no items are bound from pipeline
    $var2.SomProperty | Test # now is `$null`, thus same issue as first example