Friday, January 16, 2015

Fun with the PowerShell Pipeline

I've been having some fun with the PowerShell Pipeline. This started with a post to a PowerShell question over on Spiceworks (http://community.spiceworks.com/topic/737915-powershell-question-help). The OP wanted to stop the spooler, remove any temp files then restart. Simple enough – but the first response suggested using pipes like this:

Stop-Service -Name Spooler |
Remove-Item C:\Windows\System32\spool\PRINTERS\* |
Start-Service -Name Spooler

I looked at this for a long while wondering what the heck the pipeline would be doing here. More importantly, I could not believe it would work – surely the output of Stop-Service would be null thus the remove-item would fail, etc. Then it tried it and much to my surprise – it actually works!

Then I began to wonder two things – WHY does it work at all – and what is the performance impact. To understand why requires a good understanding of how the pipeline works. In this case, Jason Shirk has a great explanation of the underpinnings over on Stack Overflow: http://stackoverflow.com/questions/22343187/why-is-an-empty-powershell-pipeline-not-the-same-as-null. I highly recommend reading it to help you better understand what the pipeline is doing.

Ok – so I know WHY it works, but what is the performance impact if any? To answer this question, I first constructed a little script.

Function Measureit {$m1 = (Measure-Command -Expression {
      Stop-Service -Name Spooler |
      Remove-Item C:\Windows\System32\spool\PRINTERS\* |
      Start-Service -Name Spooler
  }).milliseconds

  $m2 = (Measure-Command -Expression {
      Stop-Service -Name Spooler
      Remove-Item C:\Windows\System32\spool\PRINTERS\*
      Start-Service -Name Spooler
  }).Milliseconds

  "{0,-10}{1}" -f $m1, $m2
}

1..20 | %{measureit}

Now please read the script and guess which is going to be faster – with or without pipelines? The results absolutely astounded me:

15        307
2         273
2         270
254       280
2         277
10        267
2         271
2         269
2         276
2         268
2         268
2         277
2         272
2         268
2         268
2         268
10        267
2         269
2         267
2         271

I know why this works, but I sure as heck have NO idea how adding the pipeline, in this care can offer up 100-fold improvement in the speed of execution in most cases. Hmmm.

2 comments:

Arcuss88 said...

While I am only starting to learn Powershell I believe I have an answer to why using Pipelinging for these tasks is more efficient than running the tasks as a script of 3 lines. I just finished reading the Powershell.com eBook and pipelines can be processed either sequentially or simultaneously. From what your test results it would appear that the three tasks that are being sent through the pipe are occurring simultaneously which results in a much more faster completion time. While the same tasks run sequentially in a script are run, well just that, one after the other.

http://powershell.com/cs/blogs/ebookv2/archive/2012/03/05/chapter-5-the-powershell-pipeline.aspx#streaming-real-time-processing-or-blocking-mode

aynyuh said...

Just because Start-service in pipeline doesn't run. Check by yourself:

Function Measureit {$m1 = (Measure-Command -Expression {
Stop-Service -Name Spooler |
Start-Service -Name Spooler

write-host (Get-Service -Name "Spooler").Status -ForegroundColor Red
}).milliseconds

$m2 = (Measure-Command -Expression {
Stop-Service -Name Spooler
Start-Service -Name Spooler

write-host (Get-Service -Name "Spooler").Status -ForegroundColor Yellow
}).Milliseconds

"{0,-10}{1}" -f $m1, $m2
}

1..20 | %{measureit}