How do I check the errorcode of a `&` Powershell command? - powershell

In a Powershell script, I run a command like:
& "C:\...\myprogram.exe"
How do I check to see if myprogram.exe returned an errorcode?

If you run it with the call operator (or without), check $LASTEXITCODE. It will always be the exit code of the last program you ran in the current session.
If you need to use Start-Process instead (such as needing to use -Wait to wait on a GUI application to complete), make sure to use -PassThru to store the process object as a variable, then check its ExitCode property. $LASTEXITCODE is not set in this case.

Related

is there a wait parameter in PowerShell to allow the script to wait until a command is completed

I trying to install a software using Start-Process in PowerShell, I would like for the script to wait until a command is completed before proceeding to the next one. I'm not experienced I tired the script below but it did not work.
Start-Process -Wait -FilePath "C:\Temp\Latitude_5X10_Precision_3550_1.15.0.exe" -ArgumentList "/S" -PassThru
Your Start-Process call is correct, but -Wait invariably only tracks the lifetime of the directly launched process (C:\Temp\Latitude_5X10_Precision_3550_1.15.0.exe in your case).
That is, you're out of luck if the target process itself spawns yet another process in order to perform its task and then returns before that child process has terminated.
Additional work is then needed, if even feasible:
If you know the name of the child process, you can try to find and track it via Get-Process.
Alternatively, if you know of an indirect sign that the task has completed, such as the existence of a directory or a registry entry, look for that.
As an aside: console(-subsystem) applications can be invoked directly for synchronous (blocking) execution (e.g., foo.exe bar baz or & $fooExePath bar baz), which is the preferred method, because it connects the application's output streams to PowerShell's streams.

How do I get the ErrorLevel from a process started via PowerShell Start-Process called from CMD?

I'd like to be able to get the ErrorLevel of Process.exe from this example command:
C:\>PowerShell -NoP -C "Start-Process "C:\Process.exe""
If Process.exe exits with an ErrorLevel of 5, I'd like to be able to capture that via CMD, ideally in the %ErrorLevel% variable. I'm not proficient enough in PowerShell to do this.
Use the following, assuming that C:\process.exe is a console application:[1]
PowerShell -NoP -C "C:\Process.exe; exit $LASTEXITCODE"
Note: If the executable path contains spaces, enclose it in '...'; if it contains ' itself, either escape the enclosed ' chars. as '', or enclose the path in \"...\" (sic) instead - see this answer for more information.
In order to get a process' exit code, you must wait for it to terminate.
Start-Process does not wait for termination by default, unless you add the -Wait switch - but then you'd also need -PassThru in order to return a process-information object whose .ExitCode property you'd need to report via exit.
Direct execution of the target executable, as shown above, (a) results in synchronous execution of console applications[1] to be begin with and (b) automatically reflects the process exit code in the automatic $LASTEXITCODE variable variable.
Without the exit $LASTEXITCODE statement, the process' exit code would be mapped to an abstracted exit code: 0 (success) would be reported as-is, but any nonzero exit code would be mapped to 1 - see this post for more information.
Either way, PowerShell's (powershell.exe's) exit code will be reflected in cmd.exe's dynamic %ErrorLevel% variable.
[1] If your application happens to be a GUI-subsystem application, you'll indeed need a Start-Process-based solution; as you've noted in a comment yourself, the solution would be:
PowerShell -NoP -C "exit (Start-Process 'C:\Process.exe' -Wait -PassThru).ExitCode"

Powershell Script not working out of ISE

