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

Summary: Microsoft PFE, Jason Walker, talks about how to maximize performance from Windows PowerShell in the enterprise.

Microsoft Scripting Guy, Ed Wilson, is here. Jason Walker is our guest blogger this weekend. Today is Part 1 of a 2 part series. Here’s Jason…

I work in an environment with widely dispersed datacenters and I am often tasked with searching for events from multiple servers. It becomes a challenge to quickly query the event logs of 5, 10, or even 20 servers, not to mention 70 servers.

Let us look at our options. Immediately, Windows PowerShell remoting comes to mind. Windows PowerShell jobs is a possibility, and for those developer types, runspaces is an option.

Let’s look at remoting first.

Invoke-Command has a ComputerName parameter, which accepts an array, and a ThrottleLimit parameter. It’s as if Invoke-Command was made for this type of stuff! Let’s see how it works.

I’m going to query the System log for event 1074 on five servers. And for instructional purposes, I will specify a MaxEvents of 1:

$Servers = “mail01″,”mail02″,”mbx01″,”mbx02″,”mbx03”

$Events = Invoke-Command -ComputerName $Servers -ScriptBlock {$sb=@{Logname=’System’;Id=1074};Get-WinEvent -FilterHashtable $sb -MaxEvents 1}


Image of command output

These commands could easily be combined into a one-liner. Using Invoke-Command is easy and straightforward. When you want to run a cmdlet across multiple machines and the cmdlet’s ComputerName parameter doesn’t accept an array, Invoke-Command easily adds that functionality. We see that Invoke-Command runs and returns results almost instantly.

Image of command output

Using Measure-Command shows that the series of commands took 742 milliseconds. But let’s take a closer look at the objects that are returned. If I take the first object that is returned, it looks like I ran Get-WinEvent without leveraging remoting:

Image of command output

However, notice the value of the Properties property. It has depth (nested objects). Let’s expand that and see what we get.

Image of command output

Only the names of the nested objects are returned as strings. Let’s run Get-WinEvent by itself to compare.

$Event = Get-WinEvent -cn mail01 -FilterHashtable @{Logname=’System’;id=1074} -MaxEvents 1

$Event | select *

Image of command output

It looks the same so far. Now let’s look at the nested objects in Properties:

Image of command output

Now we have values. Awesome!

When using Get-WinEvent, I always want the values within Properties, but if I’m using a different cmdlet or function, this might not be an issue. For example, let’s say I want to know who rebooted my server mail01. All I know is that the server has been rebooted.

I could use Get-WinEvent and parse through the blob of text that is the Message property, or I could look at the seventh value contained in the Properties property. In the previous example, vmicsvc.exe has initiated a shutdown on behalf of NT Authority\System. So it’s safe to say that a user shut down the virtual machine with the Hyper-V management console or by using the Stop-VM cmdlet. To drive my point home, I can easily get the data I want from the Properties property versus parsing through a paragraph of text, for example:

$Event = Get-WinEvent -cn mail01 -FilterHashtable @{Logname=’System’;id=1074} -MaxEvents 1

$Event | Format-Table MachineName,@{name=”ShutdownByUser”;Expression={$_.Properties[6].value}}

Image of command output

In the previous example, with only two lines of code (which could easily be one line of code), I can report the user name who initiated the shutdown. Super, super easy.

Let’s look at using jobs. Jobs are cool because when a cmdlet is run inside a job, the work is being done asynchronously in its own thread. So if you run a cmdlet as a job, the prompt will be instantly returned, and you can continue to Windows PowerShell as the job runs in the background. We could talk about jobs forever (for more information, see Using Windows PowerShell Jobs)—but let’s see them in action.

$Servers = “mail01″,”mail02″,”mbx01″,”mbx02″,”mbx03”

Foreach($Server in $Servers){

 Start-Job -ScriptBlock {Get-WinEvent -ComputerName $using:Server -FilterHashtable @{LogName=’System’;Id=1074} -MaxEvents 1}


$EventsUsingJobs = Get-Job | Wait-Job | Receive-Job


Image of command output

When using jobs there are a few more steps involved. Start-Job doesn’t have a ComputerName parameter, so I have to iterate through an array of server names and supply Get-WinEvent with each of the unique computer names. You can see the jobs being created, and when the jobs are finished, I have to use Receive-Job to see the results. I will use Measure-Command to see how long everything took to run.

Image of command output

By using jobs, it took four seconds to query the five servers. That is considerably longer than using Invoke-Command, but less than a server a second isn’t bad. I mean, I couldn’t manually run the command five times in four seconds.

Image of command output

When we look at the objects returned, again we see that we lose the nested objects. As a work around, I could pipe the results of Get-WinEvent to Export-CliXml inside the script block, but the results have to be written to disk and then imported by using Import-CliXml, which adds more time to task.

Let’s use runspaces. Here I wrote a function that I named Get-AsyncEvent that runs Get-WinEvent in asynchronous runspaces. Here is what happened when ran it with the exact filters as the previous examples:

Image of command output

We find that runspaces are significantly faster than using Invoke-Command, taking less than half the time to run the command. But do we get nested objects back? Let’s run the command again and take a look:

Image of command output

Jackpot! Not only are runspaces the fastest solution, but they return objects in their entirety.

Let’s recap…

  • Invoke-Command is easy and the less complicated to run, but it doesn’t return nested objects.
  • Start-Job takes a little more coding, but is the slowest and it returns no nested objects unless we use Export-CliXml.
  • Runspaces are crazy fast and returned the nested objects.

But what does it take to leverage runspaces? I can say that runspaces are by far the most complicated multithreading solution. But if you tune in tomorrow, I will explain the basics of using runspaces.


Thank you, Jason. Be sure to join us tomorrow as Jason brings you more Windows PowerShell goodness.

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 

Comments (2)

  1. Anonymous says:

    Awesome post Jason, very interesting.

  2. Well written and easy to follow. Please keep tutorials like this coming.

Skip to main content