249

I have a PowerShell 1.0 script to just open a bunch of applications. The first is a virtual machine and the others are development applications. I want the virtual machine to finish booting before the rest of the applications are opened.

In bash I could just say "cmd1 && cmd2"

This is what I've got...

C:\Applications\VirtualBox\vboxmanage startvm superdooper
    &"C:\Applications\NetBeans 6.5\bin\netbeans.exe"
CJBS
  • 14,479
  • 5
  • 82
  • 129
John Mee
  • 47,286
  • 32
  • 143
  • 181

8 Answers8

429

Normally, for internal commands PowerShell does wait before starting the next command. One exception to this rule is external Windows subsystem based EXE. The first trick is to pipeline to Out-Null like so:

Notepad.exe | Out-Null

PowerShell will wait until the Notepad.exe process has been exited before continuing. That is nifty but kind of subtle to pick up from reading the code. You can also use Start-Process with the -Wait parameter:

Start-Process <path to exe> -NoNewWindow -Wait

If you are using the PowerShell Community Extensions version it is:

$proc = Start-Process <path to exe> -NoNewWindow -PassThru
$proc.WaitForExit()

Another option in PowerShell 2.0 is to use a background job:

$job = Start-Job { invoke command here }
Wait-Job $job
Receive-Job $job
Cristian Ciupitu
  • 19,240
  • 7
  • 48
  • 73
