Consider the powershell command:
cmd.exe "/c start notepad.exe"
Using powershell.exe (console) this command completes immediately after starting the cmd/notepad process and does not wait for notepad/cmd to exit. If I want it to wait for completion I have to pipe the output to Out-Null.
Using the powershell ISE this command blocks execution until notepad/cmd is closed.
Further, if I use create a new process (of powershell.exe) in a script running in powershell.exe using System.Diagnostics.Process and redirect standard output the script now blocks execution until notepad/cmd is closed. If I don't redirect output it does not block execution.
But if I use c# to create a new process with the same settings/start info, and run the same script with redirected output it doesn't block execution.
I'm at a loss here. Obviously it has something to do with the setup of the execution and output and maybe "cmd.exe". I'm hoping someone understands what's fundamentally happening behind the scenes differently in these cases. I know the ISE and cmd.exe isn't fully supported but the I don't know why the other 3 aren't consistent.
Why do the scripts not run with the same behavior (especially the powershell console ones) and how do I get them to?
Edit:
After some troubleshooting I was able to get all the powershell.exe versions to run the same (the ISE isn't of importance to me). The odd ball where cmd.exe "/c start notepad.exe" blocks execution is when a new process is created in powershell to run powershell.exe using System.Diagnostics.Process. If output is redirected (the reason I was using System.Diagnostics.Process in the first place, Start-Process doesn't support redirecting except to a file) then calling WaitForExit() on the process object causes the blocking to occur.
Simply substituting WaitForExit() with Wait-Process (and the process ID) causes the powershell script running in the new powershell.exe to execute as expected and exit after running the command. Hopefully this information combined with @mklement0 answer will be sufficient for anyone else having similar problems.
tl;dr
cmd.exe "/c start notepad.exe" (conceptually clearer: cmd.exe /c 'start notepad.exe') should result in asynchronous execution, given that cmd.exe's internal start command launches applications asynchronously by default.
By contrast, cmd.exe "/c notepad.exe" (conceptually clearer: cmd.exe /c notepad.exe) should and does result in synchronous (blocking) execution, even in the PowerShell ISE, because cmd.exe /c as well as batch files invoke even GUI-subsystem applications synchronously (unlike when launched from an interactive cmd.exe session).
A note on Start-Process's syntax, given that the sample calls below do not show how to pass arguments to the target application: The best approach is to pass them in a single string, e.g.
Start-Process -Wait msiexec '/i some.msi' - see this answer
To get predictable behavior in both the regular console and in the obsolescent ISE:
To start a GUI application asynchronously (return to the prompt right away / continue executing the script), execute it directly; e.g.:
notepad.exe
If you still want to track the process and later check whether it is still running and what its exit code was on termination, use Start-Process with the -PassThru switch , which returns a [System.Diagnostic.Process] instance:
$process = Start-Process -PassThru notepad.exe
$process.HasExited later tells you whether the process is still running.
To wait synchronously for the process to terminate at some later point, use $process | Wait-Process or $process.WaitForExit() (to wait right away, use Start-Process' -Wait switch - see below).
Wait-Process and .WaitForExit() optionally support a timeout. With Wait-Process, you can specify a -Timeout value in seconds; a non-terminating error is reported if the process doesn't terminate within the timeout period, causing $? to reflect $False.Once the process has exited, $process.ExitCode tells you the exit code (which may not tell you much in the case of a GUI application).
To start a GUI application synchronously (block until the application terminates), use Start-Process -Wait; e.g.:
Start-Process -Wait notepad.exe
-Wait tells Start-Process to wait until the process created terminates; this is the equivalent of start /wait notepad.exe in a cmd.exe session / batch file.
In the rare event that the GUI application reports a meaningful exit code (e.g, msiexec) , you can obtain it as follows:
$exitCode = (Start-Process -PassThru -Wait notepad.exe).ExitCodeThere's a non-obvious alternative to Start-Process -Wait:
Pipe to Write-Output: This takes advantage of the fact that using a command in the non-final segment of a pipeline forces its synchronous execution. That is, if you pipe the GUI-subsystem application call to another command, PowerShell will wait for its completion. While which specific command you pipe to isn't important, the most versatile choice is Write-Output, though if you know that the GUI application produces no stdout output (GUI applications rarely do), you can also use Out-Null:[1]; e.g.:
notepad.exe | Write-OutputThis approach has two advantages:
Start-Process's -ArgumentList parameter.msiexec.exe), this approach (unlike Start-Process -Wait) causes it to be reflected in the automatic $LASTEXITCODE variable.Yet another alternative is to call via cmd /c: /c causes cmd.exe to wait even for GUI-subsystem applications to exit (even though it doesn't do so from a cmd.exe command prompt); $LASTEXITCODE is also properly set in this case.
While slightly slower due to the extra process (cmd.exe), the advantage is that you get more explicit control over the exact process command line in terms of quoting.
This can be helpful with applications that require nonstandard escaping of embedded " chars. or require partial double-quoting of arguments, such as msiexec - see this answer for more information.
Note that none of these approaches work for GUI applications that delegate launching to a different, preexisting process and then exit, which applies to applications such as:
calc.exe)Note that for console-subsystem applications (e.g., findstr.exe), synchronous execution is the default; Start-Process is only needed for GUI applications (and for special situations, such as wanting to run an application in a new console window or with elevation (run as admin)).
To run a console application or shell command asynchronously (without opening a new console window), you have the following options:
[Preferred] Use Start-Job kick off the command, and Receive-Job to receive its output / success status later.
$j = Start-Job { sleep 2; 'hi' }
To synchronously wait for this job to finish (and remove it automatically), use
Receive-Job -Wait -AutoRemoveJob $j
In PowerShell (Core) 6+:
You can use the simpler ... & syntax (as in
POSIX-like Unix shells such as bash) in lieu of Start-Job; e.g.:
$j = & { sleep 2; 'hi!' } &Better yet, you can use the lighter-weight, faster Start-ThreadJob cmdlet, which uses threads for concurrency, but otherwise seamlessly integrates with the other *-Job cmdlets (note that it has no short-hand syntax):
$j = Start-ThreadJob { sleep 2; 'hi' }[Not advisable] You can use something like Start-Process -NoNewWindow powershell -Args ..., but it is ill-advised:
powershell via Start-Process requires intricate quoting based on obscure rules - see this answer of mine for background information.RedirectStandardOutput and -RedirectStandardError (and stdin input via -RedirectStandardInput) and you can use -PassThru to obtain a process-information object to determine the status and exit code of the process, Start-Job and Receive-Job are a simpler, more convenient alternative.P.S.: I don't have an explanation for why cmd.exe "/c start notepad.exe" is blocking in the ISE but not in the regular console. Given the solutions above, however, getting to the bottom of this discrepancy may not be needed.
[1] In rare cases, a GUI application may explicitly attach to the caller's console and write information to it; Write-Output surfaces this information. If you don't want to surface it, use Out-Null.
Take out the start and it waits cmd /c notepad. Otherwise cmd /c start /wait notepad waits. cmd's start builtin runs in the background by default.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With