简体   繁体   中英

Process.Start capturing standard output characters

I have a Windows Service that is going to process results from plink.exe (Putty/SSH thing). I'm currently successfully capturing the standard output and sending commands via the standard input to the process.

My problem is, the OutputDataReceived event doesn't seem to get raised until I receive a line break from the console application process. The process prompts for a password and there isn't a line break until after I input a password.

So my question is, is there a way to process the Standard Output character-by-character, instead of line-by-line from the System.Diagnostics.Process class?

Here is my code:

_processInfoTest = new ProcessStartInfo();
_processInfoTest.FileName = serviceSettings.PlinkExecutable;
_processInfoTest.Arguments = GetPlinkArguments(serviceSettings);
_processInfoTest.RedirectStandardOutput = true;
_processInfoTest.RedirectStandardError = true;
_processInfoTest.RedirectStandardInput = true;
_processInfoTest.UseShellExecute = false;
_processInfoTest.CreateNoWindow = true;

_processTest = new Process();
_processTest.StartInfo = _processInfoTest;
_processTest.OutputDataReceived += processTest_OutputDataReceived;
_processTest.ErrorDataReceived += processTest_OutputDataReceived;
_processTest.Start();

_processTest.BeginOutputReadLine();
_processTest.BeginErrorReadLine();

And the event handler that handles the incoming lines of text:

private static void processTest_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
    string line = e.Data;

    if (line != null)
    {
        WcfServerHelper.BroadcastRemoteCallback(x => x.PlinkTextOutput(line, DateTime.Now));

        if (line.Contains("If you do not trust this host, press Return to abandon the"))
        {
            _processTest.StandardInput.Write("y");
            _processTest.StandardInput.Write("\n");
            _processTest.StandardInput.Flush();
        }

        // This code never gets called because the event doesn't get raised until a line-break occurs
        if (line.Contains("'s password:"))
        {
            _processTest.StandardInput.Write("mypassword");
            _processTest.StandardInput.Write("\n");
            _processTest.StandardInput.Flush();
        }

        if (line.Contains("Access granted"))
        {
            if (!_processTest.HasExited)
                _processTest.Kill();

            WcfServerHelper.BroadcastRemoteCallback(x => x.TestConnectionCallback(PlinkStatus.Success));
        }
        else if (line.Contains("Access denied") || line.Contains("Password authentication failed"))
        {
            if (!_processTest.HasExited)
                _processTest.Kill();

            WcfServerHelper.BroadcastRemoteCallback(x => x.TestConnectionCallback(PlinkStatus.InvalidUserOrPass));
        }
        else if (line.Contains("Host does not exist"))
        {
            if (!_processTest.HasExited)
                _processTest.Kill();

            WcfServerHelper.BroadcastRemoteCallback(x => x.TestConnectionCallback(PlinkStatus.InvalidHostname));
        }
        else if (line.Contains("Connection timed out"))
        {
            if (!_processTest.HasExited)
                _processTest.Kill();

            WcfServerHelper.BroadcastRemoteCallback(x => x.TestConnectionCallback(PlinkStatus.TimedOut));
        }
    }
}

Thank you!

您正在使用的事件是行缓冲...您必须实现自己的读取器,在流读取器上调用Read()/ ReadAsync()以获取每个char ...

I was looking for an answer forever, and right after asking the question here I found my solution.

Btw, thanks DarkSquirrel, you hit the nail on the head.

Here is my solution:

_processInfoTest = new ProcessStartInfo();
_processInfoTest.FileName = serviceSettings.PlinkExecutable;
_processInfoTest.Arguments = GetPlinkArguments(serviceSettings);
_processInfoTest.RedirectStandardOutput = true;
_processInfoTest.RedirectStandardError = true;
_processInfoTest.RedirectStandardInput = true;
_processInfoTest.UseShellExecute = false;
_processInfoTest.CreateNoWindow = true;

WcfServerHelper.BroadcastRemoteCallback(x => x.PlinkTextOutput(_processInfoTest.Arguments, DateTime.Now));

_processTest = new Process();
_processTest.StartInfo = _processInfoTest;
_processTest.Start();

Task.Factory.StartNew(() =>
    {
        ProcessOutputCharacters(_processTest.StandardError);
    });

Task.Factory.StartNew(() =>
    {
        ProcessOutputCharacters(_processTest.StandardOutput);
    });

And my methods:

private static void ProcessOutputCharacters(StreamReader streamReader)
{
    int outputCharInt;
    char outputChar;
    string line = string.Empty;

    while (-1 != (outputCharInt = streamReader.Read()))
    {
        outputChar = (char)outputCharInt;
        if (outputChar == '\n' || outputChar == '\r')
        {
            if (line != string.Empty)
            {
                ProcessLine("Output: " + line);
            }

            line = string.Empty;
        }
        else
        {
            line += outputChar;

            if (line.Contains("login as:"))
            {
                _processTest.StandardInput.Write("myusername");
                _processTest.StandardInput.Write("\n");
                _processTest.StandardInput.Flush();
            }

            if (line.Contains("'s password:"))
            {
                _processTest.StandardInput.Write("mypassword");
                _processTest.StandardInput.Write("\n");
                _processTest.StandardInput.Flush();
            }
        }
    }
}

private static void ProcessLine(string line)
{
    if (line != null)
    {
        WcfServerHelper.BroadcastRemoteCallback(x => x.PlinkTextOutput(line, DateTime.Now));

        if (line.Contains("If you do not trust this host, press Return to abandon the"))
        {
            _processTest.StandardInput.Write("y");
            _processTest.StandardInput.Write("\n");
            _processTest.StandardInput.Flush();
        }

        if (line.Contains("Access granted"))
        {
            if (!_processTest.HasExited)
                _processTest.Kill();

            WcfServerHelper.BroadcastRemoteCallback(x => x.TestConnectionCallback(PlinkStatus.Success));
        }
        else if (line.Contains("Access denied") || line.Contains("Password authentication failed"))
        {
            if (!_processTest.HasExited)
                _processTest.Kill();

            WcfServerHelper.BroadcastRemoteCallback(x => x.TestConnectionCallback(PlinkStatus.InvalidUserOrPass));
        }
        else if (line.Contains("Host does not exist"))
        {
            if (!_processTest.HasExited)
                _processTest.Kill();

            WcfServerHelper.BroadcastRemoteCallback(x => x.TestConnectionCallback(PlinkStatus.InvalidHostname));
        }
        else if (line.Contains("Connection timed out"))
        {
            if (!_processTest.HasExited)
                _processTest.Kill();

            WcfServerHelper.BroadcastRemoteCallback(x => x.TestConnectionCallback(PlinkStatus.TimedOut));
        }
    }

}

My only problem now is when I send the username or password (through StandardInput), it's only sending the first 5 characters. I'll do some research on the issue and post that as a separate question if I need to.

Thanks!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM