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