Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / PowerShell

Running PSexec in Multi Thread using Powershell

4.13/5 (5 votes)
18 Nov 2019CPOL2 min read 14.3K  
This tip explains how to execute single command in multiple parallel servers. We can use other tools to achieve the same.

Introduction

We want to execute the same command on multiple servers. We had various options to do so but every one of them required to tweak something or the other on the agents to run command.

Background

We have multiple servers in a given environment and sometimes, we need to run a command in parallel at the same time on all servers. There are a lot of tools available for doing this. Like Powershell DSC, Jenkins, Nolio, Octopus, UCDeploy and lot more. But they all had one thing in common, we need to have some sort of installation on the agent machine like a service or small runtime. And we cannot afford to install anything on these servers since they are prod servers and we would need extensive testing to do something like that.

Using the Code

We came up with the plan to use PSexec from Sysinternals. It's good and fast and does not need anything to be installed on the running agents. We can just fire and forget. The logs we could trap for further details on what happened to the process we just launched.

Psexec Command

C:\sysIntern\psexec  -u<username> -p <password> \\<machineName/IP> /accepteula
-w <working dir on Client Machine>  -h <command to run>. >{8}\{1}.log 2>$null' `

Powershell Integration

Multithreading in Powershell is more of a tweak. It does not work straight out of the box. You don't have delegation or threading libraries.

To do parallel runs in Powershell, you need to call Jobs. Jobs can run in asynchronous mode. You can use fire and forget or you can track them down. But ideally, whatever Jobs script starts should always clean it up. We should not leave the Jobs behind in the process. The Jobs can also be queried from other Powershell shells.

Code for creating and sending Jobs. Below is an example of parameterized Job which we are launching. Here, the Job will accept one parameter Command to execute. Below, you see -Name that is a parameter which is giving Name to the job which we will query later and close based on checking if there is an error or not.

PowerShell
$job = Start-Job -ScriptBlock{
            param([string] $command) 
            $output = iex $command
        }-ArgumentList $command  -Name $IP
$jobs +=$job

To check if Job has completed, here is the code for the same:

PowerShell
foreach ($job in $jobs)  {
        Wait-Job $job 
        $results = receive-job -job $job
        Write-Host ("Job Data returned for [{0}][{1}]"  -f $job.Name , $results)
        remove-job $job
    }

Now that we know how everything is working, let's put everything together.

PowerShell
 $IPS=@("192.168.1.1","192.168.1.2","192.168.1.3","192.168.1.4")
 $jobs = @();
 $executable = "iisrest /stop"
 foreach($IP in $IPS){
        $command = '{0} -u {1} -p {2} \\{3} /accepteula -w {4}  
                        -h  cmd /c {5} >{6}\{1}.log 2>$null' `
         -f ("C:\SysIntern\Psexec.exe",$username,$password,$IP,
             "C:\Windows",$executable,"\\LogServer\Logs","iisresetlog")
        Write-Host "Command to Execute [$command]"
        
        $job = Start-Job -ScriptBlock{
            param([string] $command) 
            $output = iex $command
        }-ArgumentList $command  -Name $IP
        $jobs +=$job
}

$totalJobs = $jobs.count
$jobsCompleted = 0
$printCounter = 0

while($jobsCompleted -lt $totalJobs){
    foreach ($job in $jobs)  {
        $IP=$job.Name
        if(($job.State  -ne "Completed") -or ($job.State -ne "Failed")){
            if (0 -eq $printCounter % 300){
                $totalMins=$printCounter/60
                $statusPrint = "Command is still going on {0} for last {1} mins. 
                                Current jobState is {2}." -f ($IP,$totalMins ,$job.State)
                write-host $statusPrint
            }
            continue
        }
        $jobsCompleted = $jobsCompleted + 1    
    }
    $printCounter = $printCounter + 1
    Start-Sleep 1
}

foreach ($job in $jobs)  {
    Wait-Job $job 
    $results = receive-job -job $job
    Write-Host ("Job Data returned for [{0}][{1}]"  -f $job.Name , $results)
    remove-job $job
}

Points of Interest

We could have used C# code in Powershell or System.Threading but they are not native calls of Powershell, rather would be coming from .NET API which will defeat the purpose of the script.

History

  • 7th November, 2019: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)