diff --git a/src/Renci.SshNet/ISshClient.cs b/src/Renci.SshNet/ISshClient.cs
index 1d6edfebf..602e15102 100644
--- a/src/Renci.SshNet/ISshClient.cs
+++ b/src/Renci.SshNet/ISshClient.cs
@@ -40,7 +40,7 @@ public interface ISshClient : IBaseClient
/// The command text.
/// object.
/// Client is not connected.
- public SshCommand CreateCommand(string commandText);
+ public ISshCommand CreateCommand(string commandText);
///
/// Creates the command to be executed with specified encoding.
@@ -51,7 +51,7 @@ public interface ISshClient : IBaseClient
/// This method will change current default encoding.
/// Client is not connected.
/// or is .
- public SshCommand CreateCommand(string commandText, Encoding encoding);
+ public ISshCommand CreateCommand(string commandText, Encoding encoding);
///
/// Creates and executes the command.
@@ -64,7 +64,7 @@ public interface ISshClient : IBaseClient
/// Asynchronous operation is already in progress.
/// Client is not connected.
/// is .
- public SshCommand RunCommand(string commandText);
+ public ISshCommand RunCommand(string commandText);
///
/// Creates the shell.
diff --git a/src/Renci.SshNet/ISshCommand.cs b/src/Renci.SshNet/ISshCommand.cs
new file mode 100644
index 000000000..364799f5d
--- /dev/null
+++ b/src/Renci.SshNet/ISshCommand.cs
@@ -0,0 +1,225 @@
+#nullable enable
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Renci.SshNet.Common;
+
+namespace Renci.SshNet
+{
+ ///
+ /// Represents an SSH command that can be executed.
+ ///
+ public interface ISshCommand : IDisposable
+ {
+ ///
+ /// Gets the command text.
+ ///
+ string CommandText { get; }
+
+ ///
+ /// Gets or sets the command timeout.
+ ///
+ ///
+ /// The command timeout.
+ ///
+ TimeSpan CommandTimeout { get; set; }
+
+ ///
+ /// Gets the number representing the exit status of the command, if applicable,
+ /// otherwise .
+ ///
+ ///
+ /// The value is not when an exit status code has been returned
+ /// from the server. If the command terminated due to a signal,
+ /// may be not instead.
+ ///
+ ///
+ int? ExitStatus { get; }
+
+ ///
+ /// Gets the name of the signal due to which the command
+ /// terminated violently, if applicable, otherwise .
+ ///
+ ///
+ /// The value (if it exists) is supplied by the server and is usually one of the
+ /// following, as described in https://datatracker.ietf.org/doc/html/rfc4254#section-6.10:
+ /// ABRT, ALRM, FPE, HUP, ILL, INT, KILL, PIPE, QUIT, SEGV, TER, USR1, USR2.
+ ///
+ string? ExitSignal { get; }
+
+ ///
+ /// Gets the output stream.
+ ///
+ Stream OutputStream { get; }
+
+ ///
+ /// Gets the extended output stream.
+ ///
+ Stream ExtendedOutputStream { get; }
+
+ ///
+ /// Creates and returns the input stream for the command.
+ ///
+ ///
+ /// The stream that can be used to transfer data to the command's input stream.
+ ///
+ ///
+ /// Callers should ensure that is called on the
+ /// returned instance in order to notify the command that no more data will be sent.
+ /// Failure to do so may result in the command executing indefinitely.
+ ///
+ ///
+ /// This example shows how to stream some data to 'cat' and have the server echo it back.
+ ///
+ /// using (SshCommand command = mySshClient.CreateCommand("cat"))
+ /// {
+ /// Task executeTask = command.ExecuteAsync(CancellationToken.None);
+ ///
+ /// using (Stream inputStream = command.CreateInputStream())
+ /// {
+ /// inputStream.Write("Hello World!"u8);
+ /// }
+ ///
+ /// await executeTask;
+ ///
+ /// Console.WriteLine(command.ExitStatus); // 0
+ /// Console.WriteLine(command.Result); // "Hello World!"
+ /// }
+ ///
+ ///
+ Stream CreateInputStream();
+
+ ///
+ /// Gets the standard output of the command by reading .
+ ///
+ string Result { get; }
+
+ ///
+ /// Gets the standard error of the command by reading ,
+ /// when extended data has been sent which has been marked as stderr.
+ ///
+ string Error { get; }
+
+ ///
+ /// Executes the command asynchronously.
+ ///
+ ///
+ /// The . When triggered, attempts to terminate the
+ /// remote command by sending a signal.
+ ///
+ /// A representing the lifetime of the command.
+ /// Command is already executing. Thrown synchronously.
+ /// Instance has been disposed. Thrown synchronously.
+ /// The has been cancelled.
+ /// The command timed out according to .
+ Task ExecuteAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// Begins an asynchronous command execution.
+ ///
+ ///
+ /// An that represents the asynchronous command execution, which could still be pending.
+ ///
+ /// Asynchronous operation is already in progress.
+ /// Invalid operation.
+ /// CommandText property is empty.
+ /// Client is not connected.
+ /// Operation has timed out.
+ IAsyncResult BeginExecute();
+
+ ///
+ /// Begins an asynchronous command execution.
+ ///
+ /// An optional asynchronous callback, to be called when the command execution is complete.
+ ///
+ /// An that represents the asynchronous command execution, which could still be pending.
+ ///
+ /// Asynchronous operation is already in progress.
+ /// Invalid operation.
+ /// CommandText property is empty.
+ /// Client is not connected.
+ /// Operation has timed out.
+ IAsyncResult BeginExecute(AsyncCallback? callback);
+
+ ///
+ /// Begins an asynchronous command execution.
+ ///
+ /// An optional asynchronous callback, to be called when the command execution is complete.
+ /// A user-provided object that distinguishes this particular asynchronous read request from other requests.
+ ///
+ /// An that represents the asynchronous command execution, which could still be pending.
+ ///
+ /// Asynchronous operation is already in progress.
+ /// Invalid operation.
+ /// CommandText property is empty.
+ /// Client is not connected.
+ /// Operation has timed out.
+ IAsyncResult BeginExecute(AsyncCallback? callback, object? state);
+
+ ///
+ /// Begins an asynchronous command execution.
+ ///
+ /// The command text.
+ /// An optional asynchronous callback, to be called when the command execution is complete.
+ /// A user-provided object that distinguishes this particular asynchronous read request from other requests.
+ ///
+ /// An that represents the asynchronous command execution, which could still be pending.
+ ///
+ /// Client is not connected.
+ /// Operation has timed out.
+ IAsyncResult BeginExecute(string commandText, AsyncCallback? callback, object? state);
+
+ ///
+ /// Waits for the pending asynchronous command execution to complete.
+ ///
+ /// The reference to the pending asynchronous request to finish.
+ /// .
+ /// does not correspond to the currently executing command.
+ /// is .
+ string EndExecute(IAsyncResult asyncResult);
+
+ ///
+ /// Cancels a running command by sending a signal to the remote process.
+ ///
+ /// if true send SIGKILL instead of SIGTERM.
+ /// Time to wait for the server to reply.
+ ///
+ ///
+ /// This method stops the command running on the server by sending a SIGTERM
+ /// (or SIGKILL, depending on ) signal to the remote
+ /// process. When the server implements signals, it will send a response which
+ /// populates with the signal with which the command terminated.
+ ///
+ ///
+ /// When the server does not implement signals, it may send no response. As a fallback,
+ /// this method waits up to for a response
+ /// and then completes the object anyway if there was none.
+ ///
+ ///
+ /// If the command has already finished (with or without cancellation), this method does
+ /// nothing.
+ ///
+ ///
+ /// Command has not been started.
+ void CancelAsync(bool forceKill = false, int millisecondsTimeout = 500);
+
+ ///
+ /// Executes the command specified by .
+ ///
+ /// .
+ /// Client is not connected.
+ /// Operation has timed out.
+ string Execute();
+
+ ///
+ /// Executes the specified command.
+ ///
+ /// The command text.
+ /// .
+ /// Client is not connected.
+ /// Operation has timed out.
+ string Execute(string commandText);
+ }
+}
diff --git a/src/Renci.SshNet/SshClient.cs b/src/Renci.SshNet/SshClient.cs
index 3051074e1..71d569df2 100644
--- a/src/Renci.SshNet/SshClient.cs
+++ b/src/Renci.SshNet/SshClient.cs
@@ -191,13 +191,13 @@ private static void DetachForwardedPort(ForwardedPort port)
}
///
- public SshCommand CreateCommand(string commandText)
+ public ISshCommand CreateCommand(string commandText)
{
return CreateCommand(commandText, ConnectionInfo.Encoding);
}
///
- public SshCommand CreateCommand(string commandText, Encoding encoding)
+ public ISshCommand CreateCommand(string commandText, Encoding encoding)
{
EnsureSessionIsOpen();
@@ -206,7 +206,7 @@ public SshCommand CreateCommand(string commandText, Encoding encoding)
}
///
- public SshCommand RunCommand(string commandText)
+ public ISshCommand RunCommand(string commandText)
{
var cmd = CreateCommand(commandText);
_ = cmd.Execute();
diff --git a/src/Renci.SshNet/SshCommand.cs b/src/Renci.SshNet/SshCommand.cs
index 6bfd57422..c70e5c48c 100644
--- a/src/Renci.SshNet/SshCommand.cs
+++ b/src/Renci.SshNet/SshCommand.cs
@@ -13,10 +13,8 @@
namespace Renci.SshNet
{
- ///
- /// Represents an SSH command that can be executed.
- ///
- public class SshCommand : IDisposable
+ ///
+ public class SshCommand : ISshCommand
{
private readonly ISession _session;
private readonly Encoding _encoding;
@@ -46,17 +44,10 @@ public class SshCommand : IDisposable
private int _exitStatus;
private volatile bool _haveExitStatus; // volatile to prevent re-ordering of reads/writes of _exitStatus.
- ///
- /// Gets the command text.
- ///
+ ///
public string CommandText { get; private set; }
- ///
- /// Gets or sets the command timeout.
- ///
- ///
- /// The command timeout.
- ///
+ ///
public TimeSpan CommandTimeout
{
get
@@ -71,16 +62,7 @@ public TimeSpan CommandTimeout
}
}
- ///
- /// Gets the number representing the exit status of the command, if applicable,
- /// otherwise .
- ///
- ///
- /// The value is not when an exit status code has been returned
- /// from the server. If the command terminated due to a signal,
- /// may be not instead.
- ///
- ///
+ ///
public int? ExitStatus
{
get
@@ -89,57 +71,16 @@ public int? ExitStatus
}
}
- ///
- /// Gets the name of the signal due to which the command
- /// terminated violently, if applicable, otherwise .
- ///
- ///
- /// The value (if it exists) is supplied by the server and is usually one of the
- /// following, as described in https://datatracker.ietf.org/doc/html/rfc4254#section-6.10:
- /// ABRT, ALRM, FPE, HUP, ILL, INT, KILL, PIPE, QUIT, SEGV, TER, USR1, USR2.
- ///
+ ///
public string? ExitSignal { get; private set; }
- ///
- /// Gets the output stream.
- ///
+ ///
public Stream OutputStream { get; private set; }
- ///
- /// Gets the extended output stream.
- ///
+ ///
public Stream ExtendedOutputStream { get; private set; }
- ///
- /// Creates and returns the input stream for the command.
- ///
- ///
- /// The stream that can be used to transfer data to the command's input stream.
- ///
- ///
- /// Callers should ensure that is called on the
- /// returned instance in order to notify the command that no more data will be sent.
- /// Failure to do so may result in the command executing indefinitely.
- ///
- ///
- /// This example shows how to stream some data to 'cat' and have the server echo it back.
- ///
- /// using (SshCommand command = mySshClient.CreateCommand("cat"))
- /// {
- /// Task executeTask = command.ExecuteAsync(CancellationToken.None);
- ///
- /// using (Stream inputStream = command.CreateInputStream())
- /// {
- /// inputStream.Write("Hello World!"u8);
- /// }
- ///
- /// await executeTask;
- ///
- /// Console.WriteLine(command.ExitStatus); // 0
- /// Console.WriteLine(command.Result); // "Hello World!"
- /// }
- ///
- ///
+ ///
public Stream CreateInputStream()
{
if (_channel == null)
@@ -156,9 +97,7 @@ public Stream CreateInputStream()
return _inputStream;
}
- ///
- /// Gets the standard output of the command by reading .
- ///
+ ///
public string Result
{
get
@@ -180,10 +119,7 @@ public string Result
}
}
- ///
- /// Gets the standard error of the command by reading ,
- /// when extended data has been sent which has been marked as stderr.
- ///
+ ///
public string Error
{
get
@@ -228,18 +164,7 @@ internal SshCommand(ISession session, string commandText, Encoding encoding)
_session.ErrorOccured += Session_ErrorOccured;
}
- ///
- /// Executes the command asynchronously.
- ///
- ///
- /// The . When triggered, attempts to terminate the
- /// remote command by sending a signal.
- ///
- /// A representing the lifetime of the command.
- /// Command is already executing. Thrown synchronously.
- /// Instance has been disposed. Thrown synchronously.
- /// The has been cancelled.
- /// The command timed out according to .
+ ///
#pragma warning disable CA1849 // Call async methods when in an async method; PipeStream.DisposeAsync would complete synchronously anyway.
public Task ExecuteAsync(CancellationToken cancellationToken = default)
{
@@ -307,68 +232,25 @@ public Task ExecuteAsync(CancellationToken cancellationToken = default)
}
#pragma warning restore CA1849
- ///
- /// Begins an asynchronous command execution.
- ///
- ///
- /// An that represents the asynchronous command execution, which could still be pending.
- ///
- /// Asynchronous operation is already in progress.
- /// Invalid operation.
- /// CommandText property is empty.
- /// Client is not connected.
- /// Operation has timed out.
+ ///
public IAsyncResult BeginExecute()
{
return BeginExecute(callback: null, state: null);
}
- ///
- /// Begins an asynchronous command execution.
- ///
- /// An optional asynchronous callback, to be called when the command execution is complete.
- ///
- /// An that represents the asynchronous command execution, which could still be pending.
- ///
- /// Asynchronous operation is already in progress.
- /// Invalid operation.
- /// CommandText property is empty.
- /// Client is not connected.
- /// Operation has timed out.
+ ///
public IAsyncResult BeginExecute(AsyncCallback? callback)
{
return BeginExecute(callback, state: null);
}
- ///
- /// Begins an asynchronous command execution.
- ///
- /// An optional asynchronous callback, to be called when the command execution is complete.
- /// A user-provided object that distinguishes this particular asynchronous read request from other requests.
- ///
- /// An that represents the asynchronous command execution, which could still be pending.
- ///
- /// Asynchronous operation is already in progress.
- /// Invalid operation.
- /// CommandText property is empty.
- /// Client is not connected.
- /// Operation has timed out.
+ ///
public IAsyncResult BeginExecute(AsyncCallback? callback, object? state)
{
return TaskToAsyncResult.Begin(ExecuteAsync(), callback, state);
}
- ///
- /// Begins an asynchronous command execution.
- ///
- /// The command text.
- /// An optional asynchronous callback, to be called when the command execution is complete.
- /// A user-provided object that distinguishes this particular asynchronous read request from other requests.
- ///
- /// An that represents the asynchronous command execution, which could still be pending.
- ///
- /// Client is not connected.
- /// Operation has timed out.
+ ///
public IAsyncResult BeginExecute(string commandText, AsyncCallback? callback, object? state)
{
ThrowHelper.ThrowIfNull(commandText);
@@ -378,13 +260,7 @@ public IAsyncResult BeginExecute(string commandText, AsyncCallback? callback, ob
return BeginExecute(callback, state);
}
- ///
- /// Waits for the pending asynchronous command execution to complete.
- ///
- /// The reference to the pending asynchronous request to finish.
- /// .
- /// does not correspond to the currently executing command.
- /// is .
+ ///
public string EndExecute(IAsyncResult asyncResult)
{
var executeTask = TaskToAsyncResult.Unwrap(asyncResult);
@@ -399,29 +275,7 @@ public string EndExecute(IAsyncResult asyncResult)
return Result;
}
- ///
- /// Cancels a running command by sending a signal to the remote process.
- ///
- /// if true send SIGKILL instead of SIGTERM.
- /// Time to wait for the server to reply.
- ///
- ///
- /// This method stops the command running on the server by sending a SIGTERM
- /// (or SIGKILL, depending on ) signal to the remote
- /// process. When the server implements signals, it will send a response which
- /// populates with the signal with which the command terminated.
- ///
- ///
- /// When the server does not implement signals, it may send no response. As a fallback,
- /// this method waits up to for a response
- /// and then completes the object anyway if there was none.
- ///
- ///
- /// If the command has already finished (with or without cancellation), this method does
- /// nothing.
- ///
- ///
- /// Command has not been started.
+ ///
public void CancelAsync(bool forceKill = false, int millisecondsTimeout = 500)
{
if (_tcs is null)
@@ -466,12 +320,7 @@ public void CancelAsync(bool forceKill = false, int millisecondsTimeout = 500)
SetAsyncComplete();
}
- ///
- /// Executes the command specified by .
- ///
- /// .
- /// Client is not connected.
- /// Operation has timed out.
+ ///
public string Execute()
{
ExecuteAsync().GetAwaiter().GetResult();
@@ -479,13 +328,7 @@ public string Execute()
return Result;
}
- ///
- /// Executes the specified command.
- ///
- /// The command text.
- /// .
- /// Client is not connected.
- /// Operation has timed out.
+ ///
public string Execute(string commandText)
{
CommandText = commandText;