Redirect Input\Output pipes from parent process to cmd child process C/C++ WinApi

Viewed 1072

I have been trying to run a parent process that acts like a cmd input\output console. the purpose of this is for future use, but for now I am trying to isolate this function in order to get things right.

What I am trying to achieve here is an input from user send it to a simple cmd child process, get the output and keep on sending commands, while keeping the process running.The problem is that the cmd process closes right after the first command is executed. I pasted an output of what happens right after the first command,the issue that rises once I try to keep on going and inputting more commands is that the handle is not valid (since the process is terminated).

Help would be much appreciated!

Main code:

#define  _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <stdio.h>
#include <strsafe.h>
#define bzero(p,size) (void) memset((p), 0 , (size))


// Constant
#define BUFSIZE 4096
// Global variables
HANDLE g_hChildStd_IN_Rd = NULL;
HANDLE g_hChildStd_IN_Wr = NULL;
HANDLE g_hChildStd_OUT_Rd = NULL;
HANDLE g_hChildStd_OUT_Wr = NULL;
HANDLE g_hInputFile = NULL;


// Create a child process that uses the previously created pipes for STDIN and STDOUT.
PROCESS_INFORMATION CreateChildProcess()
{
    // The following should be the child executable, see the next program example
    // Change the path accordingly...
    WCHAR szCmdline[] = L"cmd.exe";
    PROCESS_INFORMATION piProcInfo;
    STARTUPINFO siStartInfo;
    BOOL bSuccess = FALSE;
    // Set up members of the PROCESS_INFORMATION structure
    ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
    // Set up members of the STARTUPINFO structure
    // This structure specifies the STDIN and STDOUT handles for redirection
    ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
    siStartInfo.cb = sizeof(STARTUPINFO);
    siStartInfo.hStdError = g_hChildStd_OUT_Wr;
    siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
    siStartInfo.hStdInput = g_hChildStd_IN_Rd;
    siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
    // Create the child process
    bSuccess = CreateProcess(NULL, // Use szCmdLine
        szCmdline,     // command line
        NULL,          // process security attributes
        NULL,          // primary thread security attributes
        TRUE,          // handles are inherited
        CREATE_NEW_CONSOLE,             // creation flags
        NULL,          // use parent's environment
        NULL,          // use parent's current directory
        &siStartInfo,  // STARTUPINFO pointer
        &piProcInfo);  // receives PROCESS_INFORMATION
     // If an error occurs, exit the application.
    if (!bSuccess)
        printf("Error creating process, %d\n", GetLastError());

    else
    {
        wprintf(L"\nChild process ID is : %u\n", GetCurrentProcessId());
        wprintf(L"Child thread ID is : %u\n", GetCurrentThreadId());
        /*
        if (CloseHandle(piProcInfo.hProcess) != 0)
            wprintf(L"piProcInfo.hProcess handle was closed!\n");
        else
            printf("Error closing process , %d\n", GetLastError());

        if (CloseHandle(piProcInfo.hThread) != 0)
            wprintf(L"piProcInfo.hThread handle was closed!\n");
        else
            printf("Error closing process thread handle, %d\n", GetLastError());
            */
    }
    return piProcInfo;
}

void WriteToPipe(char * command)
{
    DWORD dwWritten;
    DWORD dwRead;
    CHAR chBuf[BUFSIZE];
    BOOL bSuccess = FALSE;
    bSuccess = WriteFile(g_hChildStd_IN_Wr, (LPVOID)command , (DWORD)strlen(command), &dwWritten, NULL);
    printf("Command: %s\n", command);
    if (!bSuccess)
    {
        wprintf(L"\nWriteFile() - Failed to write to pipe for child\'s STDIN! Error %u\n", GetLastError());
        //break;
    }
    else
        wprintf(L"\nWriteFile() - writing to the pipe for the child\'s STDIN...\n");
    // Close the pipe handle so the child process stops reading
    if (!CloseHandle(g_hChildStd_IN_Wr))
        printf("Error closing STDIN handle, %d\n", GetLastError());
    else
        wprintf(L"Closing the pipe handle...\n");

    //Read from pipe
    bSuccess = FALSE;
    bzero(chBuf, sizeof(chBuf));
    HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    if (!CloseHandle(g_hChildStd_OUT_Wr))
        printf("Error closing STDOUT handle, %d\n", GetLastError());
    for (;;)
    {
        bSuccess = ReadFile(g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL);
        if (!bSuccess || dwRead == 0)
        {
            wprintf(L"\nReadFile() from child's standard output failed! Error %u\n", GetLastError());
            break;
        }
        bSuccess = WriteFile(hParentStdOut, chBuf, dwRead, &dwWritten, NULL);
        if (!bSuccess)
        {
            wprintf(L"\nWriteFile() to parent's standard output failed! Error %u\n", GetLastError());
            break;
        }
    }
}

