THE SQL Server Blog Spot on the Web

Welcome to SQLblog.com - The SQL Server blog spot on the web Sign in | Join | Help
in Search

Aaron Bertrand

Aaron is a senior consultant for SQL Sentry, Inc., makers of performance monitoring and event management software for SQL Server, Analysis Services, and Windows. He has been blogging here at sqlblog.com since 2006, focusing on manageability, performance, and new features; has been a Microsoft MVP since 1997; tweets as @AaronBertrand; and speaks frequently at user group meetings and SQL Saturday events.

PowerShell, Start-Job, -ScriptBlock = sad panda face

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.

 

Published Saturday, January 29, 2011 6:04 PM by AaronBertrand

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

 

AllenMWhite said:

Aaron, the @() construct creates an empty collection, so the @($foo, $bar) creates a collection with $foo in the [0] member and $bar in the [1] member, which of course feeds your $args collection.  I learned about the @() construct in Dr. Tobias Weltner's PowerShell book, which you can download free at his site at http://www.powershell.com/

January 29, 2011 5:29 PM
 

Twitter Trackbacks for Aaron Bertrand : PowerShell, Start-Job, -ScriptBlock = sad panda face [sqlblog.com] on Topsy.com said:

January 29, 2011 6:11 PM
 

BartekB said:

Yes, working with jobs can be real pain in the... back. :/ BTW: for more complicated jobs you can use param statement inside scriptblock and make things more obvious if you read them later. Unfortunately -ArguemtnList does not supports named parameters, so all param () elements need to come in the right order.

Also, if all you need is expanded parameter just expand them when you create scriptblock:

Start-Job -ScriptBlock ([scriptblock]::create("C:\csv\JobTest.ps1 $foo $bar"))

January 29, 2011 6:37 PM
 

Scott R. said:

Aaron,

Interesting reading about your PowerShell / PerfMon exploits, especially as another PowerShell newbie.

As an alternative, have you considered the option of using a PerfMon collection to a text file (csv or tsv) created and managed by the PerfMon tools built in to the OS?  The collection definition can be created and scheduled using either the interactive GUI approach or using the LogMan command line utility.  I don’t know if this approach is useful for your situation.

I am getting more into using PowerShell in different ways, and am in favor of using it where it best fits, and in combination with other tools where they best fit.

Just some more options to consider in the buy / build / reuse spectrum of solutions.

Thanks for sharing your efforts.

Scott R.

January 30, 2011 6:02 PM
 

AaronBertrand said:

Hi Scott,

Yes, I have used LogMan in a previous iteration of this project - I'll talk a bit about this in a post I'll be ready to publish in the morning.  The reason I went with PowerShell is simply to consolidate the technologies used to preform various aspects of the project (building VMs, simulating load, running queries, and collecting counters).  If LogMan had a significant advantage over PowerShell I probably would have stuck with it; the only advantage I saw, though, was that I already had a few batch files written.

Cheers,

Aaron

January 30, 2011 6:11 PM
 

Aaron Bertrand said:

In a current project, I need to collect performance counters from a set of virtual machines that are

January 31, 2011 7:54 AM
 

Brett R said:

Dude, you saved me hours!!!!!  I was literally just starting on the frustrating road you walked and found this page....TG for Google and you my friend!

April 15, 2011 1:57 PM
 

James J said:

You saved me many hours of hair pulling, me having learnt Powershell only yesterday and was happily creating scripts before I stumbled across this not so intuitive syntax.

June 13, 2011 9:25 PM
 

Aaron Bertrand said:

Brett & James, great to hear, glad it helped!

June 13, 2011 10:18 PM
 

Benchmarking I/O: The test rig | David Curlewis said:

June 20, 2011 6:42 AM
 

Mike said:

I also spent frustrating hours about a year ago with this, but I ended up with a different solution where I didn't need the empty array "@()" to get the argument list to work. Instead I used the -filepath parameter. I don't know if it helps for what you were doing, but here's what I use:

$MyJob = start-job -name $Index -initializationscript {Add-PSSnapin Microsoft.Exchange.Transporter} -filepath "c:\scripts\migrate.ps1" -ArgumentList $User,$Index

Anyway, your post is great info, thanks for the post!

-Mike

July 4, 2011 12:51 PM
 

diver said:

Thanks for your sharing.

I tried and tried and tried and then I started to search the internet... And found your really helpful tip!

Lots of wasted time for a simple job to do.

August 23, 2011 9:53 AM
 

powershell si tratarea exceptiilor | Jurnal de programator said:

September 26, 2011 3:12 AM
 

Jeffery Hicks said:

Scriptblocks can use the Param statement just like functions. I find this a little easier to follow and use

Start-Job -ScriptBlock {param([string]$foo,[string]$bar) C:\csv\JobTest.ps1 $foo $bar }  -ArgumentList $foo,$bar

The arguments are still passed in sequential order. Here's a post with more examples of this technique. http://jdhitsolutions.com/blog/2010/08/friday-the-13-script-blocks/

October 13, 2011 12:51 PM
 

GLOOX said:

THANKS A LOT !

November 30, 2011 8:47 AM
 

Chris said:

You can also do the following, which I think is a bit simpler:

Invoke-Expresion "Start-Job -ScriptBlock { C:\csv\JobTest.ps1 $foo $bar }"

Invoke-Expression will expand your variables prior to calling Start-Job.  I just found this tonight on some other website (sorry, don't remember where it was).

January 4, 2012 12:08 AM

Leave a Comment

(required) 
(optional)
(required) 
Submit

About AaronBertrand

...about me...

This Blog

Syndication

Powered by Community Server (Commercial Edition), by Telligent Systems
  Privacy Statement