Weekend Scripter: Max Out PowerShell in a Little Bit of Time—Part 2

Weekend Scripter: Max Out PowerShell in a Little Bit of Time—Part 2

  • Comments 5
  • Likes

Summary: Microsoft PFE, Jason Walker, talks about improving performance of Windows PowerShell by using runspaces.

Microsoft Scripting Guy, Ed Wilson, is here. Jason Walker is back with us today for the conclusion of his two-part weekend series. Be sure to read yesterday’s post before you read today’s:

Weekend Scripter: Max Out PowerShell in a Little Bit of Time—Part 1

And now, Jason…

Yesterday we explored using Windows PowerShell remoting (Invoke-Command) and Windows PowerShell jobs, and we ended with a teaser about runspaces. Today let’s take a closer look at runspaces.

Guest blogger, Boe Prox previously wrote about using runspaces: Expert Commentary: 2012 Scripting Games Advanced Event 2. In his post, he links to Windows PowerShell MVP, Dr. Tobias Weltner’s, video, which explains some of the advantages of using runspaces. I recommend that you check it out.

The basic steps to leverage runspaces are as follows:

  1. Create the runspace pool.
  2. Open the runspace pool.
  3. Create a Windows PowerShell object to the run commands.
  4. Specify which runspace the Windows PowerShell object will use.
  5. Add the commands.
  6. Invoke the command.
  7. Get the results from the command.
  8. Clean up the Windows PowerShell object and runspace pool.

Here are the steps in Windows PowerShell:

#Create the runspace pool

$RunspacePool = [RunspaceFactory]::CreateRunspacePool()

#Open the runspace pool

$RunspacePool.Open()

#Create PowerShell object

$Powershell = [PowerShell]::Create()

#Specify the runspace to use

$Powershell.RunspacePool = $RunspacePool

#Add the command to run

$Powershell.AddCommand("Get-Process")

#Invoke the command

$Runspace= $PowerShell.BeginInvoke()

#Get the results of the command

$PowerShell.EndInvoke($Runspace)

#Clean up

$PowerShell.Dispose()

$RunspacePool.Close()

For us to run this asynchronously against multiple servers, we have to make a few modifications to the example. To demonstrate, I will explain how this is done by using the Get-WinEvent example from yesterday.

  1. Define a list of servers to run the cmdlet against:

$Computers = “mail01”,”mail02”,”mbx01”,”mbx02”,”mbx03”

  1. Create an empty array that we will use later in the script:

$RunspaceCollection = @()

  1. Call the CreateRunspacePool method and specify a minimum and maximum amount of runspaces that are allowed to be open. These parameters are explained in the Windows Dev Center: RunspaceFactory.CreateRunspacePool Method (Int32, Int32, PSHost).

The following example creates a runspace pool with a minimum of 1 runspace and a maximum of 5:

$RunspacePool = [RunspaceFactory]::CreateRunspacePool(1,5)

  1. Define a script block (the cmdlet and arguments we want to run) that we will supply to the Windows PowerShell object:

$ScriptBlock = {

 Param($Computer)

Get-Process -ComputerName $Computer -FilterHashtable @{Logname='System';Id=1074} -MaxEvents 10 }

  1. Because we want to run Get-WinEvent asynchronously across multiple servers, we need to create multiple Windows PowerShell objects. Then we will add the script block that we created and the unique computer name to that Windows PowerShell object. A Foreach loop will make quick work of this task:

Foreach($Computer in $Computers){

 #Create a PowerShell object to run add the script and argument.

  $Powershell = [PowerShell]::Create().AddScript($ScriptBlock).AddArgument($Computer)

 

  #Specify runspace to use

  $Powershell.RunspacePool = $RunspacePool

 

  #Create Runspace collection

  [Collections.Arraylist]$RunspaceCollection += New-Object -TypeName PSObject -Property @{

   Runspace = $PowerShell.BeginInvoke()

   PowerShell = $PowerShell  

  }

 

}

  1. You’ll notice that we also added the Windows PowerShell object to the runspace pool. We then created a PSObject that contains the Windows PowerShell object, and we defined and invoked the runspace. To get the results of the runspace, we have to call the EndInvoke method from the Windows PowerShell object that contains the runspace. This PSObject acts as a collection that keeps everything nicely packaged.
  2. The only thing left to do is check for the runspace to be completed. When it is complete, we retrieve the results. I like to use a While loop for this type of task:

While($RunspaceCollection){

 Foreach($Runspace in $RunspaceCollection.ToArray()){

  If($Runspace.Runspace.IsCompleted){

   $Runspace.PowerShell.EndInvoke($Runspace.Runspace)

   $Runspace.PowerShell.Dispose()

   $RunspaceCollection.Remove($Runspace)

  }

 }

 

}

The EndInvoke method is used to retrieve the results from the completed runspace. Finally, we clean up. That’s all there is to multithread commands in Windows PowerShell using runspaces.

You can download a more complete solution from the TechNet Gallery: Get-AsyncEvent multi thread Get-WinEvent using runspaces.

Note   Get-AsyncEvent is written as a function, so you need to dot source the .ps1 file before you can run the function inside it. For more information, see How Do I Use a Windows PowerShell Script Containing Functions?

~Jason

Thank you, Jason, for this weekend’s blog posts and for sharing your time and knowledge.

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

Ed Wilson, Microsoft Scripting Guy 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Out of curiosity how does using performance using runspaces compare to using a workflow?

  • Testing Get-AsyncEvent against a workflow with the following code below the speed results are very close.

    workflow foreachtest {

      param([string[]]$ComputerName)

      foreach –parallel ($Computer in $ComputerName){

       Get-WinEvent -PSComputerName $Computer -FilterHashtable @{Logname='System';Id=1074} -MaxEvents 10

      }

    }

    Measure-Command{

    $ComputerName = 1..5 | % {"server1"}

    foreachtest $ComputerName

    }

  • Great write up, very, very informative.

  • Great blog....very informative. Thanks for sharing it.

  • Hi, Could you help me how to create PowerCLI object instead of PowerShell object..? I would like write a Powercli script using runspace.

    Thank you