Link to home
Create AccountLog in
Avatar of String :-)
String :-)Flag for Australia

asked on

powershell - How to speed up script on multiple servers

Much of my work involves runnning scripts against multiple servers. This morning for example I had to reset IIS on 48 servers:
 
Whilst this script works just fine, it is quite slow because it waits for each server in the list to complete the task before moving on to the next one.

foreach ($server in (Get-Content "D:\Deployment\_ServerLists\AppServers.txt")){

Invoke-Command -cn $Server -ScriptBlock { iisreset.exe -Restart }
}


Is there anyway to tell powershell to invoke-command to each server concurrently but still writing output to transcripts  (I tried running each asjob, but then I lost the output of what it was doing).

cheers for any tips!.
String :-)
ASKER CERTIFIED SOLUTION
Avatar of footech
footech
Flag of United States of America image

Link to home
membership
Create an account to see this answer
Signing up is free. No credit card required.
Create Account
You can also try using Start-Process before each Invoke-Command.
It's been a while since I've run an iisreset command, so I don't remember what kind of output it produces, but it should just be string output (since it's not .Net-based).  Simple objects (like [string], or [int]) that are returned by Invoke-Command are not modified, but when the objects returned are complex objects then the PsComputerName property is added onto the returned object, so it makes it easy to see which remote computer the object came from.

You could do something like include a command to output the computername within the scriptblock to be executed, so you have some idea where the output is coming from.  My preferred method is to output a custom object that includes the string output (that you would normally get/want) as a property, then the PsComputerName property will be added onto that automatically.  Something like this:
$servers = Get-Content "D:\Deployment\_ServerLists\AppServers.txt"
Invoke-Command -ComputerName $servers -ScriptBlock { [pscustomobject]@{ "Output" = & iisreset.exe -Restart} }

Open in new window

Though with that you wouldn't see the full output of the iisreset command on screen - it would be modified by the Format-* commands.  It would be easy to pipe the output to a file, or a variable, or Tee-Object for later review.
Avatar of String :-)

ASKER

Hi all,
I tried the above suggestions against our dev servers and timed the duration, but honestly the scripts took approx. same time to run as my original script.

The output was more useful with the pscustomobject.
The speed though wasn't any different.

Unless there are any other responses, I might have to run 2 x script in 2 x ISE tabs concurrently.
Thanks to all who have replied so far.
Cheers
String




.
So what was the duration to run your original script, and the duration to run what I provided?

I'm incredulous that they would take the same time for you.

Even for a simple command, running ipconfig, where the result is returned in approximately 1 second, running it across 15 servers I can get the result from all in 2 seconds using fan-out remoting, while doing them sequentially as in your original takes 17 seconds.  For a script that takes longer to execute, the time difference would be proportionately longer.
Hi footech,
Sorry for the delay in replying, I was overseas for a month after xmas.

Thanks a bunch for your help so far.

I tried all the options presented above and there wasnt a dramastic improvement to the speed of doing an iisreset across 48 servers.

At best it was taking approx 20mins ( 48 servers @ 25 seconds each) and our PM was pushing for a marked improvement on this.


First I started using multiple ISE tabs, and selecting groups of servers in my list, but this got really messy, and also ment I couldnt capture every command in one transcript.

For example:

Tab1:
foreach ($server in (Get-Content "D:\Deployment\_ServerLists\servers.txt" | select -skip 0 -first 4 )) {
Invoke-Command -ComputerName $server -ScriptBlock { 
iisreset -restart
}}

Open in new window


Tab2:
foreach ($server in (Get-Content "D:\Deployment\_ServerLists\servers.txt" | select -skip 4 -first 4 )) {
Invoke-Command -ComputerName $server -ScriptBlock { 
iisreset -restart
}}

Open in new window



I then had a play with workflows, a simplified copy of this below:

function IISReset163 {
foreach ($server in (Get-Content "D:\Deployment\_ServerLists\163.txt")) {
Invoke-Command -ComputerName $server -ScriptBlock { 
iisreset -restart
}}
}

function IISReset167 {
foreach ($server in (Get-Content "D:\Deployment\_ServerLists\167.txt")) {
Invoke-Command -ComputerName $server -ScriptBlock { 
iisreset -restart
}}
}


workflow parallelOutput {
    parallel {
        IISReset163
        IISReset167
                
   }
}

Open in new window



I had about 6 functions running at once, and all seemed to run very well.   I am still learning and very open to any comments you have.
Many thanks
String
Thanks both for contributing.  Have a great week. String
SOLUTION
Link to home
membership
Create an account to see this answer
Signing up is free. No credit card required.
Create Account