Obtaining ExitCode using Start-Process and WaitForExit instead of -Wait - powershell

I'm trying to run a program from PowerShell, wait for the exit, then get access to the ExitCode, but I am not having much luck. I don't want to use -Wait with Start-Process, as I need some processing to carry on in the background.
Here's a simplified test script:
cd "C:\Windows"
# ExitCode is available when using -Wait...
Write-Host "Starting Notepad with -Wait - return code will be available"
$process = (Start-Process -FilePath "notepad.exe" -PassThru -Wait)
Write-Host "Process finished with return code: " $process.ExitCode
# ExitCode is not available when waiting separately
Write-Host "Starting Notepad without -Wait - return code will NOT be available"
$process = (Start-Process -FilePath "notepad.exe" -PassThru)
$process.WaitForExit()
Write-Host "Process exit code should be here: " $process.ExitCode
Running this script will cause Notepad to be started. After this is closed manually, the exit code will be printed, and it will start again, without using -wait. No ExitCode is provided when this is quit:
Starting Notepad with -Wait - return code will be available
Process finished with return code: 0
Starting Notepad without -Wait - return code will NOT be available
Process exit code should be here:
I need to be able to perform additional processing between starting the program and waiting for it to quit, so I can't make use of -Wait. How can I do this and still have access to the .ExitCode property from this process?

There are two things to remember here. One is to add the -PassThru argument and two is to add the -Wait argument. You need to add the wait argument because of this defect.
-PassThru [<SwitchParameter>]
Returns a process object for each process that the cmdlet started. By default,
this cmdlet does not generate any output.
Once you do this a process object is passed back and you can look at the ExitCode property of that object. Here is an example:
$process = start-process ping.exe -windowstyle Hidden -ArgumentList "-n 1 -w 127.0.0.1" -PassThru -Wait
$process.ExitCode
# This will print 1
If you run it without -PassThru or -Wait, it will print out nothing.
The same answer is here: How do I run a Windows installer and get a succeed/fail value in PowerShell?
It's also worth noting that there's a workaround mentioned in the "defect report" link above, which is as following:
# Start the process with the -PassThru command to be able to access it later
$process = Start-Process 'ping.exe' -WindowStyle Hidden -ArgumentList '-n 1 -w 127.0.0.1' -PassThru
# This will print out False/True depending on if the process has ended yet or not
# Needs to be called for the command below to work correctly
$process.HasExited
# This will print out the actual exit code of the process
$process.GetType().GetField('exitCode', 'NonPublic, Instance').GetValue($process)

While trying out the final suggestion above, I discovered an even simpler solution. All I had to do was cache the process handle. As soon as I did that, $process.ExitCode worked correctly. If I didn't cache the process handle, $process.ExitCode was null.
example:
$proc = Start-Process $msbuild -PassThru
$handle = $proc.Handle # cache proc.Handle
$proc.WaitForExit();
if ($proc.ExitCode -ne 0) {
Write-Warning "$_ exited with status code $($proc.ExitCode)"
}

Two things you could do I think...
Create the System.Diagnostics.Process object manually and bypass Start-Process
Run the executable in a background job (only for non-interactive processes!)
Here's how you could do either:
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "notepad.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = ""
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
#Do Other Stuff Here....
$p.WaitForExit()
$p.ExitCode
OR
Start-Job -Name DoSomething -ScriptBlock {
& ping.exe somehost
Write-Output $LASTEXITCODE
}
#Do other stuff here
Get-Job -Name DoSomething | Wait-Job | Receive-Job

The '-Wait' option seemed to block for me even though my process had finished.
I tried Adrian's solution and it works. But I used Wait-Process instead of relying on a side effect of retrieving the process handle.
So:
$proc = Start-Process $msbuild -PassThru
Wait-Process -InputObject $proc
if ($proc.ExitCode -ne 0) {
Write-Warning "$_ exited with status code $($proc.ExitCode)"
}

