1

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?

Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
David Wall
  • 42
  • 8
  • you should use foreach loop. and iterate each value and finally do the operation with that. – Ranadip Dutta Jun 19 '17 at 15:52
  • @Ranadip Dutta The problem here is that in the second example, there is only 1 file passed into the scriptblock, even though it appears to me to have been handled the same way as the first example, which does pass all the files. – David Wall Jun 19 '17 at 16:18

1 Answers1

2

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
Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
  • I liked the Scoping part . :) – Ranadip Dutta Jun 19 '17 at 16:25
  • 2
    @Ansgar Wiechers That is exactly the explanation I was looking for thank you so much! The concept makes a lot of sense to me based on the testing I did trying to solve this problem before bringing the question here. That concept is new to me, so the documentation is extremely useful. – David Wall Jun 19 '17 at 16:26