Jump to content
Sign in to follow this  
Allow2010

end dos process with ctrl+c from script - help needed

Recommended Posts

thank you, i am a bit busy right now, will take a look asap and will report back...again thank you very much for your help !

Share this post


Link to post
Share on other sites

Your example works fine so far, but what i still do not get: wehn closing the external cmd window also the autoit app closes...

i was thinking that the Func _ConsoleEventHandler($dwCtrlType) was there to prevent this, but it seems this does not work...

here is my minimal version, i do not understand why it closes when i press ctrl-c...

#NoTrayIcon
#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_Res_requestedExecutionLevel=asInvoker
#AutoIt3Wrapper_AU3Check_Stop_OnWarning=y
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****
;#AutoIt3Wrapper_Change2CUI=y
If Not @Compiled Then Exit
#include <windowsconstants.au3>
Global $iCallback
OnAutoItExitRegister("_Exit")
Global Const $CTRL_BREAK_EVENT = 1
Global Const $CTRL_C_EVENT = 0
Global $fCloseFlag = False
$iCallback = _InitConsole()
_Main()
Func _Main()
GUICreate("", 600, 400)
Local $iMemo = GUICtrlCreateEdit("", 2, 28, 596, 370, $WS_VSCROLL)
GUICtrlSetLimit(-1, 1048576)
Local $cCtrl_C = GUICtrlCreateButton("Ctrl-C", 2, 2, 120, 25)
GUISetState()
;send ctrl-c to console process not launched by script
GUICtrlSetData($iMemo, "")
DllCall("kernel32.dll", "bool", "FreeConsole") ;free console so we can attach to another console process
$PID=InputBox("PID","Enter the PID of the Console")
If $PID <> @AutoItPID Then DllCall("kernel32.dll", "bool", "AttachConsole", "dword", $PID)
GUICtrlSetData($iMemo, "Ctrl-C to close existing console")
While 1
  Switch GUIGetMsg()
   Case -3
    ExitLoop
   Case $cCtrl_C
    ;send ctrl-c to script and child process event handler, script event handler will block exit
    DllCall("kernel32.dll", "bool", "GenerateConsoleCtrlEvent", "dword", $CTRL_C_EVENT, "dword", 0)
  EndSwitch
WEnd
DllCall("kernel32.dll", "bool", "FreeConsole")
Exit
EndFunc   ;==>_Main
Func _Exit()
DllCall("kernel32.dll", "bool", "FreeConsole")
DllCall("Kernel32.dll", "bool", "SetConsoleCtrlHandler", "ptr", 0, "bool", 0)
DllCallbackFree($iCallback)
EndFunc   ;==>_Exit
Func _InitConsole()
    DllCall("kernel32.dll", "int", "AllocConsole")
    ;DllCall("kernel32.dll", "bool", "AttachConsole", "dword", -1)
    Local $hConsolehWnd = DllCall("Kernel32.dll", "hwnd", "GetConsoleWindow")
    If @error Then Return
    WinSetState($hConsolehWnd[0], "", @SW_HIDE)
    Local $iCB = DllCallbackRegister("_ConsoleEventHandler", "long", "dword")
    DllCall("Kernel32.dll", "bool", "SetConsoleCtrlHandler", "ptr", DllCallbackGetPtr($iCB), "bool", 1)
    Return $iCB
EndFunc   ;==>_InitConsole
Func _ConsoleEventHandler($dwCtrlType)
Switch $dwCtrlType
  Case $CTRL_C_EVENT, $CTRL_BREAK_EVENT
   $fCloseFlag = True ; set flag for checking if child process exists
   Return True ; exiting blocked, exit must be called from loop
   ;Return False ;exit normally
EndSwitch
Return False
EndFunc   ;==>_ConsoleEventHandler

Share this post


Link to post
Share on other sites

