Jump to content
Sign in to follow this  
Allow2010

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

Recommended Posts

Allow2010

hi all,

i run a dos process from my script and capture its output like this.

Func _RunRead($doscmd, $workingdir = @ScriptDir, $flag = @SW_HIDE)
$retval = -2
$stdflag = 2
$i_Pid = Run($doscmd, $workingdir, $flag, $stdflag)
; Get process handle
Sleep(100) ; or DllCall may fail - experimental
$h_Process = DllCall('kernel32.dll', 'ptr', 'OpenProcess', 'int', 0x400, 'int', 0, 'int', $i_Pid)
$sStdOut = ""
$chkstdout = 1
Do
  If $chkstdout = 0 Then ExitLoop
  If $chkstdout = 1 Then
   $sStdOut = $sStdOut & StdoutRead($i_Pid)
   If @error Then $chkstdout = 0
   $stdoutlines = StringSplit($sStdOut, @CR, 1)
   $counter = 1
   For $counter = 1 To $stdoutlines[0] - 1
    ; write to logfile here
    ;Logfile(CleanDSLogtext($stdoutlines[$counter]))
   Next
   If StringInStr($stdoutlines[$stdoutlines[0]], @CR) Then
    ; write to logfile here
    ;Logfile(CleanDSLogtext($stdoutlines[0]))
    $sStdOut = ""
   Else
    $sStdOut = $stdoutlines[$stdoutlines[0]]
   EndIf
  EndIf
  Sleep(1000)
Until Not ProcessExists($i_Pid)
; fetch exit code and close process handle
If IsArray($h_Process) Then
  Sleep(100) ; or DllCall may fail - experimental
  $i_ExitCode = DllCall('kernel32.dll', 'ptr', 'GetExitCodeProcess', 'ptr', $h_Process[0], 'int*', 0)
  If IsArray($i_ExitCode) Then
   $retval = $i_ExitCode[2]
  Else
   $retval = -1
  EndIf
  Sleep(100) ; or DllCall may fail - experimental
  DllCall('kernel32.dll', 'ptr', 'CloseHandle', 'ptr', $h_Process[0])
EndIf
Return $retval
EndFunc   ;==>_RunRead

sometimes it is neccessary to stop the process by sending ctrl+c (and not by processclose() as the process will do some cleanup when closed by ctrl+c) from another script.

Is there a way i can send ctrl-c to this running process from a second script? As it is usally run hidden, there is no window to send keystrokes to...

any ideas are welcome...

Thanks!

Edited by Allow2010

Share this post


Link to post
Share on other sites
wakillon

Try to use WinClose for the ConsoleWindowClass window of your hidden dos command.

Get his handle with WinGetHandle ( "CLASS:ConsoleWindowClass]" )

:oops:


AutoIt 3.3.14.2 X86 - SciTE 3.6.0WIN 8.1 X64 - Other Example Scripts

Share this post


Link to post
Share on other sites
Allow2010

thanks for the idea, i will try this....

but is there a way to make sure i kill the right process? I have the PID, can this be helpful?

Share this post


Link to post
Share on other sites
wakillon

thanks for the idea, i will try this....

but is there a way to make sure i kill the right process? I have the PID, can this be helpful?

List all windows with WinList ( 'CLASS:ConsoleWindowClass]' )

If there is several windows of this classe, use WinGetProcess function.

And it doesn't kill the process, it close his window ! :oops:


AutoIt 3.3.14.2 X86 - SciTE 3.6.0WIN 8.1 X64 - Other Example Scripts

Share this post


Link to post
Share on other sites
Allow2010

Thanks for the info...i tried it but it does not work they way i need it to work.

When i press ctrl+c inside the dos window the command closes fine and does some cleanup, when i do this by WinClose, the command also closes, but does not cleanup, so sending ctrl+c is different from using WinClose.

Any othe idea how i can send ctrl+c to a hidden dos window?

Share this post


Link to post
Share on other sites
LoWang

Hello, I also would really like this to be figured out. I found a lot of other questions regarding this problem in other programming languages too. It has also been and it looks like a really juicy problem :-)

Edited by LoWang

Share this post


Link to post
Share on other sites
Allow2010

link is broken...

Share this post


Link to post
Share on other sites
LoWang

Fixed the link...

Also I wonder if maybe using windows command taskkill would not do the same thing as pressing ctrl+c in the application window? Maybe the signal is the same if I ommit /f parameter...

Share this post


Link to post
Share on other sites
ZacUSNYR

You should check out the Console UDF by Mat

http://matdiesel.co.uk/autoit/

