Sign in to follow this  
Followers 0
MHz

Exitcode from Run()

18 posts in this topic

#1 ·  Posted (edited)

Summary

With the new StdoutRead/StderrRead etc. when using the Run() function. It is not currently a builtin feature for AutoIt3 to return the exitcode from Run(). I have just seen a request for the feature so thought best to post here to save reposting in support. This UDF will return exitcodes to users of the success with exitcode. So as a general benefit to the AutoIt users. Thanks must be awarded to DaveF for posting the DllCalls to my awareness some time ago, so I just needed to package them into UDFs for general use. Tested with AutoIt 3.3.8.1 .

Simple view of usage

You need to call the function _ProcessOpenHandle() immediately after the use of Run() to get the process handle. Then you call the function _ProcessGetExitCode() when the process has exited so as to retrieve the exitcode. Use _ProcessCloseHandle() to close the handle that was returned from _ProcessOpenHandle() to cleanup.

Functions included

_ProcessOpenHandle()
_ProcessGetExitCode()
_ProcessCloseHandle()

Download

exitcode-from-run.zip

The zip file contains ProcessGetExitcode.au3 and the examples.

Default example

Use all 3 functions from ProcessGetExitcode.au3

#include 'ProcessGetExitcode.au3'

Global $data, $exitcode, $handle_pid, $pid

; run the process with stdout flag STDERR_MERGED
$pid = Run('"' & @ComSpec & '" /c echo Hello world && Exit 13', '', Default, 8)

; open process handle
$handle_pid = _ProcessOpenHandle($pid)

; get stdout data
Do
    Sleep(10)
    $data &= StdOutRead($pid)
Until @error

; require process to be closed before calling _ProcessGetExitCode()
ProcessWaitClose($pid)

; get exitcode of process
$exitcode = _ProcessGetExitCode($handle_pid)

; close process handle
_ProcessCloseHandle($handle_pid)

MsgBox(0x40000, @ScriptName, _
    '$exitcode: ' & $exitcode & @CRLF & _
    '$data: ' & $data _
)

WinAPI* example

Only uses _ProcessGetExitCode() from ProcessGetExitcode.au3

#include 'ProcessGetExitcode.au3'
#include <WinAPI.au3>

Global Const $PROCESS_QUERY_INFORMATION = 0x400
Global $data, $exitcode, $handle_pid, $pid

; run the process with stdout flag STDERR_MERGED
$pid = Run('"' & @ComSpec & '" /c echo Hello world && Exit 13', '', Default, 8)

; open process handle
$handle_pid = _WinAPI_OpenProcess($PROCESS_QUERY_INFORMATION, 0, $pid)

; get stdout data
Do
    Sleep(10)
    $data &= StdOutRead($pid)
Until @error

; require process to be closed before calling _ProcessGetExitCode()
ProcessWaitClose($pid)

; get exitcode of process
$exitcode = _ProcessGetExitCode($handle_pid)

; close process handle
_WinAPI_CloseHandle($handle_pid)

MsgBox(0x40000, @ScriptName, _
    '$exitcode: ' & $exitcode & @CRLF & _
    '$data: ' & $data _
)

Updated UDFs to be compatible with WinAPI* UDFs. 3 UDFs now instead of 2.

MSDN mentions WinXP and above for GetExitcodeProcess so if it is untrue then confirm with a reply.

Thanks to psychotik2k3 for emailing me to fix the corrupted code. My apologies to anyone who suffered from this issue.

Cheers :)

Edited by MHz

Share this post


Link to post
Share on other sites



Summary:

With the new StdoutRead/StderrRead etc. when using the Run() function. It is not currently a builtin feature for AutoIt3 to return the exitcide from Run(). I have just seen a request for the feature so thought best to post here to save reposting in support. This UDF is now implemeted in CompileAu3, to return exitcodes to users of the success of scripts, Au3Check etc. So as a general benefit to the AutoIt community, I would like to share this UDF in open view. Many Thanks must be awarded to DaveF for posting the DllCalls to my awareness some time ago, so I just needed to package them into a UDF for general use.

