Shell
Represents instance of the SSH shell object.
            
                using Renci.SshNet.Abstractions;
using Renci.SshNet.Channels;
using Renci.SshNet.Common;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace Renci.SshNet
{
    public sealed class Shell : IDisposable
    {
        private const int DefaultBufferSize = 1024;
        private readonly ISession _session;
        private readonly string _terminalName;
        private readonly uint _columns;
        private readonly uint _rows;
        private readonly uint _width;
        private readonly uint _height;
        private readonly IDictionary<TerminalModes, uint> _terminalModes;
        private readonly Stream _outputStream;
        private readonly Stream _extendedOutputStream;
        private readonly int _bufferSize;
        private readonly bool _noTerminal;
        private ManualResetEvent _dataReaderTaskCompleted;
        private IChannelSession _channel;
        private AutoResetEvent _channelClosedWaitHandle;
        private Stream _input;
        private bool _disposed;
        public bool IsStarted { get; set; }
        public event EventHandler<EventArgs> Starting;
        public event EventHandler<EventArgs> Started;
        public event EventHandler<EventArgs> Stopping;
        public event EventHandler<EventArgs> Stopped;
        public event EventHandler<ExceptionEventArgs> ErrorOccurred;
        internal Shell(ISession session, Stream input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary<TerminalModes, uint> terminalModes, int bufferSize)
            : this(session, input, output, extendedOutput, bufferSize, false)
        {
            _terminalName = terminalName;
            _columns = columns;
            _rows = rows;
            _width = width;
            _height = height;
            _terminalModes = terminalModes;
        }
        internal Shell(ISession session, Stream input, Stream output, Stream extendedOutput, int bufferSize)
            : this(session, input, output, extendedOutput, bufferSize, true)
        {
        }
        private Shell(ISession session, Stream input, Stream output, Stream extendedOutput, int bufferSize, bool noTerminal)
        {
            if (bufferSize == -1)
                bufferSize = 1024;
            if (bufferSize <= 0)
                throw new ArgumentOutOfRangeException("bufferSize");
            _session = session;
            _input = input;
            _outputStream = output;
            _extendedOutputStream = extendedOutput;
            _bufferSize = bufferSize;
            _noTerminal = noTerminal;
        }
        public void Start()
        {
            if (IsStarted)
                throw new SshException("Shell is started.");
            this.Starting?.Invoke(this, EventArgs.Empty);
            _channel = _session.CreateChannelSession();
            _channel.DataReceived += Channel_DataReceived;
            _channel.ExtendedDataReceived += Channel_ExtendedDataReceived;
            _channel.Closed += Channel_Closed;
            _session.Disconnected += Session_Disconnected;
            _session.ErrorOccured += Session_ErrorOccurred;
            _channel.Open();
            if (!_noTerminal && !_channel.SendPseudoTerminalRequest(_terminalName, _columns, _rows, _width, _height, _terminalModes))
                throw new SshException("The pseudo-terminal request was not accepted by the server. Consult the server log for more information.");
            if (!_channel.SendShellRequest())
                throw new SshException("The request to start a shell was not accepted by the server. Consult the server log for more information.");
            _channelClosedWaitHandle = new AutoResetEvent(false);
            _dataReaderTaskCompleted = new ManualResetEvent(false);
            ThreadAbstraction.ExecuteThread(delegate {
                try {
                    byte[] array = new byte[_bufferSize];
                    while (_channel.IsOpen) {
                        Task<int> task = _input.ReadAsync(array, 0, array.Length);
                        WaitHandle asyncWaitHandle = ((IAsyncResult)task).AsyncWaitHandle;
                        if (WaitHandle.WaitAny(new WaitHandle[2] {
                            asyncWaitHandle,
                            _channelClosedWaitHandle
                        }) != 0)
                            break;
                        int result = task.GetAwaiter().GetResult();
                        _channel.SendData(array, 0, result);
                    }
                } catch (Exception exception) {
                    RaiseError(new ExceptionEventArgs(exception));
                } finally {
                    _dataReaderTaskCompleted.Set();
                }
            });
            IsStarted = true;
            this.Started?.Invoke(this, EventArgs.Empty);
        }
        public void Stop()
        {
            if (!IsStarted)
                throw new SshException("Shell is not started.");
            _channel?.Dispose();
        }
        private void Session_ErrorOccurred(object sender, ExceptionEventArgs e)
        {
            RaiseError(e);
        }
        private void RaiseError(ExceptionEventArgs e)
        {
            this.ErrorOccurred?.Invoke(this, e);
        }
        private void Session_Disconnected(object sender, EventArgs e)
        {
            Stop();
        }
        private void Channel_ExtendedDataReceived(object sender, ChannelExtendedDataEventArgs e)
        {
            Stream extendedOutputStream = _extendedOutputStream;
            if (extendedOutputStream != null) {
                ArraySegment<byte> data = e.Data;
                byte[] array = data.Array;
                data = e.Data;
                int offset = data.Offset;
                data = e.Data;
                extendedOutputStream.Write(array, offset, data.Count);
            }
        }
        private void Channel_DataReceived(object sender, ChannelDataEventArgs e)
        {
            Stream outputStream = _outputStream;
            if (outputStream != null) {
                ArraySegment<byte> data = e.Data;
                byte[] array = data.Array;
                data = e.Data;
                int offset = data.Offset;
                data = e.Data;
                outputStream.Write(array, offset, data.Count);
            }
        }
        private void Channel_Closed(object sender, ChannelEventArgs e)
        {
            if (this.Stopping != null)
                ThreadAbstraction.ExecuteThread(delegate {
                    this.Stopping(this, EventArgs.Empty);
                });
            _channel.Dispose();
            _channelClosedWaitHandle.Set();
            _input.Dispose();
            _input = null;
            _dataReaderTaskCompleted.WaitOne(_session.ConnectionInfo.Timeout);
            _dataReaderTaskCompleted.Dispose();
            _dataReaderTaskCompleted = null;
            _channel.DataReceived -= Channel_DataReceived;
            _channel.ExtendedDataReceived -= Channel_ExtendedDataReceived;
            _channel.Closed -= Channel_Closed;
            UnsubscribeFromSessionEvents(_session);
            if (this.Stopped != null)
                ThreadAbstraction.ExecuteThread(delegate {
                    this.Stopped(this, EventArgs.Empty);
                });
            _channel = null;
        }
        private void UnsubscribeFromSessionEvents(ISession session)
        {
            if (session != null) {
                session.Disconnected -= Session_Disconnected;
                session.ErrorOccured -= Session_ErrorOccurred;
            }
        }
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        private void Dispose(bool disposing)
        {
            if (!_disposed && disposing) {
                UnsubscribeFromSessionEvents(_session);
                AutoResetEvent channelClosedWaitHandle = _channelClosedWaitHandle;
                if (channelClosedWaitHandle != null) {
                    channelClosedWaitHandle.Dispose();
                    _channelClosedWaitHandle = null;
                }
                IChannelSession channel = _channel;
                if (channel != null) {
                    channel.Dispose();
                    _channel = null;
                }
                ManualResetEvent dataReaderTaskCompleted = _dataReaderTaskCompleted;
                if (dataReaderTaskCompleted != null) {
                    dataReaderTaskCompleted.Dispose();
                    _dataReaderTaskCompleted = null;
                }
                _disposed = true;
            }
        }
    }
}