I found a little utility that sends a ctrl-break. I had to collect the child pid due to what I was running and I ran this app (SendSignal.exe - you'll find a link in google).

I found it and it works well, made SendSignal in autoit, here is the source code for anyone who prefers to create his own exe.

The only problem is that i still have to call it as an external exe which slows down the process a bit

If $Cmdline[0] = 1 Then
DllCall("kernel32.dll", "bool", "AttachConsole", "dword", $Cmdline[1])
DllCall("kernel32.dll", "bool", "GenerateConsoleCtrlEvent", "dword", 0, "dword", 0)
EndIf
Edited by Allow2010

Share this post


Link to post
Share on other sites

Your example works fine so far, but what i still do not get: wehn closing the external cmd window also the autoit app closes...

i was thinking that the Func _ConsoleEventHandler($dwCtrlType) was there to prevent this, but it seems this does not work...

The attached console is shared continuously in that script, so closing either console closes both processes.

The event handler can only reliably block Ctrl-C/Break (Blocking exit only works (unreliably) in XP and is no longer supported as of Vista - see MSDN)

The example and UDF below fixes this by only attaching to the console to send Ctrl-C.

If you stay attached to a console or cmd.exe prompt or just want to prevent a console/prompt from being closed when it is running a process

you can disable the X button and remove the menu close option, but user can still Ctrl-C or enter Exit.

Local $hCon = DllCall("Kernel32.dll", "hwnd", "GetConsoleWindow");get attached console window handle
;or WinGetHandle("[TITLE:title; CLASS:ConsoleWindowClass]")
Local $hMenu = DllCall('user32.dll', 'int', 'GetSystemMenu', 'hwnd', $hCon[0], 'int', 0)
DllCall('user32.dll', 'int', 'EnableMenuItem', 'hwnd', $hMenu[0], 'int', 0xF060, 'int', 0x00000002)  ; $SC_CLOSE/$MF_DISABLED
DllCall("user32.dll", "int", "RemoveMenu", "hwnd", $hMenu[0] , "int", 0xF060, "int", 0x0)

here is my minimal version, i do not understand why it closes when i press ctrl-c...

You have to register the event handler after attaching to the console.

I found it and it works well, made SendSignal in autoit, here is the source code for anyone who prefers to create his own exe.

The only problem is that i still have to call it as an external exe which slows down the process a bit

This can be done in the main script without an external exe.

I've reworked the example and created a UDF that will work with a process run from the script or an existing console app/cmd window

Example initializes the event handler after attaching to the console, then frees the console after sending Ctrl-C,

so the script does not exit when the attached console does.

Example sends Ctrl-C to 7-Zip archiving files in a selected folder and a cmd.exe window (open a command window and run a process)

Edit: changes to udf for issue with undeclared var error with callback param dwCtrlType (removed as it is not required) (callback is created in a new thread, so there are problems with variables and functions)

added option to loop sending ctrl-c

Edit: Updated UDF with an optional mode that eliminates the requirement for a callback event handler, added sleep param,

initial unresponsive console/process checking and param error checking

#NoTrayIcon
Opt("MustDeclareVars", 1)
If Not @Compiled Then Exit

_Main()

Func _Main()
    GUICreate("Send Ctrl-C to console process", 600, 400)
    Local $iMemo = GUICtrlCreateEdit("", 2, 28, 596, 370, 0x00200000)
    GUICtrlSetLimit(-1, 1048576)
    Local $cCtrl_C = GUICtrlCreateButton("Ctrl-C", 2, 2, 120, 25)
    GUISetState()


    Cmd("cmd.exe /c dir c: /s", $iMemo, $cCtrl_C)
    Cmd("ping.exe -t " & @IPAddress1, $iMemo, $cCtrl_C)

    Local $sTargetFolder = @TempDir
    ;run 7-Zip and sends Ctrl-C to cancel
    Local $Archiv = $sTargetFolder & "_test.zip"
    Cmd(@ProgramFilesDir & "7-Zip7z a -tzip " & $Archiv & " " & $sTargetFolder & "*", $iMemo, $cCtrl_C)

    ;Send Ctrl-C to a console app or cmd.exe console running a process

    ;Enter pid of console
    Local $PID = 0

    ;Local $aCon = WinList("[CLASS:ConsoleWindowClass]") ;get first console
    ;Local $PID = WinGetProcess($aCon[1][1])
    _ConsoleSendCtrlC($PID)

    While GUIGetMsg() <> -3
    WEnd
    Exit
EndFunc   ;==>_Main

Func Cmd($sCmd, $cEdit, $cBtn)
    Local $iPid = Run($sCmd, @ScriptDir, @SW_HIDE, 2)
    Local $h_Process = DllCall('kernel32.dll', 'ptr', 'OpenProcess', 'int', 0x400, 'int', 0, 'int', $iPid)
    Local $sData, $fRead = True
    GUICtrlSetData($cEdit, "Running commandline: " & @CRLF & $sCmd & @CRLF & @CRLF)

    While 1
        If $fRead Then
            $sData = StdoutRead($iPid)
            If @error Then
                $fRead = Not $fRead
                GUICtrlSetData($cEdit, @CRLF & "+++Process finished - Exit Code: " & _GetExitCode($h_Process) & @CRLF, 1)
                Return
            Else
                $sData = StringReplace($sData, @CR & @CR, @CR)
                If $sData Then GUICtrlSetData($cEdit, $sData & @CRLF, 1)
            EndIf
        EndIf
        Switch GUIGetMsg()
            Case -3
                Exit
            Case $cBtn
                _ConsoleSendCtrlC($iPid)
                GUICtrlSetData($cEdit, @CRLF & "!!!Process terminated - Exit Code: " & _GetExitCode($h_Process) & @CRLF, 1)
                Return
        EndSwitch
    WEnd
EndFunc   ;==>Cmd

Func _GetExitCode(ByRef $hProc)
    If Not IsArray($hProc) Then Return -1
    Local $retval = -1
    Local $i_ExitCode = DllCall('kernel32.dll', 'ptr', 'GetExitCodeProcess', 'ptr', $hProc[0], 'int*', 0)
    If Not @error Then $retval = $i_ExitCode[2]
    DllCall('kernel32.dll', 'ptr', 'CloseHandle', 'ptr', $hProc[0])
    Return $retval
EndFunc   ;==>_GetExitCode

; #FUNCTION# ====================================================================================================================
; Name ..........: _ConsoleSendCtrlC
; Description....: Send Ctrl-C to cancel a process running as a console or a process running in a cmd.exe prompt.
; Syntax ........: _ConsoleSendCtrlC($PID, $iMode = 0, $iDll = "kernel32.dll")
; Parameters.....: hWnd        - Process ID of a ConsoleWindowClass window
;                 $iEvent    - 0 = Send CTRL_C_EVENT=0
;                 $iEvent    - 1 = Send CTRL_BREAK_EVENT=1
;                 $iMode    - 0 = No Callback required - Block CTRL_C_EVENT only for calling process
;                 $iMode    - 1 = Callback required - Block CTRL_C_EVENT and CTRL_BREAK_EVENT for calling process
;                 $iLoop    - Number of times to send Ctrl-C/Break event (Run in loop with ProcessExists check and 10ms Sleep)
;                 $iSlp    - Sleep value - Default 10ms
;                 $iDll    - Optional handle from DllOpen()
; Return values..: Succcess - 1
;                 Failure - -1 and sets @error
; Author.........: rover 2k12
; Remarks .......: Allows cancelled process to do cleanup before exiting.
;                 Event handler blocking of Ctrl-C/Break prevents script exiting when GenerateConsoleCtrlEvent called on a shared process console
;
;                 Note: It is preferable to send a CTRL_C_EVENT ($iEvent = 0) without the callback ($iMode = 0). (callback is created in a thread)
;
;                 Note: Console window and process checking is untested and unfinished in this version (Return = -1 and Error = 2 indicates console window is unresponsive)
;                 Script crash has occurred when sending Ctrl+C in a tight loop to an unresponsive process
;                 Test console window or process reponsiveness and send Ctrl+C one or more times with long sleep then if still unreponsive use ProcessClose.
;
; Related .......: _ConsoleEventHandler
; Example .......: Yes
;
; Optional: Callback Event Handler Function (for $iMode = 1 only)
; Func _ConsoleEventHandler()
;       Return 1 ; Ctrl-C/Break exiting blocked
; EndFunc   ;==>_ConsoleEventHandler
;
; ===============================================================================================================================
Func _ConsoleSendCtrlC($PID, $iEvent = 0, $iMode = 0, $iLoop = 1, $iSlp = 10, $iDll = "kernel32.dll")
    ;Author: rover 2k12
    Local $aRet = DllCall($iDll, "bool", "AttachConsole", "dword", $PID)
    If @error Or $aRet[0] = 0 Then Return SetError(1, 0, -1)
    ;sets GetLastError to ERROR_GEN_FAILURE (31) if process does not exist
    ;sets GetLastError to ERROR_ACCESS_DENIED (5) if already attached to a console
    ;sets GetLastError to ERROR_INVALID_HANDLE (6) if process does not have a console

    $aRet = DllCall("Kernel32.dll", "hwnd", "GetConsoleWindow") ;must be attached to console to get hWnd
    If Not @error And IsHWnd($aRet[0]) Then
        $aRet = DllCall('user32.dll', 'int', 'IsHungAppWindow', 'hwnd', $aRet[0]);from Yashieds' WinAPIEx.au3
        If Not @error And $aRet[0] Then
            DllCall($iDll, "bool", "FreeConsole")
            Return SetError(2, 0, -1)
        EndIf
    Else
        Return SetError(3, 0, -1)
    EndIf

    Local $iCTRL = 0, $iRet = 1, $pCB = 0, $iCB = 0, $iErr = 0, $iCnt = 0
    ;check params
    $iLoop = Int($iLoop)
    If $iLoop < 1 Then $iLoop = 1
    If $iEvent Then $iCTRL = 1
    If $iSlp < 10 Then $iSlp = 10

    If $iMode Then
        Local $sCB = "_ConsoleEventHandler"
        Call($sCB)
        If @error <> 0xDEAD And @extended <> 0xBEEF Then
            Local $iCB = DllCallbackRegister($sCB, "long", "")
            If Not $iCB Then
                DllCall($iDll, "bool", "FreeConsole")
                Return SetError(4, 0, -1)
            EndIf
            $pCB = DllCallbackGetPtr($iCB)
        Else
            $iCTRL = 0
        EndIf
    EndIf

    $aRet = DllCall($iDll, "bool", "SetConsoleCtrlHandler", "ptr", $pCB, "bool", 1)
    If @error Or $aRet[0] = 0 Then
        If $iMode Then DllCallbackFree($iCB)
        DllCall($iDll, "bool", "FreeConsole")
        Return SetError(5, @error, -1)
    EndIf

    ;send ctrl-c, free console and unregister event handler
    Do
        $aRet = DllCall($iDll, "bool", "GenerateConsoleCtrlEvent", "dword", $iCTRL, "dword", 0)
        If @error Or $aRet[0] = 0 Then $iRet = -1
        $iCnt += 1
        Sleep($iSlp)
    Until $iCnt = $iLoop Or ProcessExists($PID) = 0

    $aRet = DllCall($iDll, "bool", "FreeConsole")
    If Not @error Then $iErr = $aRet[0]
    If $iMode Then Sleep(50) ;10ms o.k, but margin for error better (DllCallbackFree can crash script if called too soon after console freed when using callback)
    $aRet = DllCall($iDll, "bool", "SetConsoleCtrlHandler", "ptr", 0, "bool", 0)
    If $iMode Then DllCallbackFree($iCB)
    Return SetError(@error, $iErr, $iRet)
EndFunc   ;==>_ConsoleSendCtrlC


Func _ConsoleEventHandler() ;single dword param $dwCtrlType in callback can sometimes cause - Error: Variable used without being declared. (event handler is run in a new thread)
    ;Optional: Callback Event Handler Function (for $iMode = 1 only)
    Return 1 ; Ctrl-C/Break exiting blocked
EndFunc   ;==>_ConsoleEventHandler

UDF: _ConsoleSendCtrlC()

; #FUNCTION# ====================================================================================================================
; Name ..........: _ConsoleSendCtrlC
; Description....: Send Ctrl-C to cancel a process running as a console or a process running in a cmd.exe prompt.
; Syntax ........: _ConsoleSendCtrlC($PID, $iMode = 0, $iDll = "kernel32.dll")
; Parameters.....: hWnd        - Process ID of a ConsoleWindowClass window
;                 $iEvent    - 0 = Send CTRL_C_EVENT=0
;                 $iEvent    - 1 = Send CTRL_BREAK_EVENT=1
;                 $iMode    - 0 = No Callback required - Block CTRL_C_EVENT only for calling process
;                 $iMode    - 1 = Callback required - Block CTRL_C_EVENT and CTRL_BREAK_EVENT for calling process
;                 $iLoop    - Number of times to send Ctrl-C/Break event (Run in loop with ProcessExists check and 10ms Sleep)
;                 $iSlp    - Sleep value - Default 10ms
;                 $iDll    - Optional handle from DllOpen()
; Return values..: Succcess - 1
;                 Failure - -1 and sets @error
; Author.........: rover 2k12
; Remarks .......: Allows cancelled process to do cleanup before exiting.
;                 Event handler blocking of Ctrl-C/Break prevents script exiting when GenerateConsoleCtrlEvent called on a shared process console
;
;                 Note: It is preferable to send a CTRL_C_EVENT ($iEvent = 0) without the callback ($iMode = 0). (callback is created in a thread)
;
;                 Note: Console window and process checking is untested and unfinished in this version (Return = -1 and Error = 2 indicates console window is unresponsive)
;                 Script crash has occurred when sending Ctrl+C in a tight loop to an unresponsive process
;                 Test console window or process reponsiveness and send Ctrl+C one or more times with long sleep then if still unreponsive use ProcessClose.
;
; Related .......: _ConsoleEventHandler
; Example .......: Yes
;
; Optional: Callback Event Handler Function (for $iMode = 1 only)
; Func _ConsoleEventHandler()
;       Return 1 ; Ctrl-C/Break exiting blocked
; EndFunc   ;==>_ConsoleEventHandler
;
; ===============================================================================================================================
Func _ConsoleSendCtrlC($PID, $iEvent = 0, $iMode = 0, $iLoop = 1, $iSlp = 10, $iDll = "kernel32.dll")
    ;Author: rover 2k12
    Local $aRet = DllCall($iDll, "bool", "AttachConsole", "dword", $PID)
    If @error Or $aRet[0] = 0 Then Return SetError(1, 0, -1)
    ;sets GetLastError to ERROR_GEN_FAILURE (31) if process does not exist
    ;sets GetLastError to ERROR_ACCESS_DENIED (5) if already attached to a console
    ;sets GetLastError to ERROR_INVALID_HANDLE (6) if process does not have a console

    $aRet = DllCall("Kernel32.dll", "hwnd", "GetConsoleWindow") ;must be attached to console to get hWnd
    If Not @error And IsHWnd($aRet[0]) Then
        $aRet = DllCall('user32.dll', 'int', 'IsHungAppWindow', 'hwnd', $aRet[0]);from Yashieds' WinAPIEx.au3
        If Not @error And $aRet[0] Then
            DllCall($iDll, "bool", "FreeConsole")
            Return SetError(2, 0, -1)
        EndIf
    Else
        Return SetError(3, 0, -1)
    EndIf

    Local $iCTRL = 0, $iRet = 1, $pCB = 0, $iCB = 0, $iErr = 0, $iCnt = 0
    ;check params
    $iLoop = Int($iLoop)
    If $iLoop < 1 Then $iLoop = 1
    If $iEvent Then $iCTRL = 1
    If $iSlp < 10 Then $iSlp = 10

    If $iMode Then
        Local $sCB = "_ConsoleEventHandler"
        Call($sCB)
        If @error <> 0xDEAD And @extended <> 0xBEEF Then
            Local $iCB = DllCallbackRegister($sCB, "long", "")
            If Not $iCB Then
                DllCall($iDll, "bool", "FreeConsole")
                Return SetError(4, 0, -1)
            EndIf
            $pCB = DllCallbackGetPtr($iCB)
        Else
            $iCTRL = 0
        EndIf
    EndIf

    $aRet = DllCall($iDll, "bool", "SetConsoleCtrlHandler", "ptr", $pCB, "bool", 1)
    If @error Or $aRet[0] = 0 Then
        If $iMode Then DllCallbackFree($iCB)
        DllCall($iDll, "bool", "FreeConsole")
        Return SetError(5, @error, -1)
    EndIf

    ;send ctrl-c, free console and unregister event handler
    Do
        $aRet = DllCall($iDll, "bool", "GenerateConsoleCtrlEvent", "dword", $iCTRL, "dword", 0)
        If @error Or $aRet[0] = 0 Then $iRet = -1
        $iCnt += 1
        Sleep($iSlp)
    Until $iCnt = $iLoop Or ProcessExists($PID) = 0

    $aRet = DllCall($iDll, "bool", "FreeConsole")
    If Not @error Then $iErr = $aRet[0]
    If $iMode Then Sleep(50) ;10ms o.k, but margin for error better (DllCallbackFree can crash script if called too soon after console freed when using callback)
    $aRet = DllCall($iDll, "bool", "SetConsoleCtrlHandler", "ptr", 0, "bool", 0)
    If $iMode Then DllCallbackFree($iCB)
    Return SetError(@error, $iErr, $iRet)
EndFunc   ;==>_ConsoleSendCtrlC


Func _ConsoleEventHandler() ;single dword param $dwCtrlType in callback can sometimes cause - Error: Variable used without being declared. (event handler is run in a new thread)
    ;Optional: Callback Event Handler Function (for $iMode = 1 only)
    Return 1 ; Ctrl-C/Break exiting blocked
EndFunc   ;==>_ConsoleEventHandler
Edited by rover

I see fascists...

Share this post


Link to post
Share on other sites

thank you very much.

From you post above i am a bit irritated:

you say that

>The event handler can only reliably block Ctrl-C/Break (Blocking exit only works (unreliably) in XP and is no longer supported as of Vista - see MSDN)

and as my script ist for Vista or newer this will not work, won't it?

So i will have to stay with the external app?!

I will try it out as soon as i can...

Share this post


Link to post
Share on other sites

When i use it it just works fine, but i use a loop to call it more than once to make sure the cmd windows gets it.

$counter = 0
  While ProcessExists($enginepid) And $counter < 10
   _ConsoleSendCtrlC($enginepid)
   ;Run(@ScriptDir & "CCBSendSignal.exe " & $enginepid, @ScriptDir, @SW_HIDE)
   If Not ProcessExists($enginepid) Then
    ExitLoop
   Else
    $counter = $counter + 1
    Sleep(1000)
   EndIf
  WEnd

when it is called a few time the exe script crashs with a message

Error: Variable used without being declared.

i can not figure out what var this is, but it must have to do with calling the function several times and also with the fact that the cmd window is closing while the func is beeing called...

So probaly some more errorchecking un the func is needed...

Edited by Allow2010

Share this post


Link to post
Share on other sites

thank you very much.

From you post above i am a bit irritated:

you say that

>The event handler can only reliably block Ctrl-C/Break (Blocking exit only works (unreliably) in XP and is no longer supported as of Vista - see MSDN)

and as my script ist for Vista or newer this will not work, won't it?

So i will have to stay with the external app?!

I will try it out as soon as i can...

Only the CTRL_CLOSE_EVENT can no longer be blocked, the UDF does not use it.

Ctrl-C/Break is blocked


I see fascists...

Share this post


Link to post
Share on other sites

When i use it it just works fine, but i use a loop to call it more than once to make sure the cmd windows gets it.

$counter = 0
  While ProcessExists($enginepid) And $counter < 10
   _ConsoleSendCtrlC($enginepid)
   ;Run(@ScriptDir & "CCBSendSignal.exe " & $enginepid, @ScriptDir, @SW_HIDE)
   If Not ProcessExists($enginepid) Then
    ExitLoop
   Else
    $counter = $counter + 1
    Sleep(1000)
   EndIf
  WEnd

when it is called a few time the exe script crashs with a message

Error: Variable used without being declared.

i can not figure out what var this is, but it must have to do with calling the function several times and also with the fact that the cmd window is closing while the func is beeing called...

So probaly some more errorchecking un the func is needed...

I think know what causes that error message, the event handler may need ByRef on that variable.

It may be a better idea to have an option in the UDF to loop a set number of times and optionally check if the process exists

(I left that out, as a process launched with run or a console app you can check, but a cmd.exe window still exists after the process it was running is closed)

give me a few minutes and I'll update the udf


I see fascists...

Share this post


Link to post
Share on other sites

thanks...

maybe it is wise to mention, that the app i want to close does not react if i send ctrl+c too early, so i decided to loop up to 10 times/seconds and if the process still exists i kill it with process close...so i can try to shut down nicely, but if all else fails i kill it...

Edited by Allow2010

Share this post


Link to post
Share on other sites

thanks...

maybe it is wise to mention, that the app i want to close does not react if i send ctrl+c too early, so i decided to loop up to 10 times/seconds and if the process still exists i kill it with process close...so i can try to shut down nicely, but if all else fails i kill it...

O.K. the udf is updated with an option to loop with a processexists check and the issue with the event handler thread undeclared var error is fixed

(perhaps a sleep value param should be added?)

You jumped to a conclusion regarding the event handler.

Might I give you a friendly reminder that everyone here volunteers their free time and code.

I do consider OS version issues as much as I can.

try the update, it should fix the issue with undeclared var error

I'm off for a morning walk, will check in later.

be seeing you...

Edited by rover

I see fascists...

Share this post


Link to post
Share on other sites

You jumped to a conclusion regarding the event handler.

Might I give you a friendly reminder that everyone here volunteers their free time and code.

I do consider OS version issues as much as I can.

i think we have a missunderstanding here (maybe because my english is no perfect)...

I know and value very much everyones help here and in no way was my intention to criticise your work. Sorry if it sounded like this.

I just do not understand if i can/should use your solution (UDF) in an app that is trageted for Win Vista or higher or if it is unwise to do so...

Maybe if it really is problematic one might consider to exit the function with an error code when run on not supported OS and/or some info in the header of the function.

Thank you very much for the update....yes a sleep value parameter would be nice, and when done, please consider to post it to the example scripts forum and add the url to it to the header for everyone else, as i am sure other will need this...

Thanks again !

Share this post


Link to post
Share on other sites

it still crashes inside the do...until loop

This only happends when i try to shutdown the console app while it is in a state where it does not react to ctrl+c, so it takes several "GenerateConsoleCtrlEvent"

I believe it happends when the process already stopped and it tries to do

$aRet = DllCall($iDll, "bool", "GenerateConsoleCtrlEvent", "dword", $iMode, "dword", 0)

Edit: no it can not be caused by an no longer running process. The process sometimes still runs after the crash...

Edited by Allow2010

Share this post


Link to post
Share on other sites

Perhaps dissapointed would have been a better choice.

anyway, nevermind.

I've now tested this UDF on Win 7 x64 as well as XP x86 (the earlier code was tested on both, not that there is anything in the code that would be an OS issue)

If you are concerned about OS compatibility, there is nothing used here that wont work in XP, Vista, 2003, 2008, 2008R2 and Win 7, x86 or x64bit

These are the relevant MSDN pages for:

SetConsoleCtrlHandler Function

http://msdn.microsoft.com/en-us/library/windows/desktop/ms686016(v=vs.85).aspx

Registering a Control Handler Function

http://msdn.microsoft.com/en-us/library/windows/desktop/ms685049(v=vs.85).aspx

Can you tell me what the problematic program is and does, or give me a download link?

Regarding the crashing with that process, there are a few things to try:

1 - Get return, error and extended from _ConsoleSendCtrlC() and add to a msgbox, traytip, tooltip etc.

Add _WinAPI_GetLastErrorMessage() after _ConsoleSendCtrlC and display in a msgbox, traytip, tooltip etc.

2 - Just for testing purposes, try commenting out DllCallbackFree($iCB), We can see if it crashes without DllCallbackFree.

3 - Comment out the open process and getexitcode line.

4 - Increase the sleep (modify the 10ms sleep in the udf Do loop) time between calls to GenerateConsoleCtrlEvent, and limit the number of attempts to cancel before resorting to ProcessClose.

(I will add a sleep param to the UDF)

5 - Maybe something like IsAppHung or some other API that can check the unresponsiveness of a process before bombarding it with Ctrl-C,

then based on that, try a reasonable number of GenerateConsoleCtrlEvent calls, then if that doesn't work launch the external exe, then last resort ProcessClose.

This is certainly a good worst case example to test with.


I see fascists...

Share this post


Link to post
Share on other sites

After a review of the MSDN page for SetConsoleCtrlHandler, I've updated the UDF in post #24 and added an optional mode that eliminates the requirement for a callback event handler (CTRL_C_EVENT only)

Let's see if this eliminates the crashing with an unresponsive console.

I'll see what other options there are for testing an unresponsive console/cmd window/process.

Does anyone have a suggestion?


I see fascists...

Share this post


Link to post
Share on other sites

Thanks for all your work!

i did a quick try with the new version using

_ConsoleSendCtrlC($enginepid, 0, 20,1000)

and it still crashes...sorry

i will now prepare a minimal test example and will send it to you...

Edit: oh no, did not see your change...

call must be

_ConsoleSendCtrlC($enginepid, 0, 0, 20,1000)

testing again...

Edited by Allow2010

Share this post


Link to post
Share on other sites

juhuu.-)

