skip to Main Content

Powershell script should exit if one of the main Processes stopped.

This script is the main Docker process. Docker container should stop if one of those Apps (app1, app2)stopped.

Current approach is to use Exit Events for one of the Apps and Wait-Process for the other. Is there a better approach?

$pApp1 = Start-Process -PassThru app1
$pApp2 = Start-Process -PassThru app2

Register-ObjectEvent -InputObject $pApp1 -EventName exited -Action {
    Get-EventSubscriber | Unregister-Event
    exit 1
}

Wait-Process -Id $pApp2.id
exit 1

2

Answers


  1. Wait for the HasExited property on either of them to change:

    $apps = 'app1','app2' |ForEach-Object { Start-Process $_ -PassThru }
    
    while(@($apps |Where HasExited -eq $true).Count -lt 1){
      Write-Host "Waiting for one of them to exit..."
      Start-Sleep -Seconds 1
    }
    
    Login or Signup to reply.
  2. As of PowerShell 7.2.1, Wait-Process, when given multiple processes, invariably waits for all of them to terminate before returning; potentially introducing an -Any switch so as to only wait for any one among them is the subject of GitHub proposal #16972, which would simplify the solution to Wait-Process -Any -Id $pApp1.id, $pApp2.id


    Delegating waiting for the processes to exit to thread / background jobs avoids the need for an event-based or periodic-polling solution.

    # Start all processes asynchronously and get process-information 
    # objects for them.
    $allPs = 'app1', 'app2' | ForEach-Object { Start-Process -PassThru $_ }
    
    # Start a thread job for each process that waits for that process to exit
    # and then pass the process-info object for the terminated process through.
    # Exit the overall pipeline once the first output object from one of the
    # jobs is received.
    $terminatedPs = $allPs | 
      ForEach-Object { Start-ThreadJob { $ps = $using:_; Wait-Process -Id $ps.Id; $ps } } |
        Receive-Job -Wait |
          Select-Object -First 1
    
    Write-Verbose -Verbose "Process with ID $($terminatedPs.Id) exited."
    exit 1
    

    Note:

    • I’m using he Start-ThreadJob cmdlet, which offers a lightweight, much faster thread-based alternative to the child-process-based regular background jobs created with Start-Job.
      It comes with PowerShell (Core) 7+ and in Windows PowerShell can be installed on demand with, e.g., Install-Module ThreadJob -Scope CurrentUser.
      In most cases, thread jobs are the better choice, both for performance and type fidelity – see the bottom section of this answer for why.

    • If Start-ThreadJob isn’t available to you / cannot be installed, simply substitute Start-Job in the code above.


    PowerShell (Core) 7+-only solution with ForEeach-Object -Parallel:

    PowerShell 7.0 introduced the -Parallel parameter to the ForEach-Object cmdlet, which in essence brings thread-based parallelism to the pipeline; it is a way to create multiple, implicit thread jobs, one for each pipeline input object, that emit their output directly to the pipeline (albeit in no guaranteed order).

    Therefore, the following simplified solution is possible:

    # Start all processes asynchronously and get process-information 
    # objects for them.
    $allPs = 'app1', 'app2' | ForEach-Object { Start-Process -PassThru $_ }
    
    $terminatedPs = $allPs | 
      ForEach-Object -Parallel { $_ | Wait-Process; $_  } |
        Select-Object -First 1
    
    Write-Verbose -Verbose "Process with ID $($terminatedPs.Id) exited."
    exit 1
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search