Or try adding this...
$code = #"
[DllImport("kernel32.dll")]
public static extern int GetExitCodeProcess(IntPtr hProcess, out Int32 exitcode);
"#
$type = Add-Type -MemberDefinition $code -Name "Win32" -Namespace Win32 -PassThru
[Int32]$exitCode = 0
$type::GetExitCodeProcess($process.Handle, [ref]$exitCode)
By using this code, you can still let PowerShell take care of managing redirected output/error streams, which you cannot do using System.Diagnostics.Process.Start() directly.

Here's a variation on this theme. I want to uninstall Cisco Amp, wait, and get the exit code. But the uninstall program starts a second program called "un_a" and exits. With this code, I can wait for un_a to finish and get the exit code of it, which is 3010 for "needs reboot". This is actually inside a .bat file.
If you've ever wanted to uninstall folding#home, it works in a similar way.
rem uninstall cisco amp, probably needs a reboot after
rem runs Un_A.exe and exits
rem start /wait isn't useful
"c:\program files\Cisco\AMP\6.2.19\uninstall.exe" /S
powershell while (! ($proc = get-process Un_A -ea 0)) { sleep 1 }; $handle = $proc.handle; 'waiting'; wait-process Un_A; exit $proc.exitcode

Related

Powershell Is it possible to start process -wait in parallel

I tried something like this and it starts one after the other
function start-parallel {
workflow work {
start-process $exe1 -wait
start-process $exe2 -wait
}
work
}
No, you can't simultaneously wait and not wait.
What you'll want to do is save the process objects output by Start-Process -PassThru to a variable, and then not wait until after you've kicked off all the processes:
$processes = #(
Start-Process $exe1 -PassThru
Start-Process $exe2 -PassThru
# ...
)
# now wait for all of them
$null = $processes |ForEach-Object WaitForExit

Powershell: Call Debug Analyzer cdb.exe as Process

i need to call the cdb.exe as a Process to check to kill the process after a few seconds.
Some Dumps cannot be analyzed so i have to do an other call.
Here you can see my code. But it doesn't work. The cdb.exe is not started correctly and i am not getting the output file.
Do you have some advises for me?
The call "before" implementing the process part starts the cdb.exe
$maximumRuntimeSeconds = 3
$path = "C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\cdb.exe"
$process = Start-Process -FilePath $path "-z $unzippedFile.FullName, -c `".symfix;.reload;!analyze -v; q`""
try {
$process | Wait-Process -Timeout $maximumRuntimeSeconds -ErrorAction Stop > $outputFile
Write-Warning -Message 'Process successfully completed within timeout.'
}
catch {
Write-Warning -Message 'Process exceeded timeout, will be killed now.'
$process | Stop-Process -Force
}
# call before implementing Process
& "C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\cdb.exe" -z $unzippedFile.FullName -c ".symfix;.reload;!analyze -v; q" > $outputFile
-Passthru was needed to make Wait-Process work.
I think you also need to look at how the double quoted string is expanding. I think $UnzippedFIle.Fullname might be adding a literal ".FullName" at the end of the actual fullname of the zip file. I don't have your environment, but the rudementary tests I've done show that. Try packing it in a sub-expression like:
"-z $($unzippedFile.FullName), -c `".symfix;.reload;!analyze -v; q`""
Let me know how that goes. Thanks.
C:\>dir /b ok.txt
File Not Found
C:\>type dodump.ps1
$path = "C:\Program Files\Windows Kits\10\Debuggers\x86\cdb.exe"
$process = Start-Process -PassThru -FilePath $path -ArgumentList "-z `"C:\calc.DMP`"" ,
"-c `".symfix;.reload;!analyze -v;q`"" -RedirectStandardOutput c:\\ok.txt
try {
$process | Wait-Process -Timeout 100 -ErrorAction Stop
Write-Host "Process finished within timeout"
}catch {
$process | Stop-Process
Write-Host "process killed"
}
Get-Content C:\ok.txt |Measure-Object -Line
C:\>powershell -f dodump.ps1
Process finished within timeout
Lines Words Characters Property
139

Powershell - Relying on $lastexitcode

