[Last Call] Learn how to a build a cloud-first strategyRegister Now

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 368
  • Last Modified:

How to Modify this PowerShell Script to Run Concurrently Across Many Hosts

I have written a little powershell script to enumerate an OU and pass a command line argument typed in the console to each machine.  It runs fine but seems to run one at a time.  The OU has hundreds of systems.  How can I restructure this to run concurrently on all systems or at least run in batches of 20 systems at a time?  Are workflows with PS 3 the answer?  I am new to PS and trying to learn it.

#Enumerates OU
$Computers = Get-ADComputer -Filter * -Searchbase 'OU=Servers,OU=PROD,DC=JUGGERNAUT,DC=LOCAL'
#Writes Examples to Console
Write-Host 
Write-Host –foregroundcolor green 'Type in command to run on RDS systems in PSP_RDS OU'
Write-Host
Write-Host –foregroundcolor green 'Reboot Example: SHUTDOWN /r /t:60 /c "System Is Rebooting in 60 Seconds" /d P:4:2'
Write-Host –foregroundcolor green 'Registry Import Example:  reg import \\share\update.reg'
Write-Host
#Asks for command to run on remote systems
$commandtorun = Read-Host 'Enter Client Command to Run on RDS Systems'
Write-Host
#Runs command
foreach ($COMPUTER in $Computers) 
{
write-host –foregroundcolor green "Connecting to $($COMPUTER.Name)and running ($commandtorun)"  
Invoke-Command $COMPUTER.Name {$commandtorun}
}

Open in new window

0
Mark Roddy
Asked:
Mark Roddy
  • 7
  • 7
  • 3
2 Solutions
 
SubsunCommented:
Ask the questions inside foreach  loop..
#Enumerates OU
$Computers = Get-ADComputer -Filter * -Searchbase 'OU=Servers,OU=PROD,DC=JUGGERNAUT,DC=LOCAL'
#Writes Examples to Console
Write-Host 
Write-Host –foregroundcolor green 'Type in command to run on RDS systems in PSP_RDS OU'
Write-Host
Write-Host –foregroundcolor green 'Reboot Example: SHUTDOWN /r /t:60 /c "System Is Rebooting in 60 Seconds" /d P:4:2'
Write-Host –foregroundcolor green 'Registry Import Example:  reg import \\share\update.reg'
Write-Host
foreach ($COMPUTER in $Computers) 
{
#Asks for command to run on remote systems
$commandtorun = Read-Host 'Enter Client Command to Run on RDS Systems'
Write-Host
#Runs command
write-host –foregroundcolor green "Connecting to $($COMPUTER.Name)and running ($commandtorun)"  
Invoke-Command $COMPUTER.Name {$commandtorun}
}

Open in new window

0
 
Mark RoddyAuthor Commented:
Wouldn't that ask each time for each system?  So if I had 100 computers in that OU it would prompt 100 times?
0
 
SubsunCommented:
Yes it will, you can test it and get back to us.. :-)
0
Efficient way to get backups off site to Azure

This user guide provides instructions on how to deploy and configure both a StoneFly Scale Out NAS Enterprise Cloud Drive virtual machine and Veeam Cloud Connect in the Microsoft Azure Cloud.

 
Mark RoddyAuthor Commented:
oh..ok..not what I was wanting to accomplish.  Was looking for a way to pass the command entered into the console concurrently\synchronously  to the machines that are pulled from that OU.  Not have to type in the command for every machine in the OU.

So example would be someone updated a sites list in a registry key to define locations for an app.  We need to push that out to all systems immediately.  We have to import that to 500 hosts.  Instead of it happening one at a time I would like it to push the reg import command to all the systems in the OU at the same time.

Something like this example...but I don't know how to restructure my script to do it.

http://newsqlblog.com/2012/04/16/concurrency-in-powershell-background-jobs-2/
0
 
SubsunCommented:
Okie.. That you cannot do with foreach, because foreach process objects one by one. There is no parallel processing.. You can try Foreach-Parallel function and see if it works for you..

Ref : http://gallery.technet.microsoft.com/scriptcenter/Foreach-Parallel-Parallel-a8f3d22b
0
 
