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);
I've got an answer from another forum. "inherit handles" should be set to TRUE.