SshCommand
Represents SSH command that can be executed.
using Renci.SshNet.Abstractions;
using Renci.SshNet.Channels;
using Renci.SshNet.Common;
using Renci.SshNet.Messages.Connection;
using Renci.SshNet.Messages.Transport;
using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
namespace Renci.SshNet
{
public class SshCommand : IDisposable
{
private ISession _session;
private readonly Encoding _encoding;
private IChannelSession _channel;
private CommandAsyncResult _asyncResult;
private AsyncCallback _callback;
private EventWaitHandle _sessionErrorOccuredWaitHandle;
private Exception _exception;
private bool _hasError;
private readonly object _endExecuteLock = new object();
private StringBuilder _result;
private StringBuilder _error;
private bool _isDisposed;
public string CommandText { get; set; }
public TimeSpan CommandTimeout { get; set; }
public int ExitStatus { get; set; }
public Stream OutputStream { get; set; }
public Stream ExtendedOutputStream { get; set; }
public string Result {
get {
if (_result == null)
_result = new StringBuilder();
if (OutputStream != null && OutputStream.Length > 0) {
StreamReader streamReader = new StreamReader(OutputStream, _encoding);
_result.Append(streamReader.ReadToEnd());
}
return _result.ToString();
}
}
public string Error {
get {
if (_hasError) {
if (_error == null)
_error = new StringBuilder();
if (ExtendedOutputStream != null && ExtendedOutputStream.Length > 0) {
StreamReader streamReader = new StreamReader(ExtendedOutputStream, _encoding);
_error.Append(streamReader.ReadToEnd());
}
return _error.ToString();
}
return string.Empty;
}
}
internal SshCommand(ISession session, string commandText, Encoding encoding)
{
if (session == null)
throw new ArgumentNullException("session");
if (commandText == null)
throw new ArgumentNullException("commandText");
if (encoding == null)
throw new ArgumentNullException("encoding");
_session = session;
CommandText = commandText;
_encoding = encoding;
CommandTimeout = Session.InfiniteTimeSpan;
_sessionErrorOccuredWaitHandle = new AutoResetEvent(false);
_session.Disconnected += Session_Disconnected;
_session.ErrorOccured += Session_ErrorOccured;
}
public IAsyncResult BeginExecute()
{
return BeginExecute(null, null);
}
public IAsyncResult BeginExecute(AsyncCallback callback)
{
return BeginExecute(callback, null);
}
public IAsyncResult BeginExecute(AsyncCallback callback, object state)
{
if (_asyncResult != null && !_asyncResult.EndCalled)
throw new InvalidOperationException("Asynchronous operation is already in progress.");
_asyncResult = new CommandAsyncResult {
AsyncWaitHandle = new ManualResetEvent(false),
IsCompleted = false,
AsyncState = state
};
if (_channel != null)
throw new SshException("Invalid operation.");
if (string.IsNullOrEmpty(CommandText))
throw new ArgumentException("CommandText property is empty.");
Stream outputStream = OutputStream;
if (outputStream != null) {
outputStream.Dispose();
OutputStream = null;
}
Stream extendedOutputStream = ExtendedOutputStream;
if (extendedOutputStream != null) {
extendedOutputStream.Dispose();
ExtendedOutputStream = null;
}
OutputStream = new PipeStream();
ExtendedOutputStream = new PipeStream();
_result = null;
_error = null;
_callback = callback;
_channel = CreateChannel();
_channel.Open();
_channel.SendExecRequest(CommandText);
return _asyncResult;
}
public IAsyncResult BeginExecute(string commandText, AsyncCallback callback, object state)
{
CommandText = commandText;
return BeginExecute(callback, state);
}
public string EndExecute(IAsyncResult asyncResult)
{
if (asyncResult == null)
throw new ArgumentNullException("asyncResult");
CommandAsyncResult commandAsyncResult = asyncResult as CommandAsyncResult;
if (commandAsyncResult == null || _asyncResult != commandAsyncResult)
throw new ArgumentException($"""{typeof(IAsyncResult).get_Name()}""");
lock (_endExecuteLock) {
if (commandAsyncResult.EndCalled)
throw new ArgumentException("EndExecute can only be called once for each asynchronous operation.");
WaitOnHandle(_asyncResult.AsyncWaitHandle);
UnsubscribeFromEventsAndDisposeChannel(_channel);
_channel = null;
commandAsyncResult.EndCalled = true;
return Result;
}
}
public string Execute()
{
return EndExecute(BeginExecute(null, null));
}
public void CancelAsync()
{
if (_channel != null && _channel.IsOpen && _asyncResult != null)
_channel.Dispose();
}
public string Execute(string commandText)
{
CommandText = commandText;
return Execute();
}
private IChannelSession CreateChannel()
{
IChannelSession channelSession = _session.CreateChannelSession();
channelSession.DataReceived += Channel_DataReceived;
channelSession.ExtendedDataReceived += Channel_ExtendedDataReceived;
channelSession.RequestReceived += Channel_RequestReceived;
channelSession.Closed += Channel_Closed;
return channelSession;
}
private void Session_Disconnected(object sender, EventArgs e)
{
if (!_isDisposed) {
_exception = new SshConnectionException("An established connection was aborted by the software in your host machine.", DisconnectReason.ConnectionLost);
_sessionErrorOccuredWaitHandle.Set();
}
}
private void Session_ErrorOccured(object sender, ExceptionEventArgs e)
{
if (!_isDisposed) {
_exception = e.Exception;
_sessionErrorOccuredWaitHandle.Set();
}
}
private void Channel_Closed(object sender, ChannelEventArgs e)
{
OutputStream?.Flush();
ExtendedOutputStream?.Flush();
_asyncResult.IsCompleted = true;
if (_callback != null)
ThreadAbstraction.ExecuteThread(delegate {
_callback(_asyncResult);
});
((EventWaitHandle)_asyncResult.AsyncWaitHandle).Set();
}
private void Channel_RequestReceived(object sender, ChannelRequestEventArgs e)
{
ExitStatusRequestInfo exitStatusRequestInfo = e.Info as ExitStatusRequestInfo;
if (exitStatusRequestInfo != null) {
ExitStatus = (int)exitStatusRequestInfo.ExitStatus;
if (exitStatusRequestInfo.WantReply) {
ChannelSuccessMessage message = new ChannelSuccessMessage(_channel.LocalChannelNumber);
_session.SendMessage(message);
}
} else if (e.Info.WantReply) {
ChannelFailureMessage message2 = new ChannelFailureMessage(_channel.LocalChannelNumber);
_session.SendMessage(message2);
}
}
private void Channel_ExtendedDataReceived(object sender, ChannelExtendedDataEventArgs e)
{
if (ExtendedOutputStream != null) {
ExtendedOutputStream.Write(e.Data, 0, e.Data.Length);
ExtendedOutputStream.Flush();
}
if (e.DataTypeCode == 1)
_hasError = true;
}
private void Channel_DataReceived(object sender, ChannelDataEventArgs e)
{
if (OutputStream != null) {
OutputStream.Write(e.Data, 0, e.Data.Length);
OutputStream.Flush();
}
if (_asyncResult != null) {
lock (_asyncResult) {
_asyncResult.BytesReceived += e.Data.Length;
}
}
}
private void WaitOnHandle(WaitHandle waitHandle)
{
switch (WaitHandle.WaitAny(new WaitHandle[2] {
_sessionErrorOccuredWaitHandle,
waitHandle
}, CommandTimeout)) {
case 0:
throw _exception;
case 258:
throw new SshOperationTimeoutException(string.Format(CultureInfo.CurrentCulture, "Command '{0}' has timed out.", CommandText));
}
}
private void UnsubscribeFromEventsAndDisposeChannel(IChannel channel)
{
if (channel != null) {
channel.DataReceived -= Channel_DataReceived;
channel.ExtendedDataReceived -= Channel_ExtendedDataReceived;
channel.RequestReceived -= Channel_RequestReceived;
channel.Closed -= Channel_Closed;
channel.Dispose();
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed && disposing) {
ISession session = _session;
if (session != null) {
session.Disconnected -= Session_Disconnected;
session.ErrorOccured -= Session_ErrorOccured;
_session = null;
}
IChannelSession channel = _channel;
if (channel != null) {
UnsubscribeFromEventsAndDisposeChannel(channel);
_channel = null;
}
Stream outputStream = OutputStream;
if (outputStream != null) {
outputStream.Dispose();
OutputStream = null;
}
Stream extendedOutputStream = ExtendedOutputStream;
if (extendedOutputStream != null) {
extendedOutputStream.Dispose();
ExtendedOutputStream = null;
}
EventWaitHandle sessionErrorOccuredWaitHandle = _sessionErrorOccuredWaitHandle;
if (sessionErrorOccuredWaitHandle != null) {
sessionErrorOccuredWaitHandle.Dispose();
_sessionErrorOccuredWaitHandle = null;
}
_isDisposed = true;
}
}
~SshCommand()
{
Dispose(false);
}
}
}