I am working on a project where I am using PowerShell to collect a lot of performance counters from a lot of servers. More on that later. For now I wanted to highlight an important lesson I learned when trying to use Start-Job to call a PS script using -ScriptBlock and passing in parameters. This could be a comedy of errors if you haven't come across it before, so I thought it might be useful to throw up a quick post about it.
To keep things simple, let's say I am calling a script with two parameters, am exporting a CSV file, and want those parameters embedded in the name of the CSV file. Something nice and easy, like this:
param ([string]$foo, [string]$bar)
function JobTest {
param ([string]$foo, [string]$bar)
Get-Counter | Export-Csv "C:\csv\$foo-$bar.csv";
}
JobTest -foo $foo -bar $bar; |
Calling this from the command line is straightforward, and works as expected:
C:\csv\JobTest.ps1 "test" "1"; |
Result: success! The file "test-1.csv" is created and we are happy.
Now, let's make things a little more complicated. Let's say with one script I want to call multiple jobs that will call this script asynchronously for a set of values for $foo and $bar. I might try creating a second script file like this:
param ([string]$foo, [string]$bar)
function StartJobs {
param ([string]$foo, [string]$bar)
Start-Job -ScriptBlock { C:\csv\JobTest.ps1 $foo $bar }
}
StartJobs -foo $foo -bar $bar |
When I call this from the command line:
C:\csv\StartJobs.ps1 "test" "2"; |
Result: not so good. A file is created, but it is named "-.csv" (so "test" and "2" got lost somehow).
Next, I tried embedding the variables in quotes in the Start-Job call (in the remaining code samples, I am just showing the changes to line 6 of StartJobs.ps1):
Start-Job -ScriptBlock { C:\csv\JobTest.ps1 "$foo" "$bar" } |
Result: still not good. The "-.csv" file is over-written by a new version.
After that, I tried using -ArgumentList to pass the values in:
Start-Job -ScriptBlock { C:\csv\JobTest.ps1 "$foo" "$bar" } -ArgumentList $foo $bar |
Result: error message as follows:
Start-Job : Cannot bind parameter 'InitializationScript'. Cannot convert the "5" value of type "System.String" to type "System.Management.Automation.ScriptBlock". At C:\csv\StartJobs.ps1:6 char:11 + Start-Job <<<< -ScriptBlock { C:\csv\JobTest.ps1 "$foo" "$bar" } -ArgumentList $foo $bar + CategoryInfo : InvalidArgument: (:) [Start-Job], ParameterBindingException + FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.StartJobCommand |
Well, of course... silly me, I forgot the comma between $foo and $bar. Let's try this:
Start-Job -ScriptBlock { C:\csv\JobTest.ps1 "$foo" "$bar" } -ArgumentList $foo, $bar |
Result: no error this time, but we have created yet another file named "-.csv" so our arguments are still not getting passed in correctly.
A bit of searching around and I see that -ArgumentList should be used with $args[i] as opposed to the explicitly-named argument. Aha! I am lulled into the belief that I have finally figured out the problem. So I try this:
Start-Job -ScriptBlock { C:\csv\JobTest.ps1 "$args[0]" "$args[1]" } -ArgumentList $foo, $bar
|
Result: once again no error, but once again we have created another "-.csv" file. Bummer.
I search exhaustively for alternate syntax examples - surely someone out there is passing parameters into a Start-Job call? I find the @(arg, arg) syntax somewhere (all apologies, I don't recall where I spotted this). So I try it this way:
Start-Job -ScriptBlock { C:\csv\JobTest.ps1 "$args[0]" "$args[1]" } -ArgumentList @($foo, $bar) |
Result: nada. Still a poorly-named "-.csv" file. I try one more stab, and remove the quotes around the arguments within the -ScriptBlock:
Start-Job -ScriptBlock { C:\csv\JobTest.ps1 $args[0] $args[1] } -ArgumentList @($foo, $bar) |
Result: success! Finally, I have a file named "test-9.csv" - it only took 9 tries (and lots of cursing) to get the syntax right! For the PowerShell veterans and gurus out there, you are probably saying, "DUH!" But I am neither, and I spent a lot of time experimenting with this and trying to figure out what the problem was, so I hope this helps prevent the same frustration for someone else at some point.