Sign in to follow this  
Followers 0
danls

Named Pipes example - interactive commands possible?

3 posts in this topic

#1 ·  Posted (edited)

First post and first Autoit script. Gotta say I love this scripting language so far.

I am modifying the Named Pipes server & client examples that come with the latest AutoIt to run from the console rather than Gui. So far so good. But when I try sending a command from the client to the server that takes followup interaction, the command exits after it sends its first batch of output.

As a simple example, I am trying to get the named pipe server to run "cmd.exe dir /k". I want the command prompt to then continue running and taking input after it outputs the first results from "dir", hence using the /k flag instead of /c. However, it sends the output of "dir" back to the client and then the cmd.exe process exits. I cannot figure out why!

I'm not going to give the code for the entire server script since it's quite long, but here are the relevant functions for server.au3:

#include <Timers.au3>
#include <Inet.au3>
#include <FTPEx.au3>
#include <ScreenCapture.au3>
#include <NamedPipes.au3>
#include <WinAPI.au3>
#include <WindowsConstants.au3>
#include <GuiConstantsEx.au3>

; ===============================================================================================================================
; Global constants
; ===============================================================================================================================

Global Const $DEBUGGING = True
Global Const $BUFSIZE = 4096
Global Const $PIPE_NAME = ".pipeAutoIt3"
Global Const $TIMEOUT = 5000
Global Const $WAIT_TIMEOUT = 258
Global Const $ERROR_IO_PENDING = 997
Global Const $ERROR_PIPE_CONNECTED = 535

; ===============================================================================================================================
; Global variables
; ===============================================================================================================================

Global $hEvent, $iMemo, $pOverlap, $tOverlap, $hPipe, $hReadPipe, $iState, $iToWrite, $hProcess, $ProcessID

; ===============================================================================================================================
; This function loops waiting for a connection event or the GUI to close
; ===============================================================================================================================
Func MsgLoop()
    Local $iEvent

    Do
        $iEvent = _WinAPI_WaitForSingleObject($hEvent, 0)
        If $iEvent < 0 Then
            LogError("MsgLoop ...........: _WinAPI_WaitForSingleObject failed")
            Exit
        EndIf
        If $iEvent = $WAIT_TIMEOUT Then ContinueLoop
        Debug("MsgLoop ...........: Instance signaled")

        Switch $iState
            Case 0
                CheckConnect()
            Case 1
                ReadRequest()
         Case 2
                CheckPending()
         Case 3
             RelayOutput()
        EndSwitch
    Until GUIGetMsg() = $GUI_EVENT_CLOSE
EndFunc   ;==>MsgLoop

; ===============================================================================================================================
; This function reads a request message from the client
; ===============================================================================================================================
Func ReadRequest()
    Local $pBuffer, $tBuffer, $iRead, $bSuccess

    $tBuffer = DllStructCreate("char Text[" & $BUFSIZE & "]")
    $pBuffer = DllStructGetPtr($tBuffer)
    $bSuccess = _WinAPI_ReadFile($hPipe, $pBuffer, $BUFSIZE, $iRead, $pOverlap)

    If $bSuccess And ($iRead <> 0) Then
        ; The read operation completed successfully
        Debug("ReadRequest .......: Read success")
    Else
        ; Wait for read Buffer to complete
        If Not _WinAPI_GetOverlappedResult($hPipe, $pOverlap, $iRead, True) Then
            LogError("ReadRequest .......: _WinAPI_GetOverlappedResult failed")
            ReconnectClient()
            Return
        Else
            ; Read the command from the pipe
            $bSuccess = _WinAPI_ReadFile($hPipe, $pBuffer, $BUFSIZE, $iRead, $pOverlap)
            If Not $bSuccess Or ($iRead = 0) Then
                LogError("ReadRequest .......: _WinAPI_ReadFile failed")
             ReconnectClient()
                Return
            EndIf
        EndIf
    EndIf

    ; Execute the console command
    If Not ExecuteCmd(DllStructGetData($tBuffer, "Text")) Then
     ReconnectClient()
     Return
    EndIf

    ; Relay console output back to the client
    $iState = 3
EndFunc   ;==>ReadRequest

