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.

Justin
  • 839
  • 1
  • 16
  • 24
  • Do you control the source to the application that is already running? Or is this some 3rd party application you'd like to scrape? – user7116 Oct 15 '09 at 20:25
  • Can the application be wrapped inside a .NET assembly? Meaning can you launch it as a process within code you wrote, or is it a service that fails if it's wrapped up in this way. – Agent_9191 Oct 15 '09 at 20:34
  • @sixlettervariables: It is an in-house application but I do not own the code, and it would be difficult to make changes. @Agent_9191: I updated the question to address your comment. – Justin Oct 15 '09 at 21:08

4 Answers4

3

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
  • 476,176
  • 80
  • 629
  • 1,111
  • Very interesting. I have never seen this before, but it looks really cool. I will look into it – Justin Oct 15 '09 at 21:00
  • 3
    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 Oct 15 '09 at 21:05
0

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
  • 1,023,142
  • 271
  • 3,287
  • 2,928
0

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.

Community
  • 1
  • 1
Oleg Zhylin
  • 1,290
  • 12
  • 18
0

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
  • 25,014
  • 6
  • 48
  • 78