footechCommented:
I think you would not want to use the foreach loop.  Just run something like:
Invoke-Command $COMPUTERS {$commandtorun}

Open in new window

Though I'm not certain how you would incorporate the Write-Host statement.  To me it doesn't seem like it would apply in a concurrent situation, probably better to incorporate some logging.

For concurrent sessions, if you want, you could use the -ThrottleLimit parameter, default is 32.
0
 
Mark RoddyAuthor Commented:
So I would load this function up in my session and then run something like this?

$Computers = Get-ADComputer -Filter * -Searchbase 'OU=Servers,OU=PROD,DC=JUGGERNAUT,DC=LOCAL'

$computers | Foreach-Parallel - Throttle 20 - Timeout 10 - verbose -scriptfile c:\remotecommand.ps1

Open in new window

Which would call remotecommand.ps1 to ask for my screen input...but wouldn't is ask me for input every time this runs in parallel?  How do I pass the command to every parallel session and not have to keep entering it in?

remotecommand.ps1:
Write-Host –foregroundcolor green 'Type in command to run on RDS systems in PSP_RDS OU'
Write-Host
Write-Host –foregroundcolor green 'Reboot Example: SHUTDOWN /r /t:60 /c "System Is Rebooting in 60 Seconds" /d P:4:2'
Write-Host –foregroundcolor green 'Registry Import Example:  reg import \\share\update.reg'
Write-Host
#Asks for command to run on remote systems
$commandtorun = Read-Host 'Enter Client Command to Run on RDS Systems'
Invoke-Command $COMPUTER.Name {$commandtorun}

Open in new window

0
 
Mark RoddyAuthor Commented:
Oh..so like this?  After loading up the function Foreach-Parallel

PSRemoteRun.ps1:
$Computers = Get-ADComputer -Filter * -Searchbase 'OU=Servers,OU=PROD,DC=JUGGERNAUT,DC=LOCAL'
Write-Host –foregroundcolor green 'Type in command to run on RDS systems in PSP_RDS OU'
Write-Host
Write-Host –foregroundcolor green 'Reboot Example: SHUTDOWN /r /t:60 /c "System Is Rebooting in 60 Seconds" /d P:4:2'
Write-Host –foregroundcolor green 'Registry Import Example:  reg import \\share\update.reg'
Write-Host
#Asks for command to run on remote systems
$commandtorun = Read-Host 'Enter Client Command to Run on RDS Systems'
$computers | Foreach-Parallel - Throttle 20 - Timeout 10 - verbose -scriptfile c:\remotecommand.ps1

Open in new window



and then remotecommand.ps1:
Invoke-Command {$commandtorun}

Open in new window

0
 
SubsunCommented:
You don't have to use both.. Either you can use the ScriptBlock parameter instead of Scriptfile. Or as footech suggested use Invoke-Command against $Computers.
0
 
footechCommented:
You might want to take a look at the Hey! Scripting Guy articles by Jason Hofferle.
http://blogs.technet.com/b/heyscriptingguy/archive/tags/jason+hofferle/
In particular part 1 discusses using ForEach vs. Invoke-Command.  When using Invoke-Command inside a foreach loop, pretty sure what will happen is that the command has to complete and/or the object has to be returned before moving on to the next loop of the foreach, so it really is running one at a time.
0
 
SubsunCommented:
Try this and see if it works for you..
#Enumerates OU
$Computers = Get-ADComputer -Filter * -Searchbase 'OU=Servers,OU=PROD,DC=JUGGERNAUT,DC=LOCAL'
#Writes Examples to Console
Write-Host 
Write-Host –foregroundcolor green 'Type in command to run on RDS systems in PSP_RDS OU'
Write-Host
Write-Host –foregroundcolor green 'Reboot Example: SHUTDOWN /r /t:60 /c "System Is Rebooting in 60 Seconds" /d P:4:2'
Write-Host –foregroundcolor green 'Registry Import Example:  reg import \\share\update.reg'
Write-Host
#Asks for command to run on remote systems
$commandtorun = Read-Host 'Enter Client Command to Run on RDS Systems'
Write-Host
#Runs command
write-host –foregroundcolor green "Connecting to $($COMPUTER.Name)and running ($commandtorun)"  
Invoke-Command $Computers -ScriptBlock {Invoke-Expression $args[0]} -Args $commandtorun