; ===============================================================================================================================
; This function relays the console output back to the client
; ===============================================================================================================================
Func RelayOutput()
    Local $pBuffer, $tBuffer, $sLine, $iRead, $bSuccess, $iWritten

    $tBuffer = DllStructCreate("char Text[" & $BUFSIZE & "]")
    $pBuffer = DllStructGetPtr($tBuffer)
    ; Read data from console pipe
    _WinAPI_ReadFile($hReadPipe, $pBuffer, $BUFSIZE, $iRead)

    ;No output read from process
    If $iRead = 0 Then
        ; First check if process still exists
        If ProcessExists($ProcessID) Then
            LogMsg("Process still exists")
            ; 1) continue interacting with command
        Else
            ; 2) close hReadPipe and request new command
            LogMsg("RelayOutput .......: Process " & $ProcessID & " !exists..Write done")
            _WinAPI_CloseHandle($hReadPipe)
            _WinAPI_FlushFileBuffers($hPipe)
            ReconnectClient()
            Return
        EndIf
    EndIf

    ; Get the data and strip out the extra carriage returns
    $sLine = StringLeft(DllStructGetData($tBuffer, "Text"), $iRead)
    $sLine = StringReplace($sLine, @CR & @CR, @CR)
    $iToWrite = StringLen($sLine)
    DllStructSetData($tBuffer, "Text", $sLine)
    ; Relay the data back to the client
    $bSuccess = _WinAPI_WriteFile($hPipe, $pBuffer, $iToWrite, $iWritten, $pOverlap)
    If $bSuccess And ($iWritten = $iToWrite) Then
        Debug("RelayOutput .......: Write success")
    Else
        If Not $bSuccess And (_WinAPI_GetLastError() = $ERROR_IO_PENDING) Then
            Debug("RelayOutput .......: Write pending")
            $iState = 2
        Else
            ; An error occurred, disconnect from the client
            LogError("RelayOutput .......: Write failed")
            ReconnectClient()
        EndIf
    EndIf
EndFunc   ;==>RelayOutput

; ===============================================================================================================================
; Executes a command and returns the results
; ===============================================================================================================================
Func ExecuteCmd($sCmd)
    Local $tProcess, $tSecurity, $tStartup, $hWritePipe, $iSuccess

    ; Set up security attributes
    $tSecurity = DllStructCreate($tagSECURITY_ATTRIBUTES)
    DllStructSetData($tSecurity, "Length", DllStructGetSize($tSecurity))
    DllStructSetData($tSecurity, "InheritHandle", True)

    ; Create a pipe for the child process's STDOUT
    If Not _NamedPipes_CreatePipe($hReadPipe, $hWritePipe, $tSecurity) Then
        LogError("ExecuteCmd ........: _NamedPipes_CreatePipe failed")
        Return False
    EndIf

    ; Create child process
    $tProcess = DllStructCreate($tagPROCESS_INFORMATION)
    $tStartup = DllStructCreate($tagSTARTUPINFO)
    DllStructSetData($tStartup, "Size", DllStructGetSize($tStartup))
    DllStructSetData($tStartup, "Flags", BitOR($STARTF_USESTDHANDLES, $STARTF_USESHOWWINDOW))
    DllStructSetData($tStartup, "StdOutput", $hWritePipe)
    DllStructSetData($tStartup, "StdError", $hWritePipe)
    $iSuccess = _WinAPI_CreateProcess("", $sCmd, 0, 0, True, 0, 0, "", DllStructGetPtr($tStartup), DllStructGetPtr($tProcess))
    If Not $iSuccess Then
        LogError("ExecuteCmd ........: _WinAPI_CreateProcess failed")
        _WinAPI_CloseHandle($hReadPipe)
        _WinAPI_CloseHandle($hWritePipe)
        Return False
    EndIf

    _WinAPI_CloseHandle(DllStructGetData($tProcess, 'hThread'))
    $hProcess = DllStructGetData($tProcess, 'hProcess')
    $ProcessID = DllStructGetData($tProcess, 'ProcessID')
    LogMsg("PID: " & $ProcessID)

    ;_WinAPI_CloseHandle(DllStructGetData($tProcess, "hProcess"))
    ;_WinAPI_CloseHandle(DllStructGetData($tProcess, "hThread"))

    ; Close the write end of the pipe so that we can read from the read end
    _WinAPI_CloseHandle($hWritePipe)

    LogMsg("ExecuteCommand ....: " & $sCmd)
    Return True
EndFunc   ;==>ExecuteCmd
Edited by danls

Share this post


Link to post
Share on other sites



So I'm starting to think maybe I should use the Run command instead of _WinAPI_CreateProcess, which the named pipes example uses. I could then use StdoutRead and StdInWrite to send and receive data from the process, and then send this into the named pipe.

But I still can't figure out why the cmd /k process exits when using _WinAPI_CreateProcess as above. Any ideas?

Share this post


Link to post
Share on other sites

No one has any clue about why a created process just exits like this? This is driving me crazy...

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