views:

191

answers:

4

I am trying to redirect the stdout of an already running process on Windows XP using C#. I am aware that I can do this if I spawn the process myself, but for this application I would prefer a "listener" i could just attach to another process.

Is this possible in pure .Net and if not is it even possible with Win32?

Thanks

UPDATE: There are multiple processes I am trying to monitor that are all started by a "gate keeper" process that will restart these processes if they crash. This makes it difficult for me to do any redirecting up front.

+1  A: 

SetOut method allows you to redirect standard output.

var sb = new StringBuilder();
using (var writer = new StringWriter(sb))
{
    Console.SetOut(writer);
    Console.WriteLine("Hello World");
}
var result = sb.ToString();
// The result variable will contain Hello World\r\n
Darin Dimitrov
This works only for your own console, not for the stdout of another running process.
itowlson
I do not own the code of the process I am trying to redirect
Justin
A: 

I don't have the experience of actually doing this, but I think you should take a look at this StackOverflow question. Next step would be to open objects associated with thos handles for reading and somehow determine which one is standard output and error. I doubt you will be able to hijack the handles. For example imagine the scenario when some other process already redirected standard output and possesses a handle and maybe even used some IPC to have some other processes have that handle too.

Sorry for not giving a definitive answer whether this is possible. I would like to know this myself.

Oleg Zhylin
I will look into this
Justin
+3  A: 

It would be fairly easy to do this in Win32 using the Detours Library. You'd look at all calls to WriteFile, and check whether they were going to standard output. You might also want to look at the console output functions (e.g. WriteConsoleOutput) but they're used rarely enough that you probably don't need to bother for use with most programs.

Offhand I don't remember whether detours directly supports use from .NET languages or not. If it doesn't, I suppose you could still use it via P/Invoke, but I don't think it would be pretty at all...

Edit: There are various similar (free) libraries around. For one example, Jeffrey Richter's book Advanced Windows used to include one that should work for this purpose. A quick look indicates that his current Windows via C/C++ still includes a section on "DLL injection and API hooking." That probably includes (and updated version of) the same code, which should be adequate for this kind of job.

Jerry Coffin
Very interesting. I have never seen this before, but it looks really cool. I will look into it
Justin
Unfortunately detours license restricts me from using it in a commercial environment unless I pay a on time $10,000 fee. So, while cool this is a no go.
Justin
A: 

This will do what your looking for I didn't know if you were using c++ so I just used c conventions. You need to clean this up before you use it I I just baned it out also make sure to close the handles to the pipe or you will leak.

// RunCmd.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>
#include <strsafe.h>
int RunCmd(_TCHAR * Command,_TCHAR **OutPut);
bool HasTerminated(PROCESS_INFORMATION PI,DWORD *ExitCode);
bool HasData(HANDLE H);
void ErrorExit(LPTSTR lpszFunction);

int _tmain(int argc, _TCHAR* argv[])
{
 _TCHAR * Buffer;
 _TCHAR CmdLine[] = _TEXT("Outputter.exe");
 RunCmd(CmdLine,&Buffer);
 wprintf(_TEXT("Buffer from other pgm \n%s"),Buffer);
 free(Buffer);
}


int RunCmd(_TCHAR * Command,_TCHAR ** OutPut)
{
 _TCHAR * CpBUff = NULL;
 STARTUPINFO SI;
 memset(&SI,0,sizeof(SI));
 *OutPut = NULL;
 SECURITY_ATTRIBUTES SA; 
 SA.nLength = sizeof(SECURITY_ATTRIBUTES);
 SA.lpSecurityDescriptor = NULL;
 SA.bInheritHandle = true;
 HANDLE ReadOutPut, WriteOutPut;
 if(!CreatePipe(&ReadOutPut,&WriteOutPut,&SA,0))
 {
  wprintf(_TEXT("Error"));
 }
 SI.hStdOutput = WriteOutPut;
 SI.cb = sizeof(STARTUPINFO);
 SI.dwFlags = STARTF_USESTDHANDLES;
 PROCESS_INFORMATION PI;

 if (!CreateProcess(NULL,Command,&SA,NULL,true,CREATE_NO_WINDOW,NULL,NULL,&SI,&PI))
 {

  ErrorExit(TEXT("CreateProcess"));
 }
 Sleep(500);
 DWORD ExitCode;
 char Buffer[512];
 _TCHAR ConvBuff[512];
 int Total =0;
 bool Zero;
 while (!HasTerminated(PI,&ExitCode) & HasData(ReadOutPut))
 {
  ZeroMemory(Buffer,512*sizeof(char));
  ZeroMemory(ConvBuff,512 * sizeof(_TCHAR));
  DWORD NumBytesRead;
  ReadFile(ReadOutPut,Buffer,512,&NumBytesRead,NULL);
  Zero = Total == 0; 
  Total += NumBytesRead +1;
  *OutPut = ((_TCHAR *) realloc(*OutPut,Total*sizeof(_TCHAR)));
  if(Zero)
  {
   ZeroMemory(*OutPut,Total * sizeof(_TCHAR));
  }
  size_t ConChar;
  mbstowcs_s(&ConChar,ConvBuff,strlen(Buffer)+1,Buffer,511);
  StringCchCat(*OutPut,Total,ConvBuff);
 }
 CloseHandle(PI.hProcess);
 CloseHandle(PI.hThread);
 return ExitCode;

}

bool HasTerminated(PROCESS_INFORMATION PI,DWORD *ExitCode)
{
 return (STILL_ACTIVE == GetExitCodeProcess(PI.hProcess,ExitCode));
}

bool HasData(HANDLE H)
{
 char Buffer[25];
 DWORD ReadBytes,TotalBytes,TotalLeft;

 PeekNamedPipe(H,Buffer,25,&ReadBytes,&TotalBytes,&TotalLeft);
 return ReadBytes > 0;

}

void ErrorExit(LPTSTR lpszFunction) 
{ 
    // Retrieve the system error message for the last-error code

    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 );

    // Display the error message and exit the process

    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(dw); 
}
rerun