Jump to content

Named Pipes example - interactive commands possible?


danls
 Share

Recommended Posts

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
Link to comment
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?

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

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...