Thursday, August 30, 2012

Interacting with a sub-processed shell in .Net

A while ago I asked on StackOverflow for some help with System.Diagnostics.Process. I was unable to make it work correctly to interact with the Flex Compiler Shell (fcsh.exe).

I accepted my own answer where I just gave up on the managed Process class and dealt directly with the Windows API.

Recently someone commented on my answer saying it doesn't work for him or her. I couldn't really help the commentator as my code had evolved a lot since I posted that answer. So I decided to blog about the code I now use.

In my latest code, I created an abstract class, ShellProcess, which should, in theory, allow interacting with any shell. ShellProcess is then sub-classed as FcshProcess. Both classes are shown below.

In this example, I obtain the output of the help command:

In debug, the output is:

List of fcsh commands:
mxmlc arg1 arg2 ... full compilation and optimization; return a target id
compc arg1 arg2 ... full SWC compilation
compile id incremental compilation
clear [id] clear target(s)
info [id] display compile target info
quit quit
(fcsh)

Note that ShellProcess.cs also holds a static class named Kernel32 that contains the API methods, structs and consts. I keep these separate in my project.

8 comments:

Chandan Dey said...

Hi Ludo
I am just go through your code and try to implement my requirement with your example but no luck.
I am trying to execute "psexec" command like "psexec \\testmac -u domain\admin -p abcd1234 powercfg /a" with "cmd.exe"
I am calling like this....
FcshProcess p = new FcshProcess();
p.Start(@"cmd.exe", @"C:\Windows\System32");
var fcshHelp = p.SendAndReceive(@"psexec \\testmac -u domain\admin -p abcd1234 powercfg /a");
p.Terminate();
Debug.Print(fcshHelp.Item1);

my code was fall into a loop in ReadToPrompt()method and after some time it will show timeout.
Please help me asap.

Ludo said...

Firstly, if you mean to interact with psexec, you should start it directly rather than start cmd.exe.

Secondly, you must subclass ShellProcess to match the psexec shell. It seems you are using the FcshProcess class as-is. If you modified it to match psexec, please provide the code so I can have a look.

alex said...

it works great! thank you very much for sharing, you saved a lot of mine time.

Seungweon's Blog said...

Hi Ludo,

I followed your instruction, and try to run python 2.7.3 from windows machine. It doesn't look like running correctly. As you see my code at http://sdrv.ms/14TWjQK , once I execute Python.exe and send a command: help(), then wants to get the help page. Would you give me a feedback what's wrong in my code in order to get the output result? Thank you.

Seungweon's Blog said...

For better understanding, I'm using Windows 2008 R2 with VS2012 for testing.

Seungweon's Blog said...

I have tried with very simple test with

namespace npythontest
{
public class Program
{
static void Main(string[] args)
{
PythonWrapper pw = new PythonWrapper();
pw.Start(@"C:\Windows\System32\cmd.exe", @"C:\Windows\System32\");

var pyHelp = pw.SendAndReceive(@"dir");
pw.Terminate();
Console.Write(pyHelp.Item1);
}
}
}

namespace Python
{
public sealed class PythonWrapper : ShellProcess
{
protected override string Prompt
{
get { return ">"; }
}

protected override string ExitCommand
{
get { return "exit"; }
}

protected override Encoding Encoding
{
get { return Encoding.UTF8; }
}
}
}

It is running ReadToPrompt()infinitely, and stdOut doesn't get any string.

I tried

pw.Start(@"C:\Python27\python.exe", @"C:\Python27\");

with prompt string ">>> ", but no success. the result is same. Any idea?

Seungweon's Blog said...

From the previous comments, I figured out how I resolved the issue of "cmd.exe" with

protected override string Prompt
{
get
{
var utf8 = Encoding.UTF8;
//byte[] utfBytes = utf8.GetBytes(@"C:\Windows\System32>");
byte[] utfBytes = utf8.GetBytes(@">>> ");
return utf8.GetString(utfBytes);
}
}

It looks like it has a problem in string comparison in ReadToPrompt between 'stdOut' and 'Prompt' in my windows system, so I added this and it works fine with cmd.exe.

However, I don't still get any stream output (stdOut) from ReadToPrompt() when tried "python.exe". I think It has to have any output although unicode is different in stdOut, but it doesn't capture any output. Any idea? Is it related to any CreatePipe() or SetHandleInformation in Start in ShellProcess.cs? Thanks.

Seungweon Park said...

Python.exe seems to send any prompt string to stdErr. Once I added stdErr checking from ReadToPrompt(), it works great!! Thank you.