Beginning Use of PowerShell Runspaces: Part 1


Summary: Boe Prox presents some tips about beginning use with runspaces.

Honorary Scripting Guy and Cloud and Datacenter Management MVP, Boe Prox, here today filling in for my good friend, The Scripting Guy. 

   Note   This is a four-part series that includes the following posts:

Let's say you are working in PowerShell and you want to kick off something to run in the background while you are working on some other things in the console. You would usually use something related to PSJobs, such as the *-Job cmdlets. Or you might use the –Asjob parameter that you see in some cmdlets, such as Get-WMIObject.

The main complaint in some of these efforts is that this causes another PowerShell process to be created and loaded with all of the format and type files, creating the PSDrives and such. This can take up a lot of resources depending on how many jobs you plan to run.

Using runspaces is a great alternative to get around this issue. Runspaces create a new thread on the existing process, and you can simply add what you need to it and send it off running. The downside is the level of effort that is required to create the runspace and the PowerShell instance to run it. Additionally, you have to manage the whole process by kicking off the instance and monitoring it for completion. Then you need to retrieve the information (if applicable) and tear down the runspace and PowerShell instance for disposal.

Using the built-in jobs framework makes this easy because you simply run something like Start-Job and then Receive-Job to get the data. Everything else happens behind the scenes. My plan is to take difficulty that may appear with using runspaces and show you how quickly and simply you can kick off a command in a runspace and pull it back without breaking a sweat.

To start off, I will make use of the type accelerator and use the Create() method to quickly create the instance. From there, I'll add a script block and kick off the instance. Note that this already has a runspace built-in, so I do not have to worry about creating a new runspace at this point.

$PowerShell = ::Create()

[void]$PowerShell.AddScript({

    Get-Date

})

$PowerShell.Invoke()

The result of running this is simply the return of the current date and time. Also look at where I used [void] so that it doesn’t pollute my pipeline or anywhere in the output. What you may not know is that this is no different than what would run if I typed Get-Date in my console—which is not really useful if we want a way to run things in the background. Add a Start-Sleep –Seconds 10 in my example script block, and you will see what I mean.

Before I jump into some better multithreading examples, I am going to show the same example, but this time I am creating a runspace to work with and adding it to the PowerShell instance.

$Runspace = [runspacefactory]::CreateRunspace()

$PowerShell = ::Create()

$PowerShell.runspace = $Runspace

$Runspace.Open()

[void]$PowerShell.AddScript({

    Get-Date

})

$PowerShell.Invoke()

I need a better way to run a command in the background so the console can be open to do other things. Instead of using Invoke() to kick off our commands, I will instead use BeginInvoke(). This will give back an async object, which can be used to monitor the state of our currently running background command.

Assuming that everything works out, we will eventually see that the background command has ended and I am free to retrieve the output and dispose of the PowerShell instance (so I do not have the possibility of memory leaks or something else).

First things first. Let’s kick off a background command and get back the object so we can track it.

$Runspace = [runspacefactory]::CreateRunspace()

$PowerShell = ::Create()

$PowerShell.runspace = $Runspace

$Runspace.Open()

[void]$PowerShell.AddScript({

    Get-Date

    Start-Sleep -Seconds 10

})

$AsyncObject = $PowerShell.BeginInvoke()

We can now inspect our Async object and view its status to see how things are going.

$AsyncObject

Image of command output

Based on the IsCompleted property, the command is currently running in the background, so I need to wait a little bit longer and then check again. When it shows True, I can begin processing the data and tearing down the instance.

Let’s assume that this has finally completed, and now I need to get the output. First I need to use the async object that was saved as the key to unlock the door on the PowerShell instance when I call EndInvoke(). As soon as I do this, the output is displayed. It is a good idea to save the output for future use.

$Data = $PowerShell.EndInvoke($AsyncObject)

Next up is to clean up by properly disposing of the instance. Nothing too crazy, but it is definitely necessary to ensure that I do not set myself up for disaster by having resource issues.

$PowerShell.Dispose()

And that is it for today and beginning with PowerShell runspaces. Tomorrow, I'll build on what we learned by incorporating parameters and arguments into runspaces.

I invite you to follow the Scripting Guy on Twitter and Facebook. If you have any questions, send email to the Scripting Guy at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. Until then, see ya!

Boe Prox, Windows PowerShell MVP and Honorary Scripting Guy

Comments (6)

  1. Terry C says:

    Hi Scripting guys !
    Your recent post is very interesting as I needed to execute multiple ‘same script’ in parallel. As you wrote at the beginning I was intended to use the ‘Job’ to do what i needed but now I am not sure to follow my initial idea. I do not understand when it is
    better to use Job vs Runspaces.

    Let’s suppose that my script do something like that for one of customers (just for illustration….)
    1. Update Azure Service Bus (stop & start StreamAnalytics are so loooong)
    2. Backup Azure SQL Database
    3. Update Azure website
    4. Do coffee.

    And i need to repeat several time above steps because I have 10 customers. Shall you suggest Runspace or Job ? Which criteria do you use to choose Runspace ?

    Regards,
    Terry

  2. Anonymous says:

    Powershell Praxis

  3. Boe Prox says:

    Hi Terry C,

    It really depends on what you are looking to do. If it is something rather simple and do not care if you have to throttle anything, then a PSJob (*-Job) would be fine. But if you wanted quicker execution of the commands in the background as well as throttling,
    then you should look at using a runspace instead.

    Based on your example, you could really go either way with this. If you are comfortable with runspaces, then I would go with that. But if you are looking at something that others might need to troubleshoot when you are not around, PSJobs will provide an easier
    approach.

  4. Rowland G says:

    Nice job on this article!

    Thanks!

  5. Russell says:

    When I attempt to use ::Create()

    At line:1 char:15
    + $y = ::Create()
    + ~
    An expression was expected after ‘(‘.
    + CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : ExpectedExpression

    Using Powershell v4 on Win8Enterprise 64-bit

    1. WayneCC says:

      It looks like the assembly was assumed in previous versions of PS. Explicitly providing the assembly name before ::Create() gets around this issue…

      $PowerShell = [System.Management.Automation.PowerShell]::Create()

      That worked fine in PS 5.0 on Windows 10.

Skip to main content