Keith Hill
  • 184,219
  • 38
  • 329
  • 358
  • 3
    My bad. Start-Process -Wait works great, but now I see it this is not what I was looking for... I'm actually seeking to wait until the vm to finishes booting. I imagine that's going to be tough. I suppose I'll have to find a new thread for that. Thanks but. – John Mee Nov 20 '09 at 00:37
  • 9
    Brilliant, `| out-null` did just what I needed. Tried using `Start-Job` but because I'm passing the results of functions as parameters, it got a little skitzoid on me, so I couldn't use the last suggestion... – jcolebrand Sep 11 '12 at 16:19
  • 8
    As a side note, if you need to pass multiple arguments with `-ArgumentList`, separate them with commas like `-ArgumentList /D=test,/S`. – sschuberth Sep 04 '15 at 13:05
  • 1
    Thank you for the simple " | Out-Null" solution! The problem with "the Start-Process -NoNewWindow -Wait" method is that the PowerShell pauses until all child processes spawned by the parent are complete, even if the parent terminates before them. This caused our setup program issues. – zax Apr 03 '18 at 21:22
  • This is what I use to wait for a VM to start Start-AzureRmVM -ResourceGroupName $ResourceGroupName -Name $VmName while((Get-AzureRmVM -ResourceGroupName $ResourceGroupName -Name $VmName -Status | ` select -ExpandProperty Statuses | ` ?{ $_.Code -match "PowerState" } | ` select -ExpandProperty DisplayStatus) -ne "VM running") { Start-Sleep -s 2 } Start-Sleep -s 5 ## Give the VM time to come up so it can accept remote requests – andrewmo Jul 16 '18 at 12:08
  • 1
    Sorry for zombie comment but in case anyone is trying to duplicate this for VMware, grab PowerCLI and use Wait-Tools or a do-while with Invoke-VMScript until you get the desired response. – duct_tape_coder Dec 03 '18 at 21:34
  • Is it possible to do this for a >1 commands (i.e. wait until 2 commands finish before going on to the next?) - also supposing those 2 commands take an unknown amount of time and happen simultaneously. I ask because I have [this](https://stackoverflow.com/questions/56612034/wait-for-multiple-simultaneous-powershell-commands-in-other-sessions-to-finish-b) question – stevec Jun 15 '19 at 16:36
  • Depends what you're running, this option is super-useful `$proc.WaitForExit()` I had problems with long-executing process until I used this option. – T.S. Jan 07 '20 at 00:02
56

Besides using Start-Process -Wait, piping the output of an executable will make Powershell wait. Depending on the need, I will typically pipe to Out-Null, Out-Default, Out-String or Out-String -Stream. Here is a long list of some other output options.

# Saving output as a string to a variable.
$output = ping.exe example.com | Out-String

# Filtering the output.
ping stackoverflow.com | where { $_ -match '^reply' }

# Using Start-Process affords the most control.
Start-Process -Wait SomeExecutable.com

I do miss the CMD/Bash style operators that you referenced (&, &&, ||). It seems we have to be more verbose with Powershell.

Cristian Ciupitu
  • 19,240
  • 7
  • 48
  • 73
Nathan Hartley
  • 3,713
  • 1
  • 40
  • 46
  • This is an incredibly good solution if you need to parse the output! – Jan Rothkegel Jan 17 '18 at 06:27
  • 1
    Pointing out **Out-xxxx** redirectors list quickly allowed me understand why I should use **Out-Default** instead of **Out-Null**, since I needed to keep console output. – Julio Nobre May 14 '19 at 15:27
  • Note that _no_ extra work is needed to execute _console_ applications synchronously - as in any shell, that is the default behavior. Piping to `Out-String` _changes the output_ to a _single, multi-line_ string, whereas PowerShell by default returns an _array of lines_. `Start-Process` should be avoided for console applications (unless you truly want to run them in a _new window_) because you won't be able to capture or redirect their output. – mklement0 Nov 11 '19 at 19:21
  • Actually, whether a native command runs synchronously (with output) or asynchronously depends on the executable. As an example, without capturing the output, the following only outputs "bingo". & 'C:\Program Files\Mozilla Firefox\firefox.exe' -? ; 'bingo' – Nathan Hartley Nov 12 '19 at 14:09
15

Just use "Wait-process" :

"notepad","calc","wmplayer" | ForEach-Object {Start-Process $_} | Wait-Process ;dir

job is done

Flaviofire
  • 151
  • 1
  • 2
8

If you use Start-Process <path to exe> -NoNewWindow -Wait

You can also use the -PassThru option to echo output.

Sandy Chapman
  • 10,805
  • 3
  • 56
  • 64
  • 2
    Note that `-PassThru` doesn't echo _output_ (a non-console-application by definition won't produce console output), it outputs a `System.Diagnostics.Process` instance that represents the newly launched process, so you can examine its properties and wait for it to exit later. – mklement0 Nov 11 '19 at 19:16
8

Some programs can't process output stream very well, using pipe to Out-Null may not block it.
And Start-Process needs the -ArgumentList switch to pass arguments, not so convenient.
There is also another approach.

$exitCode = [Diagnostics.Process]::Start(<process>,<arguments>).WaitForExit(<timeout>)
Igor Popov
  • 9,381
  • 7
  • 53
  • 65
ifree
  • 387
  • 5
  • 12
  • 2
    how do multiple arguments work in that call? how are they delimited? how does one handle various nested string escaping? ty! – AnneTheAgile Jan 13 '15 at 23:00
  • 1
    @AnneTheAgile [doc](http://msdn.microsoft.com/en-us/library/h6ak8zt5(v=vs.110).aspx) use space to separate the arguments, for char escaping use backslash – ifree Jan 14 '15 at 05:35
  • 1
    ty @ifree, I did get testcomplete to run that way! I used single quotes around the list of space delimited arguments.[1]; but now echo $exitcode=false, which wasn't my returncode from the process? [1] $exitCode = [Diagnostics.Process]::Start( "c:\Program Files (x86)\SmartBear\TestComplete 10\Bin\TestComplete.exe" ,'"c:\Users\ME\Documents\TestComplete 10 Projects\hig4TestProject1\hig4TestProject1.pjs" /run /project:myProj/test:"KeywordTests|zTdd1_Good" /exit' ).WaitForExit(60) – AnneTheAgile Jan 21 '15 at 14:15
  • Start-Process -Wait took a long time (like a minute) to return to script execution after closing the application. This took a couple of seconds. – ste-fu Nov 24 '21 at 17:21
3

Including the option -NoNewWindow gives me an error: Start-Process : This command cannot be executed due to the error: Access is denied.

The only way I could get it to work was to call:

Start-Process <path to exe> -Wait
twasbrillig
  • 14,704
  • 9
  • 39
  • 61
  • 1
    Usually means that it needs to run as admin. Admin privilege escalation *needs* to open a new window. It's not possible to connect an Admin command to a non admin console. – silicontrip Sep 08 '21 at 02:36
1

Taking it further you could even parse on the fly

e.g.

& "my.exe" | %{
    if ($_ -match 'OK')
    { Write-Host $_ -f Green }
    else if ($_ -match 'FAIL|ERROR')
    { Write-Host $_ -f Red }
    else 
    { Write-Host $_ }
}
Justin
  • 1,059
  • 12
  • 26
0

There's always cmd.

cmd /c start /wait notepad

Or

notepad | out-host
js2010
  • 17,785
  • 4
  • 45
  • 50