Search code examples
powershellparametersscriptblock

How to pass results of Get-Childitem into a Scriptblock properly?


In this example, I'm trying to pass the results of Get-ChildItem into a scriptblock, with limited success and unexpected results.

C:\temp contains 3 files:

  • A.txt
  • B.txt
  • C.txt

What I want is to pass $files into the scriptblock here, and maintain the entire list of three files that Get-ChildItem has returned. In other words, I would like for $files to return A.txt, B.txt, and C.txt, both inside and outside the scriptblock.

I am able to do this, but I'm not able to do it reliably, and cannot explain why. I do not want to have to rely on a "dummy" variable to make this work properly, I'd like to understand what the difference is.

The first example here returns all of the files:

$files = Get-ChildItem -Path "C:\temp"
$test = $files.Get(0)
Write-Host $files.Count

Start-Job -ScriptBlock {
    Param($test, $files)
    Write-Host $files.Count
} -ArgumentList $test, $files | Out-Null

Wait-Job * | Out-Null
$results += Get-Job | Receive-Job

The second example only passes A.txt into the scriptblock:

$files = Get-ChildItem -Path "C:\temp"
Write-Host $files.Count

Start-Job -ScriptBlock {
    Param($files)
    Write-Host $files.Count
} -ArgumentList $files | Out-Null

Wait-Job * | Out-Null
$results += Get-Job | Receive-Job

Can someone help to explain the proper way to do this?


Solution

  • PowerShell unrolls lists when passing them to a scriptblock, so that the first value goes to the first parameter, the second value to the second parameter, etc. instead of the entire list/array going to the first parameter.

    In your first example you pass an argument list that consists of a single value and an array of values. When unrolling that (outer) list you still have two arguments (a single value and an array).

    In your second example you pass just an array. When unrolling that parameter list you end up with a list of three individual values, the first of which goes to the first parameter of the scriptblock.

    To prevent this behavior you need to "mask" lone arrays by wrapping the variable in another array with the unary array construction operator:

    Start-Job -ScriptBlock {
        Param($files)
        Write-Host $files.Count
    } -ArgumentList (,$files) | Out-Null
    

    Alternatively use the using scope modifier instead of passing arguments:

    Start-Job -ScriptBlock {
        Write-Host $using:files.Count
    } | Out-Null