Open in new window

0
 
Mark RoddyAuthor Commented:
thanks...will try in a few when I get home and settled. Hey there again footech!
0
 
footechCommented:
Hi mroddy77.  :)
0
 
Mark RoddyAuthor Commented:
Thanks Subsun

It works after a little tweaking...had to change the $Computers variable to pipe to a Select-Object  - expand Name

So one last nice to have if possible.  Can I have it write to the application log on the machine this script runs from what was ran and on what remote systems?  I am googling but not seeing anything cut and dry when using the invoke command?

This is what I have so far..just creating a event log source.  

#Enumerates OU
$Computers = Get-ADComputer -Filter * -Searchbase 'OU=Servers,OU=PROD,DC=JUGGERNAUT,DC=LOCAL'|Select-Object -expand Name
#create event source if it doesn't exist
if ([System.Diagnostics.EventLog]::SourceExists("RemoteCommandScript") -eq $false) {
    [System.Diagnostics.EventLog]::CreateEventSource("RemoteCommandScript", "Application")
	}
#Writes Examples to Console
Write-Host 
Write-Host –foregroundcolor green 'Type in command to run on RDS systems in PSP_RDS OU'
Write-Host
Write-Host –foregroundcolor green 'Reboot Example: SHUTDOWN /r /t:60 /c "System Is Rebooting in 60 Seconds" /d P:4:2'
Write-Host –foregroundcolor green 'Registry Import Example:  reg import \\share\update.reg'
Write-Host
#Asks for command to run on remote systems
$commandtorun = Read-Host 'Enter Client Command to Run on RDS Systems'
Write-Host
#Runs command
Invoke-Command $Computers -ScriptBlock {Invoke-Expression $args[0]} -Args $commandtorun

Open in new window

0
 
SubsunCommented:
You can make use of Write-Event function which I have written for another question.. See the example code..


$eventLogID = 20399
$eventLogPrefix = "FileCopyStat"

function Write-Event{
		$msg = $args[0]
		$source = $args[1]
		$type = $args[2]
		$eventid = $args[3]
		if(![System.Diagnostics.EventLog]::SourceExists($source))
			{
				[System.Diagnostics.EventLog]::CreateEventSource($source,'Application')
			}
		$log = New-Object System.Diagnostics.EventLog 
		$log.set_log("Application") 
		$log.set_source($source)
		$log.WriteEntry($msg,$type,$eventid)
}

#Enumerates OU
$Computers = Get-ADComputer -Filter * -Searchbase 'OU=Servers,OU=PROD,DC=JUGGERNAUT,DC=LOCAL'|Select-Object -expand Name

#Writes Examples to Console
Write-Host 
Write-Host –foregroundcolor green 'Type in command to run on RDS systems in PSP_RDS OU'
Write-Host
Write-Host –foregroundcolor green 'Reboot Example: SHUTDOWN /r /t:60 /c "System Is Rebooting in 60 Seconds" /d P:4:2'
Write-Host –foregroundcolor green 'Registry Import Example:  reg import \\share\update.reg'
Write-Host

#Asks for command to run on remote systems
$commandtorun = Read-Host 'Enter Client Command to Run on RDS Systems'

#create event
Write-Event $("Comand "+$commandtorun" run on "+$Computers) $eventLogPrefix "Information" $eventLogID
Write-Host

#Runs command
Invoke-Command $Computers -ScriptBlock {Invoke-Expression $args[0]} -Args $commandtorun

Open in new window

0
 
Mark RoddyAuthor Commented:
Thank you Subsun.  Works great.
0
 
SubsunCommented:
You are welcome!!
0

Featured Post

Concerto Cloud for Software Providers & ISVs

Can Concerto Cloud Services help you focus on evolving your application offerings, while delivering the best cloud experience to your customers? From DevOps to revenue models and customer support, the answer is yes!

Learn how Concerto can help you.

  • 7
  • 7
  • 3
Tackle projects and never again get stuck behind a technical roadblock.
Join Now