<PackageReference Include="SSH.NET" Version="2024.2.0" />

Shell

public class Shell : IDisposable
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 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_ErrorOccured; _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_ErrorOccured(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) { _extendedOutputStream?.Write(e.Data, 0, e.Data.Length); } private void Channel_DataReceived(object sender, ChannelDataEventArgs e) { _outputStream?.Write(e.Data, 0, e.Data.Length); } 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_ErrorOccured; } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual 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; } } } }