How to execute commands from an attached console

I'm coding a WinAPI GUI program that needs calling ftp and possibly other console programs while getting their console output to act accordingly ie. waiting for ftp to complete execution before reading all its output wouldn't do.

My current approach is calling CreateProcess() to create a cmd.exe process potentially hiding the ugly console window, AttachConsole() to make it my own, GetStdHandle() to get input and output handles, SetConsoleCursorPosition() to the end of the console buffer, and WriteConsole() with commands such as ftp\n or dir\n. Yet this commands are written but not executed. However, I can manually use the same console ( using CreateProcess() with CREATE_NEW_CONSOLE flag ) to type ftp press enter and get it executed.

Previous approaches involved:

  1. Calling ftp directly with CreateProcess() and redirected inputs/outputs.

    Couldn't get ftp output until the CreateProcess() process had already ended.

  2. Using system().

    Was advised against its usage before getting any output.

My current stripped down code:

// Next two structures might be a bit misleading, they were used for the 1. previous
// approach
PROCESS_INFORMATION piProcInfo;
ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION)); 
STARTUPINFO siStartInfo;
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;
SECURITY_ATTRIBUTES security;
security.nLength = sizeof(SECURITY_ATTRIBUTES); 
security.lpSecurityDescriptor = NULL;
security.bInheritHandle = FALSE;
CreateProcess( NULL, "cmd", &security, &security, FALSE, NORMAL_PRIORITY_CLASS |
 CREATE_NEW_CONSOLE, NULL, NULL, &siStartInfo, &piProcInfo); 
uint32_t pidConsole = piProcInfo.dwProcessId;
while ( ! AttachConsole(pidConsole) ){};
HANDLE myConsoleIn, myConsoleOut;
myConsoleIn = GetStdHandle(STD_INPUT_HANDLE);
myConsoleOut = GetStdHandle(STD_OUTPUT_HANDLE);
Sleep(100);
CONSOLE_SCREEN_BUFFER_INFO myConsoleCursorInformation = {};
GetConsoleScreenBufferInfo(myConsoleOut,&myConsoleCursorInformation);
SetConsoleCursorPosition(myConsoleOut,myConsoleCursorInformation.dwSize);
CHAR myConsoleBuffer[200]="dir\n";
DWORD myConsoleProcessed;
WriteConsole( myConsoleOut, myConsoleBuffer, 4, &myConsoleProcessed, NULL);

How can I get a command written in the console to execute? Is there an alternative to my attempt of ending commands with a trailing \n ie. using WriteConsole() with a dir\n or ftp\n argument.

I thought about sending a keypress to the process in question after typing the desired command. Yet the created console needs not only to manually press the enter key but also having dir, ftp or whatever command to be manually typed.

Please feel free to point out any missing information !

Upvotes: 0

Views: 763

Answers (1)

Rita Han
Rita Han

Reputation: 9700

How can I get a command written in the console to execute? Is there an alternative to my attempt of ending commands with a trailing \n ie. using WriteConsole() with a dir\n or ftp\n argument.

Try the following code to see if it works:

STARTUPINFO si;
PROCESS_INFORMATION pi;

ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));

const wchar_t *cmdPath = L"C:\\Windows\\System32\\cmd.exe";
wchar_t *cmdArgs = (wchar_t *)L"C:\\Windows\\System32\\cmd.exe /k dir";

BOOL result = CreateProcess(cmdPath, cmdArgs, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
DWORD errCode = GetLastError();
if (!result)
{
    std::cout << "Create Process failed: " << GetLastError() << std::endl;
}

/K Run Command and then return to the CMD prompt. This is useful for testing, to examine variables

Use /C if you want "Run Command and then terminate".

Update: Complete code for communicating with a child process(cmd.exe) using pipes.

HANDLE g_hChildStd_IN_Rd = NULL;
HANDLE g_hChildStd_IN_Wr = NULL;
HANDLE g_hChildStd_OUT_Rd = NULL;
HANDLE g_hChildStd_OUT_Wr = NULL;

#define BUFSIZE 1024

void ErrorExit(LPCTSTR lpszFunction)
{
    LPVOID lpMsgBuf;
    LPVOID lpDisplayBuf;
    DWORD dw = GetLastError();

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR)&lpMsgBuf,
        0, NULL);

    lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT,
        (lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR));
    StringCchPrintf((LPTSTR)lpDisplayBuf,
        LocalSize(lpDisplayBuf) / sizeof(TCHAR),
        TEXT("%s failed with error %d: %s"),
        lpszFunction, dw, lpMsgBuf);
    MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK);

    LocalFree(lpMsgBuf);
    LocalFree(lpDisplayBuf);
    ExitProcess(1);
}

void ReadFromPipe(void)
{
    DWORD dwRead, dwWritten;
    CHAR chBuf[BUFSIZE];
    BOOL bSuccess = FALSE;
    HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);

    for (;;)
    {
        DWORD bytesAvail = 0;
        if (!PeekNamedPipe(g_hChildStd_OUT_Rd, NULL, 0, NULL, &bytesAvail, NULL)) {
            std::cout << "Failed to call PeekNamedPipe" << std::endl;
        }
        if (bytesAvail) {
            DWORD n;
            BOOL success = ReadFile(g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &n, NULL);
            if (!success || n == 0) {
            }
            bSuccess = WriteFile(hParentStdOut, chBuf,n, &dwWritten, NULL);
        }
        else
        {
            break;
        }
    }

}

void WriteToPipe(void)
{
    DWORD dwWritten;
    BOOL bSuccess = FALSE;
    CHAR buf[] = "dir\n";

    bSuccess = WriteFile(g_hChildStd_IN_Wr, buf, sizeof(buf)-1, &dwWritten, NULL);
}

int main()
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    SECURITY_ATTRIBUTES saAttr;

    printf("\n->Start of parent execution.\n");

    // 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))
        ErrorExit(TEXT("StdoutRd CreatePipe"));

    // Ensure the read handle to the pipe for STDOUT is not inherited.

    if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0))
        ErrorExit(TEXT("Stdout SetHandleInformation"));

    // Create a pipe for the child process's STDIN. 

    if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0))
        ErrorExit(TEXT("Stdin CreatePipe"));

    // Ensure the write handle to the pipe for STDIN is not inherited. 

    if (!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0))
        ErrorExit(TEXT("Stdin SetHandleInformation"));

    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));


    si.cb = sizeof(STARTUPINFO);
    si.hStdError = g_hChildStd_OUT_Wr;
    si.hStdOutput = g_hChildStd_OUT_Wr;
    si.hStdInput = g_hChildStd_IN_Rd;
    si.dwFlags |= STARTF_USESTDHANDLES;

    TCHAR cmdPath[] = TEXT("C:\\Windows\\System32\\cmd.exe");

    BOOL result = CreateProcess(cmdPath, NULL, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
    DWORD errCode = GetLastError();
    if (!result)
    {
        std::cout << "Create Process failed: " << GetLastError() << std::endl;
    }

    for (;;)
    {
        ReadFromPipe();

        WriteToPipe();
    }
}

Upvotes: 1

Related Questions