Simple view of usage:

You need to call the function straight after the use of Run to get the process handle (only use 1 parameter), then you call the function a 2nd time when the process has exited, to retrieve the exitcode (need 2 parameters).

$pid = Run('file.exe')
$handle = _ProcessExitCode($pid)

While ProcessExists($pid)
    Sleep(10)
WEnd

$exitcode = _ProcessExitCode($pid, $handle)
Exit $exitcode

UDF with a working test of operation:

#region - Test
; test file using Exit 3
Do
    $path = Random(1000, 2000, 1) & '.au3'
Until Not FileExists($path)
If Not FileExists($path) Then
    FileWrite($path, 'Exit ' & Random(0, 19, 1))
    $read = FileReadLine($path)
    $pid = Run(@AutoItExe & ' "' & $path & '"')
    $handle = _ProcessExitCode($pid)
    While ProcessExists($pid)
        Sleep(10)
    WEnd
    $exitcode = _ProcessExitCode($pid, $handle)
    FileDelete($path)
    MsgBox(0x0, 'Returns codes of ' & $path, _
            'Line: ' & @TAB & $read & @LF & @LF & _
            'PID: '  & @TAB & $pid  & @LF & @LF & _
            'Exitcode: ' & $exitcode)
    Exit $exitcode
EndIf
#endregion

;===============================================================================
;
; Function Name:    _ProcessExitCode()
; Description:    Returns a handle/exitcode from use of Run().
; Parameter(s):  $i_Pid     - ProcessID returned from a Run() execution
;                  $h_Process   - Process handle
; Requirement(s):   None
; Return Value(s):  On Success - Returns Process handle while Run() is executing
;                               (use above directly after Run() line with only PID parameter)
;                             - Returns Process Exitcode when Process does not exist
;                               (use above with PID and Process Handle parameter returned from first UDF call)
;                  On Failure - 0
; Author(s):        MHz (Thanks to DaveF for posting these DllCalls in Support Forum) 
;
;===============================================================================
;
Func _ProcessExitCode($i_Pid, $h_Process = 0)
; 0 = Return Process Handle of PID else use Handle to Return Exitcode of a PID
    Local $v_Placeholder
    If Not IsArray($h_Process) Then
  ; Return the process handle of a PID
        $h_Process = DllCall('kernel32.dll', _
                'ptr', 'OpenProcess', _
                'int', 0x400, _
                'int', 0, _
                'int', $i_Pid)
        If Not @error Then Return $h_Process
    Else
  ; Return Process Exitcode of PID
        $h_Process = DllCall('kernel32.dll', _
                'ptr', 'GetExitCodeProcess', _
                'ptr', $h_Process[0], _
                'int_ptr', $v_Placeholder)
        If Not @error Then Return $h_Process[2]
    EndIf
    Return 0
EndFunc

Cheers :)

This UDF is producing a memory leak as no CloseHandle of the process handle is done. after retrieving the exitcode.

Perhaps it would be clearer to have a _ProcessOpenHandle, _ProcessGetExitCode and _ProcessCloseHandle if we really want to retrieve more than once the exitcode. :mellow:

Share this post


Link to post
Share on other sites

#4 ·  Posted (edited)

This UDF is producing a memory leak as no CloseHandle of the process handle is done. after retrieving the exitcode.

Perhaps it would be clearer to have a _ProcessOpenHandle, _ProcessGetExitCode and _ProcessCloseHandle if we really want to retrieve more than once the exitcode. :)

Ok, I need to do what you recommend. 3 Functions would now make a handful to use now as use of 3 function calls to accomplish the task. I'm not sure why you want to get the exitcode more then once when you store it in a variable?

How about this concept of usage, JPM?

Is the leak fixed with this?

