273

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
  • 15,147
  • 6
  • 86
  • 135
John Mee
  • 50,179
  • 34
  • 152
  • 186

11 Answers11

459

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
  • 20,270
  • 7
  • 50
  • 76
Keith Hill
  • 194,368
  • 42
  • 353
  • 369
  • 10
    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
63

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
  • 20,270
  • 7
  • 50
  • 76
Nathan Hartley
  • 4,005
  • 2
  • 43
  • 46
  • 1
    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
15

Just use "Wait-process" :

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

job is done

Flaviofire
  • 151
  • 1
  • 2
10

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

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

Sandy Chapman
  • 11,133
  • 3
  • 58
  • 67
  • 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
  • Even -passthru doesn't capture the standardoutput or standarderror, but at least it has the exitcode. – js2010 Nov 25 '22 at 16:24
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,795
  • 7
  • 55
  • 68
Frei Zhang
  • 417
  • 6
  • 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 – Frei Zhang 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
4

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
  • 17,084
  • 9
  • 43
  • 67
  • 2
    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
4

Building upon @Justin & @Nathan Hartley 's answers:

& "my.exe" | Out-Null    #go nowhere    
& "my.exe" | Out-Default # go to default destination  (e.g. console)
& "my.exe" | Out-String  # return a string

the piping will return it in real-time

& "my.exe" | %{    
   if ($_ -match 'OK')    
   { Write-Host $_ -f Green }    
   else if ($_ -match 'FAIL|ERROR')   
   { Write-Host $_ -f Red }   
   else    
   { Write-Host $_ }    
}

Note: If the executed program returns anything other than a 0 exitcode, the piping will not work. You can force it to pipe with redirection operators such as 2>&1

& "my.exe" 2>&1 | Out-String

sources:

https://stackoverflow.com/a/7272390/254276

https://social.technet.microsoft.com/forums/windowsserver/en-US/b6691fba-0e92-4e9d-aec2-47f3d5a17419/start-process-and-redirect-output-to-powershell-window

tcables
  • 1,231
  • 5
  • 16
  • 36
3

The question was asked long ago, but since answers here are kind of references, I may mention an up to date usage. With the current implementation of PowerShell (it's 7.2 LTS as of writing) you can use && as you would do in Bash.

Conditionally execute the right-hand side pipeline based on the success of the left-hand side pipeline.

   # If Get-Process successfully finds a process called notepad,
   # Stop-Process -Name notepad is called
   Get-Process notepad && Stop-Process -Name notepad

Further info on documentation

mselmany
  • 116
  • 4
2

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,303
  • 15
  • 30
0

There's always cmd.

cmd /c start /wait notepad

Or

notepad | out-host
js2010
  • 23,033
  • 6
  • 64
  • 66
0

I guess we all don't remember ; is the simplest way to make a script wait for the previous command, the old DOS ';'. I use this ALLOT to convert long scripts to run in a remote session with enter-pssession, because Microsoft decided to make remote PowerShell session awkwardly restricted. I've written allot of scripts to run in TaskMgr and that can be quite a process to develop, so I use -command 'command to run;then another;then another', and so on.

I remember the wonderfully useful 'Call' command in DOS batch file scripting, and it looks like what 'wait-process' does, but not quite, as that specifically waits for a denoted process, not for the line above, which is what was originally asked all those years ago (what an old still-open thread!).

So here is a reminder (update) that the newer versions of PowerShell (not PS 1, good God don't use THAT!) has allot of (new) options for stopping inside, not exiting, the console, like 'exit' does, as some thought the original question was (you can simply 'break' to stop at that point...

  • Disable-PSBreakpoint Cmdlet Microsoft.PowerShell.U... Disables the breakpoints in the current console.
  • Enable-PSBreakpoint Cmdlet Microsoft.PowerShell.U... Enables the breakpoints in the current console.
  • Get-PSBreakpoint Cmdlet Microsoft.PowerShell.U... Gets the breakpoints that are set in the current session.
  • Remove-PSBreakpoint Cmdlet Microsoft.PowerShell.U... Deletes breakpoints from the current console.
  • Set-PSBreakpoint Cmdlet Microsoft.PowerShell.U... Sets a breakpoint on a line, command, or variable.

But Break isn't what was asked, right prior answers before mselmany (https://stackoverflow.com/users/15224124/mselmany)?

So the answer to the question How to to use a single script to run and then wait for the next command is to use && or ; or |out-null or even |$null

Note: If you ever need to look something up use get-help, or find-command. That's why you have to regularly update your help files -see command below

#Update-Windows_Powerhell.ps1
$modules = $null
$modules = (Get-Module -ListAvailable).name
foreach ($module in $modules) {
    Write-Output $module
    Import-Module $module -NoClobber -Verbose -ErrorAction SilentlyContinue;
    if((Get-Module -ListAvailable $module).Version -lt (Get-Module $module).Version){  
        Update-Help -Module $module -Confirm:$false  
    }  
}  

**you can change 'listAvailable' to find a speciifc module like '-ListAvailable Graph For more, that update regularly now, look at my Tech Blog: Click www.Burwell.tech!

Patrick Burwell
  • 129
  • 1
  • 12