int main()
{
    PROCESS_INFORMATION pi;
    char filename[100];
    ZeroMemory(&filename, sizeof(filename));
    strcpy(filename, "commands.txt");
    SECURITY_ATTRIBUTES saAttr;
    wprintf(L"Parent process ID %u\n", GetCurrentProcessId());
    // Set the bInheritHandle flag so pipe handles are inherited
    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
    saAttr.bInheritHandle = TRUE;
    saAttr.lpSecurityDescriptor = NULL;
    // Create a pipe for the child process's STDOUT
    if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0))
        printf("Create pipe for STDOUT failed,%d\n", GetLastError());
    else
        wprintf(L"CreatePipe() - pipe for child process\'s STDOUT pipe was created!\n");
    // Ensure the read handle to the pipe for STDOUT is not inherited
    if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0))
        printf("Create handle for STDOUT failed,%d\n", GetLastError());
    else
        wprintf(L"SetHandleInformation() - pipe STDOUT read handle is not inherited!\n");
    // Create a pipe for the child process's STDIN
    if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0))
        printf("Create pipe for STDIN failed,%d\n", GetLastError());
    else
        wprintf(L"CreatePipe() - pipe for child process\'s STDIN was created!\n");
    // Ensure the write handle to the pipe for STDIN is not inherited
    if (!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0))
        printf("Error getting handle on STDIN,%d\n", GetLastError());
    else
        wprintf(L"Stdin SetHandleInformation() - pipe STDIN read handle is not inherited!\n");
    // Create the child process
    wprintf(L"Creating the child process...\n");
    pi = CreateChildProcess();
    char chr[1000];
    for (;;)
    {
        bzero(chr, sizeof(chr));
        printf("Enter a character: ");
        scanf("%s", chr);
        strcat(chr, "\n");
        if(strncmp(chr,"exit",4) == 0)
            break;
        WriteToPipe(chr);
    }
    if (CloseHandle(pi.hProcess) != 0)
        wprintf(L"piProcInfo.hProcess handle was closed!\n");
    else
        printf("Error closing process , %d\n", GetLastError());

    if (CloseHandle(pi.hThread) != 0)
        wprintf(L"piProcInfo.hThread handle was closed!\n");
    else
        printf("Error closing process thread handle, %d\n", GetLastError());
    printf("End of parent execution.\n");
    return 0;

}

Output:

Parent process ID 10580
CreatePipe() - pipe for child process's STDOUT pipe was created!
SetHandleInformation() - pipe STDOUT read handle is not inherited!
CreatePipe() - pipe for child process's STDIN was created!
Stdin SetHandleInformation() - pipe STDIN read handle is not inherited!
Creating the child process...

Child process ID is : 10580
Child thread ID is : 1880
Enter a character: whoami
Command: whoami


WriteFile() - writing to the pipe for the child's STDIN...
Closing the pipe handle...
Microsoft Windows [Version 10.0.18363.836]
(c) 2019 Microsoft Corporation. All rights reserved.

C:\Users\Administrator\Desktop>whoami
Administrator

C:\Users\Administrator\Desktop>Clink v0.4.9 [git:2fd2c2] Copyright (c) 2012-2016 Martin Ridgers
http://mridgers.github.io/clink


