Jump to content

Sending control characters to DOS command started with script


gritts
 Share

Recommended Posts

I have been wracking my brains today trying to figure out how to send a control character to a DOS command I initiate within a script. I am doing this little project to eventually add a GUI to some older DOS based apps still in use. (Not everyone appreciates the fine art of command line execution I guess  :P ) Anywho, to test my scripting and what I was able to dig up, I came up with the following. (Yes, I know AutoIT has a ping function, DOS ping was what I came up with to test sending commands to, it is not the final program)

#cs ----------------------------------------------------------------------------

 AutoIt Version: 3.3.12.0
 Author:         myName

 Script Function:
    Template AutoIt script.

#ce ----------------------------------------------------------------------------

; Script Start - Add your code below here


#include <ButtonConstants.au3>
#include <EditConstants.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <Constants.au3>
#include <process.au3>
Opt("GUIOnEventMode", 1)
#Region ### START Koda GUI section ###
$Main = GUICreate("Ping Now", 547, 62, 192, 124)
GUISetFont(10, 400, 0, "MS Sans Serif")
GUISetOnEvent($GUI_EVENT_CLOSE, "MainClose")
GUISetOnEvent($GUI_EVENT_MINIMIZE, "MainMinimize")
GUISetOnEvent($GUI_EVENT_MAXIMIZE, "MainMaximize")
GUISetOnEvent($GUI_EVENT_RESTORE, "MainRestore")
$tbPingURL = GUICtrlCreateInput("", 16, 16, 425, 24)
GUICtrlSetOnEvent($tbPingURL, "tbPingURLChange")
$btnPingStart = GUICtrlCreateButton("Ping Now", 456, 16, 75, 25)
GUICtrlSetOnEvent($btnPingStart, "btnPingStartClick")
GUISetState(@SW_SHOW)
#EndRegion ### END Koda GUI section ###
Global $srPID

While 1
    Sleep(100)
WEnd

Func btnPingStartClick()
    Local $sURL = GUICtrlRead($tbpingURL),$stdOutLine
    MsgBox(0,"Ping URL","Reading URL: "&$sURL) ;ignored as I got tired of typing
    $srPID = Run(@ComSpec&" /c "&"ping -t yahoo.com","",@SW_HIDE,$STDIN_CHILD + $STDOUT_CHILD)
    ConsoleWrite("Running with PID: "&$srPID&@CRLF)
Sleep(1000)

EndFunc
Func MainClose()
    ;ControlSend(@ComSpec,"","",Chr(3),1)
    ;ConsoleWrite(_ProcessGetName($srPID))
    $charout=StdinWrite($srPID,Chr(3))
    ConsoleWrite($charout&"  -  "&@error&@CRLF)
    StdinWrite($srPID)
    ;ProcessWait($srPID)
    ProcessWaitClose($srPID)
    Exit
EndFunc
Func MainMaximize()

EndFunc
Func MainMinimize()

EndFunc
Func MainRestore()

EndFunc
Func tbPingURLChange()

EndFunc

When I close the resulting window, I am expecting a CTRL+c to be sent to the running Ping command. Instead, I get a result code back implying that the command was send but nothing happens after that. Ping continues to run in the background till I end its process through Task Manager. When I end the process, the script closes, provided I attempt to close the GUI window (Func MainClose()). If I do not try to close the GUI first and end the ping process through task manager, I get a '0' as the result code for StdInWrite.

The PID returned is NOT that of the child process initiated by the Run command but that of the cmd.exe that is started to run the Ping command within. Leaving off the @comspec portion of the Run statement makes no difference, the ping continues to run till ended. 

Any suggestions? I noted that other queries about sending control keys to DOS commands have been posted in the past but I didn't really pick up on a solution.

Link to comment
Share on other sites

Well, I'm not sure to really understand what is your problem...

Does this code helps you ?

#cs ----------------------------------------------------------------------------

 AutoIt Version: 3.3.12.0
 Author:         myName

 Script Function:
    Template AutoIt script.

#ce ----------------------------------------------------------------------------

; Script Start - Add your code below here