It no longer crashes:-))

there is just one small issue, which probably can not be solved:

the Obfuscater does not like Call() and throws warnings and errors...

i have to comment out the block

If $iMode Then
        Local $sCB = "_ConsoleEventHandler"
        Call($sCB)
        If @error <> 0xDEAD And @extended <> 0xBEEF Then
            Local $iCB = DllCallbackRegister($sCB, "long", "")
            If Not $iCB Then
                DllCall($iDll, "bool", "FreeConsole")
                Return SetError(4, 0, -1)
            EndIf
            $pCB = DllCallbackGetPtr($iCB)
        Else
            $iCTRL = 0
        EndIf
    EndIf

This is no problem for me as i do not need it. But if the usage of call() can be prevented, it would be nice...

Thank you so much for your help....i am sure i no longer need the external exe...

Edited by Allow2010

Share this post


Link to post
Share on other sites

Good to hear it finally stopped crashing.

Eliminating the event handler callback (in a new thread) was the solution.

How long did it have to loop before the process finally closed?

I've removed the hung app check as I don't know if that would even work on a console, it was just an experiment.

The Call() check for the event handler is removed.

The callback is required for Ctrl-Break blocking, but I don't know if there is any usage where Ctrl-Break would be useful.

Try this version and call it with a DllOpen(kernel32.dll) handle