ReadFile() from child's standard output failed! Error 109
Enter a character:
1 Answers

The problem is that the cmd process closes right after the first command is executed.

The child process exits after this line executed:

if (!CloseHandle(g_hChildStd_IN_Wr))

To solve this problem you can use separate threads. One thread for reading command from console input. Another thread for reading result of executed command and print them out. Without closing g_hChildStd_IN_Wr handle.

In main() method:

HANDLE rThread = CreateThread(NULL, 0, ReceiveCommand, NULL, 0, NULL);

HANDLE oThread = CreateThread(NULL, 0, OutputResult, NULL, 0, NULL);

WaitForSingleObject(rThread, INFINITE);

Split WriteToPipe function to two parts: WriteToPipe and ReadFromPipe.

void WriteToPipe(char * command)
{
    DWORD dwWritten;
    BOOL bSuccess = FALSE;
    bSuccess = WriteFile(g_hChildStd_IN_Wr, (LPVOID)command, (DWORD)strlen(command), &dwWritten, NULL);
    printf("Command: %s\n", command);
    if (!bSuccess)
    {
        wprintf(L"\nWriteFile() - Failed to write to pipe for child\'s STDIN! Error %u\n", GetLastError());
        //break;
    }
    else
        wprintf(L"\nWriteFile() - writing to the pipe for the child\'s STDIN...\n");
}

void ReadFromPipe()
{
    DWORD dwWritten;
    DWORD dwRead;
    CHAR chBuf[BUFSIZE];
    BOOL bSuccess = FALSE;
    DWORD availableBytes;
    DWORD bytesToRead;

    //Read from pipe
    bSuccess = FALSE;
    bzero(chBuf, sizeof(chBuf));
    HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    if (!CloseHandle(g_hChildStd_OUT_Wr))
        printf("Error closing STDOUT handle, %d\n", GetLastError());
    for (;;)
    {
        PeekNamedPipe(g_hChildStd_OUT_Rd, NULL, NULL, NULL, &availableBytes, NULL);

        while (availableBytes > 0)
        {
            if (availableBytes <= BUFSIZE)
            {
                bytesToRead = availableBytes;
            }
            else
            {
                bytesToRead = BUFSIZE;
            }

            bSuccess = ReadFile(g_hChildStd_OUT_Rd, chBuf, bytesToRead, &dwRead, NULL);
            if (!bSuccess || dwRead == 0)
            {
                wprintf(L"\nReadFile() from child's standard output failed! Error %u\n", GetLastError());
                break;
            }

            availableBytes -= bytesToRead;

            bSuccess = WriteFile(hParentStdOut, chBuf, dwRead, &dwWritten, NULL);
            if (!bSuccess)
            {
                wprintf(L"\nWriteFile() to parent's standard output failed! Error %u\n", GetLastError());
                break;
            }
        }
    }
}

Using GetConsoleScreenBufferInfo to monitor cursor position to wait the output from child process print completely. Before that don't start to receive new command.

DWORD WINAPI ReceiveCommand(LPVOID lpParameter)
{
    char chr[1000];
    CONSOLE_SCREEN_BUFFER_INFO cbsi;
    if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cbsi))
    {
        curPos = cbsi.dwCursorPosition;
    }

    for (;;)
    {
        while (TRUE)
        {
            Sleep(50);
            if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cbsi))
            {
                // printf error message
                return 0;
            }

            if ((curPos.X == cbsi.dwCursorPosition.X) && (curPos.Y == cbsi.dwCursorPosition.Y))
            {
                // All output of the last command executed have been printed completely.
                break;
            }

            curPos = cbsi.dwCursorPosition;
        }

        bzero(chr, sizeof(chr));
        printf("Enter a character: ");
        scanf("%s", chr);
        strcat(chr, "\n");
        if (strncmp(chr, "exit", 4) == 0)
            break;

        WriteToPipe(chr);
    }

    return 0;
}

DWORD WINAPI OutputResult(LPVOID lpParameter)
{
    ReadFromPipe();
    return 0;
}
Related