_Console_GenerateConsoleCtrlEvent

Haven't messed around with it much myself.

Share this post


Link to post
Share on other sites
Allow2010

ahh, this look interesting...thank you !!!

Share this post


Link to post
Share on other sites
Allow2010

hmmm

it comes down to this:

DllCall("kernel32.dll", "bool", "GenerateConsoleCtrlEvent", "dword", 0, "dword", $processgroup)

the problem is i can not figure out what $processgroup is:-)

I tried the pid and also the window handle, but it did nothing....

can anyone give an example on how to use _Console_GenerateCtrlEvent ? I could not get it to do anything....

Any help is really apreciated !

Edited by Allow2010

Share this post


Link to post
Share on other sites
LoWang

You should check out the Console UDF by Mat http://matdiesel.co.uk/autoit/ _Console_GenerateConsoleCtrlEvent Haven't messed around with it much myself.

Unfortunatelly this looks to be intended for different use. Not to read or write something to another process STDOUT or STDIN...

Share this post


Link to post
Share on other sites
ZacUSNYR

You need to alloc a console, then attach a process to that console. The Ctrl-C send works sending to that console. In the UDF he wrote you can interact with input/output via the UDF (the standard auto it functions don't interact with the console window).

I was able to successfully start a ping -t and send a ctrl c via _Console_GenerateCtrlEvent("CTRL_C_EVENT")

It appears to only works when the script is compiled and not via 'Go' Console window.

A way I did this for an app I wrote as well. 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). Capture the "Control-Break" text and taskkill it. Not a clean exit.

Share this post


Link to post
Share on other sites
Allow2010

>I was able to successfully start a ping -t and send a ctrl c via _Console_GenerateCtrlEvent("CTRL_C_EVENT")

could you post this as an example?

Edit: SendSignal.exe works fine (i can call it with the pid, that is great), thank you for finding this...

I still would love to get this done in autoit without an external app...

EDIT:

>I was able to successfully start a ping -t and send a ctrl c via _Console_GenerateCtrlEvent("CTRL_C_EVENT")

It seems to me that this is wrong, as the console.au3 defines

Global Const $CTRL_C_EVENT = 0

Global Const $CTRL_BREAK_EVENT = 1

so you should have used

_Console_GenerateCtrlEvent($CTRL_C_EVENT)

which is equivalent to

_Console_GenerateCtrlEvent(0)

or am i getting something wrong here?

Edited by Allow2010

Share this post


Link to post
Share on other sites
rover

You can put it all in one script if you use a console event handler with an attached console.

The script does not close when Ctrl-C is sent to the shared console of the parent/child processes.

Edit: removed AttachConsole

Edit: added code for sending Ctrl-C to process not opened by script

Edit: replaced WinList with GetConsoleWindow

Use the popup option to copy the script, Expand or drag selecting the text screws up the @Tabs

;coded by rover 2k12

;Ref:
;http://stackoverflow.com/questions/813086/can-i-send-a-ctrl-c-sigint-to-an-application-on-windows

#NoTrayIcon
;#AutoIt3Wrapper_Change2CUI=y
If Not @Compiled Then Exit

#include <windowsconstants.au3>
Opt("MustDeclareVars", 1)

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()

Local $iExitCode = Cmd("cmd.exe /c dir c:\ /s", $iMemo, $cCtrl_C)
MsgBox(262144, "Exit Code", $iExitCode & @TAB & @TAB & @TAB)

GUICtrlSetData($iMemo, "")
$iExitCode = Cmd("ping.exe -t " & @IPAddress1, $iMemo, $cCtrl_C)
MsgBox(262144, "Exit Code", $iExitCode & @TAB & @TAB & @TAB)

;Local $Archiv = @TempDir &amp; "\" &amp; @HOUR &amp; "_" &amp; @MIN &amp; "_" &amp; @SEC &amp; "_test.zip"
;GUICtrlSetData($iMemo, "")
;$iExitCode = Cmd(@ProgramFilesDir &amp; "\7-Zip\7z a -tzip "&amp;$Archiv&amp;" "&amp;@TempDir &amp; "\*.*", $iMemo, $cCtrl_C)
;MsgBox(262144, "Exit Code", $iExitCode&amp;@TAB&amp;@TAB&amp;@TAB)

;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
MsgBox(262144, "Open CMD prompt", "open a single cmd prompt with ping.exe -t yourIP, then continue")
Local $aCon = WinList("[CLASS:ConsoleWindowClass]"), $PID
For $i = 0 To $aCon[0][0]
  $PID = WinGetProcess($aCon[$i][1])
  If $PID <> @AutoItPID Then DllCall("kernel32.dll", "bool", "AttachConsole", "dword", $PID)