Let me know how many times you had to loop (place a MsgBox with $iCnt after the loop exits)

Try sleep lower than 1000ms

Edit: a few modifications, added link

; #FUNCTION# ====================================================================================================================
; Name ..........: _ConsoleSendCtrlC
; Description....: Send Ctrl+C (SIGINT) to cancel a process running as a console or a process running in a cmd.exe prompt
;                 Allows cancelled process to do cleanup before exiting
; Syntax ........: _ConsoleSendCtrlC($PID, $iLoop = 1, $iSlp = 10, $iDll = "kernel32.dll")
; Parameters.....: $PID     - Process ID of a console or ConsoleWindowClass window
;                 $iLoop    - Number of times to send CTRL_C_EVENT     - Default = 1 (Run in loop with ProcessExists check and 10ms Sleep)
;                 $iSlp - Delay time in ms between each CTRL_C_EVENT - Default = 10ms
;                 $iDll - Optional handle from DllOpen() - For performance reasons use a dll handle even if not using loop values >1
; Return values..: Succcess - 1
;                 Failure - -1 and sets @error
; Author.........: rover 2k12
; Remarks .......: Blocking of Ctrl+C prevents script exiting when GenerateConsoleCtrlEvent called on a shared console process
;                 Note: Only blocks CTRL_C_EVENT, will not block CTRL_BREAK_EVENT
;                 If console unresponsive with default params, set $iLoop >1 and $iSlp >=10 <=1000 (If still unresponsive use ProcessClose)
; MSDN ..........: SetConsoleCtrlHandler can also enable an inheritable attribute that causes the calling process to ignore CTRL+C signals.
;                 If GenerateConsoleCtrlEvent sends a CTRL+C signal to a process for which this attribute is enabled, the handler functions for that process are not called.
;                 CTRL+BREAK signals always cause the handler functions to be called.
; Example .......: Yes
; [http://www.autoitscript.com/forum/topic/138191-end-dos-process-with-ctrlc-from-script-help-needed/page__view__findpost__p__970554]
; ===============================================================================================================================
Func _ConsoleSendCtrlC($PID, $iLoop = 1, $iSlp = 10, $iDll = "kernel32.dll")
    ;Author: rover 2k12
    Local $aRet = DllCall($iDll, "bool", "AttachConsole", "dword", $PID)
    If @error Or $aRet[0] = 0 Then Return SetError(1, 0, -1)
    Local $iRet = 1, $iExt = -1, $iCnt = 0
    ;check params
    $iLoop = Int($iLoop)
    If $iLoop < 1 Then $iLoop = 1
    $iSlp = Int($iSlp)
    If $iSlp < 10 Then $iSlp = 10
    $aRet = DllCall($iDll, "bool", "SetConsoleCtrlHandler", "ptr", 0, "bool", 1)
    If @error Or $aRet[0] = 0 Then
        DllCall($iDll, "bool", "FreeConsole")
        Return SetError(2, 0, -1)
    EndIf
    ;Send SIGINT, free console and reset Ctrl+C blocking
    Do
        $aRet = DllCall($iDll, "bool", "GenerateConsoleCtrlEvent", "dword", 0, "dword", 0)
        If @error Or $aRet[0] = 0 Then $iRet = -1
        $iCnt += 1
        Sleep($iSlp)
    Until $iCnt = $iLoop Or ProcessExists($PID) = 0
    $aRet = DllCall($iDll, "bool", "FreeConsole")
    If Not @error Then $iExt = $aRet[0]
    $aRet = DllCall($iDll, "bool", "SetConsoleCtrlHandler", "ptr", 0, "bool", 0)
    If @error Or $aRet[0] = 0 Then Return SetError(3, $iExt, $iRet)
    Return SetError(4, $iExt, $iRet)
EndFunc   ;==>_ConsoleSendCtrlC
Edited by rover

I see fascists...

Share this post


Link to post
Share on other sites

Thanks, i tested it

it looped 7 times with a deley of 1000

i laso tried a delay of 10 and it looped 500+ times and still everything works fine...

I then used dllopen and dllclose and it also worked fine...

Compiling with obfuscator is no longer a problem.

Thanks !!

Share this post


Link to post
Share on other sites

Thanks, i tested it

it looped 7 times with a deley of 1000

i laso tried a delay of 10 and it looped 500+ times and still everything works fine...

I then used dllopen and dllclose and it also worked fine...

Compiling with obfuscator is no longer a problem.

Thanks !!

As a native AutoIt code solution that sounds good, however...

Earlier on, (in XP) I tested TaskKill /PID from one cmd prompt to interrupt another cmd.exe and got an access denied error message - said to use /f force kill switch

so I discounted using that, though it did interrupt a process launched by Run() from a script, but wouldn't interrupt an external process. (I don't recall what I did differently at the time)

On further investigation, it looks like taskkill might work after all.

(some differences though, taskkill interrupts the process in a cmd.exe prompt but also closes it)

Run("TaskKill /PID " & $Pid, @ScriptDir, @SW_HIDE)

See if TaskKill works for you

Oh well, at least the udf is a native autoit code solution...

The difference is, on a cmd.exe prompt, taskkill interrupts the process (7-Zip deleted the temp file) but the cmd prompt also closed.

_ConsoleSendCtrlC only interrupted the process, cmd.exe remained open.


I see fascists...

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  

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...