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

SshCommand

public class SshCommand : IDisposable
Represents an SSH command that can be executed.
using Renci.SshNet.Channels; using Renci.SshNet.Common; using Renci.SshNet.Messages.Connection; using Renci.SshNet.Messages.Transport; using System; using System.IO; using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Renci.SshNet { [System.Runtime.CompilerServices.NullableContext(1)] [System.Runtime.CompilerServices.Nullable(0)] public class SshCommand : IDisposable { private readonly ISession _session; private readonly Encoding _encoding; [System.Runtime.CompilerServices.Nullable(2)] private IChannelSession _channel; [System.Runtime.CompilerServices.Nullable(new byte[] { 2, 1 })] private TaskCompletionSource<object> _tcs; [System.Runtime.CompilerServices.Nullable(2)] private CancellationTokenSource _cts; private CancellationTokenRegistration _tokenRegistration; [System.Runtime.CompilerServices.Nullable(2)] private string _stdOut; [System.Runtime.CompilerServices.Nullable(2)] private string _stdErr; private bool _hasError; private bool _isDisposed; [System.Runtime.CompilerServices.Nullable(2)] private ChannelInputStream _inputStream; private TimeSpan _commandTimeout; private CancellationToken _userToken; private bool _cancellationRequested; private int _exitStatus; private volatile bool _haveExitStatus; public string CommandText { get; set; } public TimeSpan CommandTimeout { get { return _commandTimeout; } set { value.EnsureValidTimeout("CommandTimeout"); _commandTimeout = value; } } public int? ExitStatus { get { if (!_haveExitStatus) return null; return _exitStatus; } } [System.Runtime.CompilerServices.Nullable(2)] [field: System.Runtime.CompilerServices.Nullable(2)] public string ExitSignal { [System.Runtime.CompilerServices.NullableContext(2)] get; [System.Runtime.CompilerServices.NullableContext(2)] private set; } public Stream OutputStream { get; set; } public Stream ExtendedOutputStream { get; set; } public string Result { get { if (_stdOut != null) return _stdOut; if (_tcs == null) return string.Empty; using (StreamReader streamReader = new StreamReader(OutputStream, _encoding)) return _stdOut = streamReader.ReadToEnd(); } } public string Error { get { if (_stdErr != null) return _stdErr; if (_tcs == null || !_hasError) return string.Empty; using (StreamReader streamReader = new StreamReader(ExtendedOutputStream, _encoding)) return _stdErr = streamReader.ReadToEnd(); } } public Stream CreateInputStream() { if (_channel == null) throw new InvalidOperationException("The input stream can be used only after calling BeginExecute and before calling EndExecute."); if (_inputStream != null) throw new InvalidOperationException("The input stream already exists."); _inputStream = new ChannelInputStream(_channel); return _inputStream; } internal SshCommand(ISession session, string commandText, Encoding encoding) { ThrowHelper.ThrowIfNull(session, "session"); ThrowHelper.ThrowIfNull(commandText, "commandText"); ThrowHelper.ThrowIfNull(encoding, "encoding"); _session = session; CommandText = commandText; _encoding = encoding; CommandTimeout = Timeout.InfiniteTimeSpan; OutputStream = new PipeStream(); ExtendedOutputStream = new PipeStream(); _session.Disconnected += Session_Disconnected; _session.ErrorOccured += Session_ErrorOccured; } public Task ExecuteAsync(CancellationToken cancellationToken = default(CancellationToken)) { ThrowHelper.ThrowObjectDisposedIf(_isDisposed, this); if (cancellationToken.IsCancellationRequested) return Task.FromCanceled(cancellationToken); if (_tcs != null) { if (!_tcs.Task.IsCompleted) throw new InvalidOperationException("Asynchronous operation is already in progress."); OutputStream.Dispose(); ExtendedOutputStream.Dispose(); OutputStream = new PipeStream(); ExtendedOutputStream = new PipeStream(); } _exitStatus = 0; _haveExitStatus = false; ExitSignal = null; _stdOut = null; _stdErr = null; _hasError = false; _tokenRegistration.Dispose(); _tokenRegistration = default(CancellationTokenRegistration); _cts?.Dispose(); _cts = null; _cancellationRequested = false; _tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously); _userToken = cancellationToken; _channel = _session.CreateChannelSession(); _channel.DataReceived += Channel_DataReceived; _channel.ExtendedDataReceived += Channel_ExtendedDataReceived; _channel.RequestReceived += Channel_RequestReceived; _channel.Closed += Channel_Closed; _channel.Open(); _channel.SendExecRequest(CommandText); if (CommandTimeout != Timeout.InfiniteTimeSpan) { _cts = CancellationTokenSource.CreateLinkedTokenSource(new CancellationToken[1] { cancellationToken }); _cts.CancelAfter(CommandTimeout); cancellationToken = _cts.Token; } if (cancellationToken.CanBeCanceled) _tokenRegistration = cancellationToken.Register(delegate(object cmd) { ((SshCommand)cmd).CancelAsync(false, 500); }, this); return _tcs.Task; } public IAsyncResult BeginExecute() { return BeginExecute(null, null); } public IAsyncResult BeginExecute([System.Runtime.CompilerServices.Nullable(2)] AsyncCallback callback) { return BeginExecute(callback, null); } [System.Runtime.CompilerServices.NullableContext(2)] [return: System.Runtime.CompilerServices.Nullable(1)] public IAsyncResult BeginExecute(AsyncCallback callback, object state) { return TaskToAsyncResult.Begin(ExecuteAsync(default(CancellationToken)), callback, state); } public IAsyncResult BeginExecute(string commandText, [System.Runtime.CompilerServices.Nullable(2)] AsyncCallback callback, [System.Runtime.CompilerServices.Nullable(2)] object state) { ThrowHelper.ThrowIfNull(commandText, "commandText"); CommandText = commandText; return BeginExecute(callback, state); } public string EndExecute(IAsyncResult asyncResult) { Task task = TaskToAsyncResult.Unwrap(asyncResult); TaskCompletionSource<object> tcs = _tcs; if (task != ((tcs != null) ? tcs.Task : null)) throw new ArgumentException("Argument does not correspond to the currently executing command.", "asyncResult"); task.GetAwaiter().GetResult(); return Result; } public void CancelAsync(bool forceKill = false, int millisecondsTimeout = 500) { if (_tcs == null) throw new InvalidOperationException("Command has not been started."); if (!_tcs.Task.IsCompleted) { _cancellationRequested = true; Interlocked.MemoryBarrier(); if ((_channel?.SendSignalRequest(forceKill ? "KILL" : "TERM")).HasValue) { try { if (_tcs.Task.Wait(millisecondsTimeout)) return; } catch (AggregateException) { return; } SetAsyncComplete(true); } } } public string Execute() { ExecuteAsync(default(CancellationToken)).GetAwaiter().GetResult(); return Result; } public string Execute(string commandText) { CommandText = commandText; return Execute(); } private void Session_Disconnected([System.Runtime.CompilerServices.Nullable(2)] object sender, EventArgs e) { _tcs?.TrySetException(new SshConnectionException("An established connection was aborted by the software in your host machine.", DisconnectReason.ConnectionLost)); SetAsyncComplete(false); } private void Session_ErrorOccured([System.Runtime.CompilerServices.Nullable(2)] object sender, ExceptionEventArgs e) { _tcs?.TrySetException(e.Exception); SetAsyncComplete(false); } private void SetAsyncComplete(bool setResult = true) { Interlocked.MemoryBarrier(); if (setResult) { if (_userToken.IsCancellationRequested) _tcs.TrySetCanceled(_userToken); else if (_cts?.Token.IsCancellationRequested ?? false) { _tcs.TrySetException(new SshOperationTimeoutException(string.Format("Command '{0}' timed out. ({1}: {2}).", CommandText, "CommandTimeout", CommandTimeout))); } else if (_cancellationRequested) { _tcs.TrySetCanceled(); } else { _tcs.TrySetResult(null); } } UnsubscribeFromEventsAndDisposeChannel(); OutputStream.Dispose(); ExtendedOutputStream.Dispose(); } private void Channel_Closed([System.Runtime.CompilerServices.Nullable(2)] object sender, ChannelEventArgs e) { SetAsyncComplete(true); } private void Channel_RequestReceived([System.Runtime.CompilerServices.Nullable(2)] object sender, ChannelRequestEventArgs e) { ExitStatusRequestInfo exitStatusRequestInfo = e.Info as ExitStatusRequestInfo; if (exitStatusRequestInfo != null) { _exitStatus = (int)exitStatusRequestInfo.ExitStatus; _haveExitStatus = true; } else { ExitSignalRequestInfo exitSignalRequestInfo = e.Info as ExitSignalRequestInfo; if (exitSignalRequestInfo != null) ExitSignal = exitSignalRequestInfo.SignalName; else if (e.Info.WantReply) { uint? nullable = _channel?.RemoteChannelNumber; if (nullable.HasValue) { uint valueOrDefault = nullable.GetValueOrDefault(); ChannelFailureMessage message = new ChannelFailureMessage(valueOrDefault); _session.SendMessage(message); } } } } private void Channel_ExtendedDataReceived([System.Runtime.CompilerServices.Nullable(2)] object sender, ChannelExtendedDataEventArgs e) { ExtendedOutputStream.Write(e.Data, 0, e.Data.Length); if (e.DataTypeCode == 1) _hasError = true; } private void Channel_DataReceived([System.Runtime.CompilerServices.Nullable(2)] object sender, ChannelDataEventArgs e) { OutputStream.Write(e.Data, 0, e.Data.Length); } private void UnsubscribeFromEventsAndDisposeChannel() { IChannelSession channel = _channel; if (channel != null) { _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) { _session.Disconnected -= Session_Disconnected; _session.ErrorOccured -= Session_ErrorOccured; UnsubscribeFromEventsAndDisposeChannel(); _inputStream?.Dispose(); _inputStream = null; OutputStream.Dispose(); ExtendedOutputStream.Dispose(); _tokenRegistration.Dispose(); _tokenRegistration = default(CancellationTokenRegistration); _cts?.Dispose(); _cts = null; TaskCompletionSource<object> tcs = _tcs; if (tcs != null) { Task<object> task = tcs.Task; if (task != null && !task.IsCompleted) tcs.TrySetException(new ObjectDisposedException(GetType().FullName)); } _isDisposed = true; } } } }