Next
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 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, $retval = -1
While 1
  $sData = StdoutRead($iPid)
  If @error Then ExitLoop
  $sData = StringReplace($sData, @CR & @CR, @CR)
  If $sData Then GUICtrlSetData($cEdit, $sData & @CRLF, 1)
  #cs
   If $fCloseFlag Then
   ;continually send ctrl-c until child process exits (not all child processes will respond to Ctrl-C the first time)
   DllCall("kernel32.dll", "bool", "GenerateConsoleCtrlEvent", "dword", $CTRL_C_EVENT, "dword", 0)
   If ProcessExists($iPid) = 0 Then ExitLoop
   EndIf
  #ce
  Switch GUIGetMsg()
   Case -3
    ;ExitLoop
   Case $cBtn
    ;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
$fCloseFlag = False
If IsArray($h_Process) Then
  Local $i_ExitCode = DllCall('kernel32.dll', 'ptr', 'GetExitCodeProcess', 'ptr', $h_Process[0], 'int*', 0)
  If IsArray($i_ExitCode) Then $retval = $i_ExitCode[2]
  DllCall('kernel32.dll', 'ptr', 'CloseHandle', 'ptr', $h_Process[0])
EndIf
Return $retval
EndFunc   ;==>Cmd

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
Edited by rover

I see fascists...

Share this post


Link to post
Share on other sites
Allow2010

thanks, looks interesting...will try it...

Share this post


Link to post
Share on other sites
Allow2010

#include "console.au3"
$iPid = Run("ping.exe -t " & @IPAddress1)
Sleep(1000)
$h_Process = DllCall('kernel32.dll', 'ptr', 'OpenProcess', 'int', 0x400, 'int', 0, 'int', $iPid)
_Console_GenerateCtrlEvent($CTRL_C_EVENT,$h_Process[0])

From what i found i was thinking this should work, but it does not...anyone know why?

Edited by Allow2010

Share this post


Link to post
Share on other sites
rover

#include "console.au3"
$iPid = Run("ping.exe -t " & @IPAddress1)
Sleep(1000)
$h_Process = DllCall('kernel32.dll', 'ptr', 'OpenProcess', 'int', 0x400, 'int', 0, 'int', $iPid)
_Console_GenerateCtrlEvent($CTRL_C_EVENT,$h_Process[0])

From what i found i was thinking this should work, but it does not...anyone know why?

That 2nd param can only be used if you use _WinAPI_CreateProcess() with the CREATE_NEW_PROCESS_GROUP flag instead of Run()

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

dwProcessGroupId [in]

The identifier of the process group to receive the signal.

A process group is created when the CREATE_NEW_PROCESS_GROUP flag is specified in a call to the CreateProcess function.

The process identifier of the new process is also the process group identifier of a new process group.

The process group includes all processes that are descendants of the root process.

Only those processes in the group that share the same console as the calling process receive the signal.

In other words, if a process in the group creates a new console, that process does not receive the signal, nor do its descendants.

If this parameter is zero, the signal is generated in all processes that share the console of the calling process.

either compile as CUI or use AllocConsole

Edit: restored UDF name (conflicted with existing Console.au3)

If Not @Compiled Then Exit
#include "Console.au3"
_Console_Alloc()
$iPid = Run("ping.exe -t " & @IPAddress1)
Sleep(1000)
_Console_GenerateCtrlEvent($CTRL_C_EVENT, 0)
Do
Until Not Sleep(1000)

#AutoIt3Wrapper_Change2CUI=y
If Not @Compiled Then Exit
#include "Console.au3"
$iPid = Run("ping.exe -t " & @IPAddress1)
Sleep(1000)
_Console_GenerateCtrlEvent($CTRL_C_EVENT, 0)
Do
Until Not Sleep(1000)
Edited by rover

I see fascists...

Share this post


Link to post
Share on other sites
Allow2010

thanks a lot for explaining, but my problem still exist: i have to send ctrl-c to a process that was started by another autoit script, so i can not use the method above...:-(

Share this post


Link to post
Share on other sites
rover

thanks a lot for explaining, but my problem still exist: i have to send ctrl-c to a process that was started by another autoit script, so i can not use the method above...:-(

After much f* around with the malfunctioning editor and code tags,

I managed to add a fourth example I was about to upload when you posted.

It occured to me I hadn't included an example for another process

You may have to click the Ctrl-C button a 2nd time to close the program running in the console

Edited by rover

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  

×