#region - Test
; test file using an Exit code
Do
    $path = Random(1000, 2000, 1) & '.au3'
Until Not FileExists($path)

If Not FileExists($path) Then
    FileWrite($path, 'Exit ' & Random(0, 19, 1))
    $read = FileReadLine($path)
    
    $pid = Run(@AutoItExe & ' "' & $path & '"')
    $handle = _ProcessOpenHandle($pid)
    
    While ProcessExists($pid)
        Sleep(10)
    WEnd
    
    $exitcode = _ProcessGetExitCode($handle)
    _ProcessCloseHandle($handle)
    
    FileDelete($path)
    MsgBox(0x0, 'Returns codes of ' & $path, _
            'Line: ' & @TAB & $read & @LF & @LF & _
            'PID: '  & @TAB & $pid  & @LF & @LF & _
            'Exitcode: ' & $exitcode)
    Exit $exitcode
EndIf
#endregion

Func _ProcessOpenHandle($i_pid)
; Return the process handle of a PID
    $h_Process = DllCall('kernel32.dll', _
            'ptr', 'OpenProcess', _
            'int', 0x400, _
            'int', 0, _
            'int', $i_Pid)
    If Not @error Then Return $h_Process
    Return 0
EndFunc

Func _ProcessGetExitCode($h_Process)
; Return Process Exitcode of PID
    Local $v_Placeholder
    If IsArray($h_Process) Then
        $h_Process = DllCall('kernel32.dll', _
                'ptr', 'GetExitCodeProcess', _
                'ptr', $h_Process[0], _
                'int_ptr', $v_Placeholder)
        If Not @error Then Return $h_Process[2]
    EndIf
    Return 0
EndFunc

Func _ProcessCloseHandle($h_Process)
; Close the process handle of a PID
    DllCall('kernel32.dll', _
            'ptr', 'CloseHandle', _
            'ptr', $h_Process)
    If Not @error Then Return 1
    Return 0
EndFunc

Edit: I'm not getting a positive return on the CloseHandle. Working on it.

Edit: Working without error now. Was just a typo causing grief.

Edited by MHz

Share this post


Link to post
Share on other sites

Edit: I'm not getting a positive return on the CloseHandle. Working on it. :)

Anything you'd like us to try... maybe to help you out a bit?

[center]Common sense plays a role in the basics of understanding AutoIt... If you're lacking in that, do us all a favor, and step away from the computer.[/center]

Share this post


Link to post
Share on other sites

See my 2nd edit, thanks. I made a typo in the name of the dll. The last post of code is my working proposal for use currently. Unless something else needs attention that someone spots.

Share this post


Link to post
Share on other sites

@MHz

If you don't want 3 functions you can add an optional parameter to _ProcessGetExitCode which will do the closing of the process handle or not. I will recommend the default being close not to forget. :)

Share this post


Link to post
Share on other sites

#8 ·  Posted (edited)

@MHz

If you don't want 3 functions you can add an optional parameter to _ProcessGetExitCode which will do the closing of the process handle or not. I will recommend the default being close not to forget. :mellow:

Thanks JPM. Updated at 1st post. :)

Please report any issues.

Edit:

Thanks for the positive comment, nfwu.

Edited by MHz

Share this post


Link to post
Share on other sites

Woot! Way to go, MHz!


Yes yes yes, there it was. Youth must go, ah yes. But youth is only being in a way like it might be an animal. No, it is not just being an animal so much as being like one of these malenky toys you viddy being sold in the streets, like little chellovecks made out of tin and with a spring inside and then a winding handle on the outside and you wind it up grrr grrr grrr and off it itties, like walking, O my brothers. But it itties in a straight line and bangs straight into things bang bang and it cannot help what it is doing. Being young is like being like one of these malenky machines.

Share this post


Link to post
Share on other sites