I'm executing the following 7 Zip command, which takes about 3-4 hours to finish, and then using $lastexitcode to make sure the compression was successful. Is this the right way of doing this? I'm finding the $lastexitcode not to always be 0 but the compressed file looks fine.
Could something else be modifying this exit code?
& $7z a -tzip -mx=1 $destinationdir\$today.zip $destinationdir\$db1 >$null 2>&1
if ($lastexitcode -ne 0){
...
The '>$null 2>&1' is just used so I don't see the output from 7 zip.
Thanks for any help.
I usually use another approach, like:
$process = Start-Process notepad.exe -PassThru -Wait
then use the
$process.ExitCode
Try this:
$process = Start-Process $7z -PassThru -Wait -ArgumentList "a -tzip -mx=1 $destinationdir\$today.zip $destinationdir\$db1 >$null 2>&1"
if ($process.ExitCode -ne 0) { ....}

Powershell Start Process, Wait with Timeout, Kill and Get Exit Code

I want to repeatedly execute a program in a loop.
Sometimes, the program crashes, so I want to kill it so the next iteration can correctly start. I determine this via timeout.
I have the timeout working but cannot get the Exit Code of the program, which I also need to determine its result.
Before, I did not wait with timeout, but just used -wait in Start-Process, but this made the script hang if the started program crashed. With this setup I could correctly get the exit code though.
I am executing from ISE.
for ($i=0; $i -le $max_iterations; $i++)
{
$proc = Start-Process -filePath $programtorun -ArgumentList $argumentlist -workingdirectory $programtorunpath -PassThru
# wait up to x seconds for normal termination
Wait-Process -Timeout 300 -Name $programname
# if not exited, kill process
if(!$proc.hasExited) {
echo "kill the process"
#$proc.Kill() <- not working if proc is crashed
Start-Process -filePath "taskkill.exe" -Wait -ArgumentList '/F', '/IM', $fullprogramname
}
# this is where I want to use exit code but it comes in empty
if ($proc.ExitCode -ne 0) {
# update internal error counters based on result
}
}
How can I
Start a process
Wait for it to orderly execute and finish
Kill it if it is crashed (e. g. hits timeout)
get exit code of process
You can terminate the process more simply using $proc | kill or $proc.Kill(). Be aware, that you won't be able to retrieve a exit code in this case, you should rather just update the internal error counter:
for ($i=0; $i -le $max_iterations; $i++)
{
$proc = Start-Process -filePath $programtorun -ArgumentList $argumentlist -workingdirectory $programtorunpath -PassThru
# keep track of timeout event
$timeouted = $null # reset any previously set timeout
# wait up to x seconds for normal termination
$proc | Wait-Process -Timeout 4 -ErrorAction SilentlyContinue -ErrorVariable timeouted
if ($timeouted)
{
# terminate the process
$proc | kill
# update internal error counter
}
elseif ($proc.ExitCode -ne 0)
{
# update internal error counter
}
}

How to check the return code of Start-Process [duplicate]

I'm trying to run a program from PowerShell, wait for the exit, then get access to the ExitCode, but I am not having much luck. I don't want to use -Wait with Start-Process, as I need some processing to carry on in the background.
Here's a simplified test script:
cd "C:\Windows"
# ExitCode is available when using -Wait...
Write-Host "Starting Notepad with -Wait - return code will be available"
$process = (Start-Process -FilePath "notepad.exe" -PassThru -Wait)
Write-Host "Process finished with return code: " $process.ExitCode
# ExitCode is not available when waiting separately
Write-Host "Starting Notepad without -Wait - return code will NOT be available"
$process = (Start-Process -FilePath "notepad.exe" -PassThru)
$process.WaitForExit()
Write-Host "Process exit code should be here: " $process.ExitCode
Running this script will cause Notepad to be started. After this is closed manually, the exit code will be printed, and it will start again, without using -wait. No ExitCode is provided when this is quit:
Starting Notepad with -Wait - return code will be available
Process finished with return code: 0
Starting Notepad without -Wait - return code will NOT be available
Process exit code should be here:
I need to be able to perform additional processing between starting the program and waiting for it to quit, so I can't make use of -Wait. How can I do this and still have access to the .ExitCode property from this process?
There are two things to remember here. One is to add the -PassThru argument and two is to add the -Wait argument. You need to add the wait argument because of this defect.
-PassThru [<SwitchParameter>]
Returns a process object for each process that the cmdlet started. By default,
this cmdlet does not generate any output.
Once you do this a process object is passed back and you can look at the ExitCode property of that object. Here is an example:
$process = start-process ping.exe -windowstyle Hidden -ArgumentList "-n 1 -w 127.0.0.1" -PassThru -Wait
$process.ExitCode
# This will print 1
If you run it without -PassThru or -Wait, it will print out nothing.
The same answer is here: How do I run a Windows installer and get a succeed/fail value in PowerShell?
It's also worth noting that there's a workaround mentioned in the "defect report" link above, which is as following:
# Start the process with the -PassThru command to be able to access it later
$process = Start-Process 'ping.exe' -WindowStyle Hidden -ArgumentList '-n 1 -w 127.0.0.1' -PassThru
# This will print out False/True depending on if the process has ended yet or not
# Needs to be called for the command below to work correctly
$process.HasExited
# This will print out the actual exit code of the process
$process.GetType().GetField('exitCode', 'NonPublic, Instance').GetValue($process)
While trying out the final suggestion above, I discovered an even simpler solution. All I had to do was cache the process handle. As soon as I did that, $process.ExitCode worked correctly. If I didn't cache the process handle, $process.ExitCode was null.
example:
$proc = Start-Process $msbuild -PassThru
$handle = $proc.Handle # cache proc.Handle
$proc.WaitForExit();
if ($proc.ExitCode -ne 0) {
Write-Warning "$_ exited with status code $($proc.ExitCode)"
}
Two things you could do I think...
Create the System.Diagnostics.Process object manually and bypass Start-Process
Run the executable in a background job (only for non-interactive processes!)
Here's how you could do either:
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "notepad.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = ""
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
#Do Other Stuff Here....
$p.WaitForExit()
$p.ExitCode
OR
Start-Job -Name DoSomething -ScriptBlock {
& ping.exe somehost
Write-Output $LASTEXITCODE
}
#Do other stuff here
Get-Job -Name DoSomething | Wait-Job | Receive-Job
The '-Wait' option seemed to block for me even though my process had finished.
I tried Adrian's solution and it works. But I used Wait-Process instead of relying on a side effect of retrieving the process handle.
So:
$proc = Start-Process $msbuild -PassThru
Wait-Process -InputObject $proc
if ($proc.ExitCode -ne 0) {
Write-Warning "$_ exited with status code $($proc.ExitCode)"
}
Or try adding this...
$code = #"
[DllImport("kernel32.dll")]
public static extern int GetExitCodeProcess(IntPtr hProcess, out Int32 exitcode);
"#
$type = Add-Type -MemberDefinition $code -Name "Win32" -Namespace Win32 -PassThru
[Int32]$exitCode = 0
$type::GetExitCodeProcess($process.Handle, [ref]$exitCode)
By using this code, you can still let PowerShell take care of managing redirected output/error streams, which you cannot do using System.Diagnostics.Process.Start() directly.
Here's a variation on this theme. I want to uninstall Cisco Amp, wait, and get the exit code. But the uninstall program starts a second program called "un_a" and exits. With this code, I can wait for un_a to finish and get the exit code of it, which is 3010 for "needs reboot". This is actually inside a .bat file.
If you've ever wanted to uninstall folding#home, it works in a similar way.
rem uninstall cisco amp, probably needs a reboot after
rem runs Un_A.exe and exits
rem start /wait isn't useful
"c:\program files\Cisco\AMP\6.2.19\uninstall.exe" /S
powershell while (! ($proc = get-process Un_A -ea 0)) { sleep 1 }; $handle = $proc.handle; 'waiting'; wait-process Un_A; exit $proc.exitcode