#include <ButtonConstants.au3>
#include <EditConstants.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <Constants.au3>
#include <process.au3>
Opt("GUIOnEventMode", 1)
#Region ### START Koda GUI section ###
$Main = GUICreate("Ping Now", 547, 62, 192, 124)
GUISetFont(10, 400, 0, "MS Sans Serif")
GUISetOnEvent($GUI_EVENT_CLOSE, "MainClose")
GUISetOnEvent($GUI_EVENT_MINIMIZE, "MainMinimize")
GUISetOnEvent($GUI_EVENT_MAXIMIZE, "MainMaximize")
GUISetOnEvent($GUI_EVENT_RESTORE, "MainRestore")
$tbPingURL = GUICtrlCreateInput("", 16, 16, 425, 24)
GUICtrlSetOnEvent($tbPingURL, "tbPingURLChange")
$btnPingStart = GUICtrlCreateButton("Ping Now", 456, 16, 75, 25)
GUICtrlSetOnEvent($btnPingStart, "btnPingStartClick")
GUISetState(@SW_SHOW)
#EndRegion ### END Koda GUI section ###
Global $srPID

While 1
    Sleep(100)
WEnd

Func btnPingStartClick()
    Local $sURL = GUICtrlRead($tbpingURL),$stdOutLine
    MsgBox(0,"Ping URL","Reading URL: "&$sURL) ;ignored as I got tired of typing
    $srPID = Run(@ComSpec&" /c ping -t google.com","","",$STDIN_CHILD + $STDOUT_CHILD)
    ConsoleWrite("Running with PID: "&$srPID&@CRLF)
EndFunc


Func MainClose()
    ProcessClose($srPID)
    
    $sOutput = StdoutRead($srPID)
    ConsoleWrite($sOutput)
    
    Exit
EndFunc

Func MainMaximize()

EndFunc
Func MainMinimize()

EndFunc
Func MainRestore()

EndFunc
Func tbPingURLChange()

EndFunc
Link to comment
Share on other sites

The problem is that Ping with the "-t" parm pings the target continuously until a CTRL+C is received by the command window.  It seems that "normal" methods do not work on the command window.  Do a Google search for lot's of info on it.

Forum Rules         Procedure for posting code

"I like pigs.  Dogs look up to us.  Cats look down on us.  Pigs treat us as equals."

- Sir Winston Churchill

Link to comment
Share on other sites

@jguinch - Your approach does work and I can use it. I had tested it in a version of my script but I am trying to exhaust my options first before I just kill the process. The process I intend to use this with likes to be closed with a CTRL+c but it can be killed. It closes connections and files when it receives the CTRL+c. I used ping as it seemed the best proxy for the actual programs. I hope to use this method against a proprietary app in our office as a means of creating an eventual front-end of sorts. 

@kylomas - I did reseach Google for a while and after feeling like I was running in circles I came to the same conclusion. I had read here that others were attempting the same if not similar things I was attempting (CTRL+c to a console window opened with the "Run" command) but did not see where there had been a resolution. (Unless my search criteria never returned the results)

It almost sounds like sending the CTRL+c is not possible. I'll keep plugging away at it... 

Link to comment
Share on other sites

  • 4 months later...

@jguinch - Your approach does work and I can use it. I had tested it in a version of my script but I am trying to exhaust my options first before I just kill the process. The process I intend to use this with likes to be closed with a CTRL+c but it can be killed. It closes connections and files when it receives the CTRL+c. I used ping as it seemed the best proxy for the actual programs. I hope to use this method against a proprietary app in our office as a means of creating an eventual front-end of sorts. 

@kylomas - I did reseach Google for a while and after feeling like I was running in circles I came to the same conclusion. I had read here that others were attempting the same if not similar things I was attempting (CTRL+c to a console window opened with the "Run" command) but did not see where there had been a resolution. (Unless my search criteria never returned the results)

It almost sounds like sending the CTRL+c is not possible. I'll keep plugging away at it... 

 

Any luck? I've searched for the past hour, Writing Chr(3) to StdIn nor GenerateConsoleCtrlEvent work.

Googling this, it seems to be a widespread "feature" of Windows - rather than an AutoIt bug. See here for more (albeit non-AutoIt) discussion on this.

Edited by JonusC
Link to comment
Share on other sites

Sorry for double post, but it's worthy of a notification to all subscribed. I found a solution to this but with one caveat - it only works when the script is compiled.

GenerateConsoleCtrlEvent works, but the console needs to be "attached" first. Example below, all credits to rover 2k12.

EDIT: It also doesn't work if it's run by SciTE/Wrapper "#AutoIt3Wrapper_Run_After="%out%"" directive... so it's a very last-resort thing. But still better than doing a "kill process" IMO.

;coded by rover 2k12

;Ref:
;<a href='http://stackoverflow.com/questions/813086/can-i-send-a-ctrl-c-sigint-to-an-application-on-windows' class='bbc_url' title='External link' rel='nofollow external'>http://stackoverflow.com/questions/813086/can-i-send-a-ctrl-c-sigint-to-an-application-on-windows</a>

#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 JonusC
Link to comment
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
 Share

×
×
  • Create New...