I have a powershell Script that runs fine in the ISE, but when I run it directly from the .ps1 file it stops at this point:
Write-Host "Attempting to start FFMPEG Process with arguments:$ArgumentList::::" -ForegroundColor Green
Start-Process $SCRIPT:FFMPEGLocation $ArgumentList -Wait
I get the Write-Host printout, with the correct Argument List, but no window pops up for the start-process or anything. I made sure to use the SCRIPT scope variable for anything that is outside the function. It runs fine in ISE just not when I run it in Console. No errors either, and the $SCRIPT:FFMPEGLocation variable is a direct path to the exe to be executed.
Any help would be greatly appreciated, and if you need more let me know.
PSVersion 5.1.14393.1066
OSVersion 10.0.14393
As per comments, check the value passed to Start-Process to ensure it contains what you expect as the issue is likely to be with $SCRIPT:FFMPEGLocation.
The difference between the ISE and Console with regard to scope can be frustrating.
See this SuperUser post which explains the behaviour further
. You may also find this post helpful;
BartekB explains that F5 in the ISE actually dot-sources the script instead of calling it, and provides a function to run a clean session.
An alternative method which will also wait on the process that needs to be run:
& $EXE /arg 1 /arg 2 -etc 2>&1 | % { $_ }
This will execute the command and pass the args, redirect any errors to the output stream and then print that output (for some reason, I've had bad luck with actually getting output from commands)

Powershell waits on cmd.exe differently depending on environment

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.
To get predictable behavior in both the regular console and in the ISE:
Start a GUI application asynchronously (return to the prompt right away / continue executing the script):
notepad.exe
Invoking Notepad directly makes it run asynchronously, because it is a GUI-subsystem application.
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 -PassThru, which makes Start-Process return a [System.Diagnostic.Process] instance:
$process = Start-Process -PassThru notepad.exe
$process.HasExited later tells you whether the process is still running.
Once it has exited, $process.ExitCode tells you the exit code (which may not tell you much in the case of a GUI application).
To wait synchronously (at some point):
Use Wait-Process $process.ID to wait (indefinitely) for the process to terminate.
Add a -Timeout value in seconds to limit the waiting period; a non-terminating error is reported if the process doesn't terminate within the timeout period, causing $? to reflect $False.
Start a GUI application synchronously (block until the application terminates):
Start-Process -Wait notepad.exe
-Wait tells Start-Process to wait until the process created terminates; this is the equivalent of cmd /c 'start /wait notepad.exe'.
There's a non-obvious shortcut to Start-Process -Wait: you can repurpose the Out-Null cmdlet to take advantage of the fact that piping invocation of a program to it makes Out-Null to wait for the program's termination (a GUI application has no stdout or stderr output, so there's nothing for Out-Null to discard; the only effect is synchronous invocation):
notepad.exe | Out-Null
In fact, this approach has two advantages:
If arguments must be passed to the GUI application, they can be passed directly, as usual - rather than indirectly, via Start-Process's -ArgumentList parameter.
In the (rare) event that a GUI application reports a meaningful process exit code (e.g, msiexec.exe), the Out-Null approach (unlike Start-Process -Wait) causes it to be reflected in the automatic $LASTEXITCODE variable.
Note: In rare cases, a GUI application may explicitly attach to the caller's console and write information to it; in order to surface that, pipe to | Write-Output instead (you'll still be able to evaluate $LASTEXITCODE) - see this answer.
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:
Passing a shell command to powershell via Start-Process requires intricate quoting based on obscure rules - see this answer of mine for background information.
Any stdout and stderr output produced by the application / shell command will by default arrive asynchronously in the current console, interspersed with what you're doing interactively.
While you can redirect these streams to files with 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.

PowerShell Start-Process not setting $lastexitcode

I have a set of test DLL's that I'm running from a powershell script that calls OpenCover.Console.exe via the Start-Process command.
I have the -returntargetcode flag set
After execution I check $lastexitcode and $?. They return 1 and True respectively all the time. Even when tests are failing.
Shouldn't $lastexitcode be 0 when all tests pass and 1 when they fail?
By default, Start-Process is asynchronous, so it doesn't wait for your process to exit. If you want your command-line tool to run synchronously, drop the Start-Process and invoke the command directly. That's the only way it will set $LASTEXITCODE. For example, causing CMD.exe to exit with a 2:
cmd /c exit 2
$LASTEXITCODE
You can make Start-Process synchronous by adding the -Wait flag, but it still wont' set $LASTEXITCODE. To get the ExitCode from Start-Process you add -PassThru to your Start-Process, which then outputs a [System.Diagnostics.Process] object which you can use to monitor the process, and (eventually) get access to its ExitCode property. Here's an example that should help:
$p = Start-Process "cmd" -ArgumentList "/c exit 2" -PassThru -Wait
$p.ExitCode
Of course the advantage of this approach is you don't need to wait for the process, but later when it exits you have the information about it's run in $p.
When executing a GUI application, dropping the Start-Process does not help, as PowerShell does not wait for GUI application to complete, when executing them directly this way. So $LASTEXITCODE is not set.
Piping the (non existing) GUI application output helps, as it makes PowerShell to wait for the application to complete.
notepad.exe | Out-Null
echo $LASTEXITCODE
Note that "GUI application" does not necessarily mean that the application has windows. Whether an application is GUI or console is a flag in .exe file header.
Start-Process -PassThru -Wait as suggested in the answer by #Burt_Harris works too in this case, it's just a bit more complicated.