Thanks Dave. Your intial code helped to make a useful exitcode UDFs for Run(). I just packaged it up into some general UDFs that make it useful for any script. Thankfully, JPM noticed a missing step which is corrected.

Your statement is positive that it is deemed good code. :)

Share this post


Link to post
Share on other sites

You don't mention that GetExitCodeProcess() can return STILL_ACTIVE if called on a running process.

Share this post


Link to post
Share on other sites

#12 ·  Posted (edited)

You don't mention that GetExitCodeProcess() can return STILL_ACTIVE if called on a running process.

Your right, Valik. May just need to check to see if the Pid is still active, set error and then close the handle to resolve the event. I updated the 1st post to reflect the changes of this. Thanks for the alert. Edited by MHz

Share this post


Link to post
Share on other sites

Thanx!!!, works perfect! :whistle:

Share this post


Link to post
Share on other sites

Thanks, I go the JPM idea of 3 funcs rather then the consolidated just simply for that view as those funcs are multipurpose for other functions. :whistle:

Share this post


Link to post
Share on other sites

#15 ·  Posted (edited)

This is great! I'd love to see _ProcessOpenHandle, _ProcessCloseHandle, and _ProcessGetExitCode added to Process.au3, as well as maybe the following change to _RunDOS:

Current _RunDOS: (Returns only the exit code, output must be obtained by piping the output to a temporary file)

Func _RunDOS($sCommand)
    Local $nResult = RunWait(@ComSpec & " /C " & $sCommand, "", @SW_HIDE)
    Return SetError(@error, @extended, $nResult)
EndFunc;==>_RunDOS

Proposed _RunDOS: (Captures and returns the STDIO, and @error=exit code)

Func _RunDOS($sCommand)
    Local $iPID = Run(@ComSpec & " /C " & $sCommand, "", @SW_HIDE, 8); 8 = StdOut + StdErr merged
    ProcessWaitClose($iPID); Wait for the process to finish, like RunWait would
    Return SetError(_ProcessGetExitCode($iPID, _ProcessOpenHandle($iPID)), 0, StdoutRead($iPID))
EndFunc ;==>_RunDOS

Returns the STDIO of the process, and @error is the exitcode, so this would be a code-breaking, but worth it instead of piping output to a file as I'm sure many people currently do it.

Alternative, non-code-breaking:

Func _RunDOS($sCommand, $iMode=0)
; $iMode - 0 = Classic (return exit code in @error)
;         1 = Return the STDIO, @error = exit code
    If $iMode = 0 Then
        Local $iResult = RunWait(@ComSpec & " /C " & $sCommand, "", @SW_HIDE)
        Return SetError(@error, @extended, $iResult)
    Else
        Local $iPID = Run(@ComSpec & " /C " & $sCommand, "", @SW_HIDE, 8); 8 = StdOut + StdErr merged
        ProcessWaitClose($iPID); Wait for the process to finish, like RunWait would
        Return SetError(_ProcessGetExitCode($iPID, _ProcessOpenHandle($iPID)), 0, StdoutRead($iPID))
    EndIf
EndFunc ;==>_RunDOS
Edited by c0deWorm

My UDFs: ExitCodes

Share this post


Link to post
Share on other sites

the codebox in the first post seems to be brolen, only shows a few lines of code and some garbage characters...

can anyone post the latest version of this udf or tell me where i csn find it...thanks !

Share this post


Link to post
Share on other sites

the codebox in the first post seems to be brolen, only shows a few lines of code and some garbage characters...

can anyone post the latest version of this udf or tell me where i csn find it...thanks !

I never knew why old threads have that.. but look the dates of the posts, this have been abandoned since 2008

Share this post


Link to post
Share on other sites

Corrupted code replaced with updated code. You can now use all 3 functions in the include file or just use _ProcessGetExitCode() with the Standard UDFs _WinAPI_OpenProcess() and _WinAPI_CloseHandle().

Thanks psychotik2k3 for the reminder.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0