Search code examples
c++windows-services

How to read output of a console application launched by a windows service


I've create a windows service run in LocalSystem account. In the service, a console application is launched and I need to capture the console output of the console application inside my windows service.

Inside the code, a pipe is created for redirecting console window output, and the handle is passed to the STARTUPINFO for creating the console application process. After the console application is launched, the code wait for the finish of the console application and then read the console application output. I tested this logic (similar code) working if not running within windows service.

In my windows service, the console application is launched in an environment created using a logged-on user's session id. I can see that the console application is launched with text output in the console window. But after the console application finishes, no console output text is read by my windows service.

My question is can what I want to achieved be achieved? If so, what I have missing in my code below?

bool WinProcess::SpawnConsoleProcessAsUser()
{
    HANDLE hToken = NULL;
    bool bSuccess = (TRUE == WTSQueryUserToken(m_nSessionId, &hToken));     // calling application must be running within the context of the LocalSystem account
    if (bSuccess)
    {
        void* pEnvironment = nullptr;
        bSuccess = (TRUE == CreateEnvironmentBlock(&pEnvironment, hToken, TRUE));
        if (bSuccess)
        {
            std::wstring strParameters = L"\"" + m_strFullPathExe + L"\" ";
            for (size_t i = 0; i < m_vecParams.size(); ++i)
            {
                strParameters += m_vecParams[i] + L" ";
            }

            wchar_t cBuf[1024];
            ZeroMemory(cBuf, sizeof(cBuf));
            swprintf_s(cBuf, _countof(cBuf), strParameters.c_str());

            // Create security attributes to create pipe.
            SECURITY_ATTRIBUTES oSecurity  = { sizeof(SECURITY_ATTRIBUTES) };
            oSecurity.bInheritHandle       = TRUE; // Set the bInheritHandle flag so pipe handles are inherited by child process. Required.
            oSecurity.lpSecurityDescriptor = NULL;

            m_hChildStdOutRead = NULL;
            m_hChildStdOutWrite = NULL;;

            // Create a pipe to get results from child's stdout.
            // I'll create only 1 because I don't need to pipe to the child's stdin.
            bool bSuccess = (TRUE == CreatePipe(&m_hChildStdOutRead, &m_hChildStdOutWrite, &oSecurity, 0));
            if (bSuccess)
            {
                ZeroMemory(&m_oStartupInfo, sizeof(m_oStartupInfo));
                m_oStartupInfo.cb          = sizeof(m_oStartupInfo);
                m_oStartupInfo.dwFlags     = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; // STARTF_USESTDHANDLES is Required.
                m_oStartupInfo.hStdOutput  = m_hChildStdOutWrite; // Requires STARTF_USESTDHANDLES in dwFlags.
                m_oStartupInfo.hStdError   = m_hChildStdOutWrite; // Requires STARTF_USESTDHANDLES in dwFlags.
                // m_oStartupInfo.hStdInput remains null.
#if 0
                m_oStartupInfo.wShowWindow = SW_HIDE;
#else
                m_oStartupInfo.wShowWindow = SW_SHOW;
#endif
                m_oStartupInfo.lpDesktop   = L"winsta0\\default";

                PROCESS_INFORMATION oProcessInfo;
                ZeroMemory(&oProcessInfo, sizeof(oProcessInfo));

                bSuccess = (TRUE == CreateProcessAsUser(hToken,                                         // primary token representing a user
                                                        m_strFullPathExe.c_str(),                       // optional application name
                                                        cBuf,                                           // command line
                                                        NULL,                                           // process attributes
                                                        NULL,                                           // thread attributes
                                                        FALSE,                                          // inherit handles
                                                        NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT
                                                                              | CREATE_NEW_CONSOLE,     // creation flags
                                                        pEnvironment,                                   // environment block
                                                        m_strWorkingDir.c_str(),                        // starting directory
                                                        &m_oStartupInfo,                                // startup info
                                                        &oProcessInfo));                                // process info

                DestroyEnvironmentBlock(pEnvironment);
                CloseHandle(hToken);
                if (bSuccess)
                {
                    m_hChildProcess = oProcessInfo.hProcess;
                    m_hChildThread = oProcessInfo.hThread;
                }
            }
            if (!bSuccess)
            {
                DWORD dwError = GetLastError();
                ErrorReport(TEXT(__FUNCTION__), dwError);
            }
        }
        else
        {
            CloseHandle (hToken);
        }
    }
    return bSuccess;
}

bool WinProcess::WaitFinish(const uint32_t a_unTimeoutMs)
{
    static const wchar_t CR = L'\r';
    DWORD dwTimeoutMs = a_unTimeoutMs;
    if (UINT_MAX == a_unTimeoutMs)
    {
        dwTimeoutMs = INFINITE;
    }
    time_t tStart;
    time(&tStart);
    DWORD dwCode = WaitForSingleObject(m_hChildProcess, dwTimeoutMs);
    bool bSuccess = (WAIT_TIMEOUT != dwCode);
    if (!bSuccess)
    {
        // timed out waiting for the termination of the net command process
        bSuccess = (FALSE != TerminateProcess(m_hChildProcess, WAIT_FAILED));
    }
    else
    {
        DWORD dwExitCode = STILL_ACTIVE;
        if (FALSE == GetExitCodeProcess(m_hChildProcess, &dwExitCode))
        {
            // logging her
        }
        else if (STILL_ACTIVE == dwExitCode)
        {
            // logging her
        }
        else
        {
            time_t tDone;
            time(&tDone);
            bSuccess = (0 == dwExitCode);
        }
        m_nExitCode = dwExitCode;

        CloseHandle(m_hChildProcess);
        if (NULL != m_hChildThread)
        {
            CloseHandle(m_hChildThread);
        }
    }
    CloseHandle(m_hChildStdOutWrite);

    m_bTerminated = true;
    m_strProcessOutput = ReadProcessOutput();
    m_strProcessOutput.erase(std::remove(m_strProcessOutput.begin(), m_strProcessOutput.end(), CR), m_strProcessOutput.end());

    return bSuccess;
}

std::wstring WinProcess::ReadProcessOutput()
{
    std::string strProcessOut("");
    for (;;)
    {
        DWORD dwRead;
        char cBuf[1024];

        // Read from pipe that is the standard output for child process.
        ZeroMemory(cBuf, sizeof(cBuf));
        bool bDone = !ReadFile(m_hChildStdOutRead, cBuf, sizeof(cBuf) - 1, &dwRead, NULL) || dwRead == 0;
        if (bDone)
        {
            break;
        }
        cBuf[dwRead] = '\0';

        // Append result to string.
        strProcessOut += std::string(cBuf);
    }
    return StrUtil::string_cast<std::wstring>(strProcessOut);

Solution

  • I've got an answer from another forum. "inherit handles" should be set to TRUE.