From 8fa7db2d439160170ce407d664cb779601f16826 Mon Sep 17 00:00:00 2001 From: Martin Hansen Date: Mon, 23 Mar 2026 12:43:49 +0100 Subject: [PATCH 1/3] Create interface and refactor --- src/Renci.SshNet/ISshClient.cs | 245 +---------- src/Renci.SshNet/SshClient.cs | 322 +------------- src/Renci.SshNet/SshCommand.cs | 654 ++++------------------------- src/Renci.SshNet/V2/ISshClient.cs | 246 +++++++++++ src/Renci.SshNet/V2/ISshCommand.cs | 225 ++++++++++ src/Renci.SshNet/V2/SshClient.cs | 328 +++++++++++++++ src/Renci.SshNet/V2/SshCommand.cs | 515 +++++++++++++++++++++++ 7 files changed, 1410 insertions(+), 1125 deletions(-) create mode 100644 src/Renci.SshNet/V2/ISshClient.cs create mode 100644 src/Renci.SshNet/V2/ISshCommand.cs create mode 100644 src/Renci.SshNet/V2/SshClient.cs create mode 100644 src/Renci.SshNet/V2/SshCommand.cs diff --git a/src/Renci.SshNet/ISshClient.cs b/src/Renci.SshNet/ISshClient.cs index 4507c316b..df774d290 100644 --- a/src/Renci.SshNet/ISshClient.cs +++ b/src/Renci.SshNet/ISshClient.cs @@ -1,246 +1,19 @@ -#nullable enable using System; -using System.Collections.Generic; -using System.IO; using System.Text; -using Renci.SshNet.Common; - namespace Renci.SshNet { - /// - /// Provides client connection to SSH server. - /// - public interface ISshClient : IBaseClient + /// + [Obsolete($"Use {nameof(V2.ISshClient)} instead.")] + public interface ISshClient : V2.ISshClient { - /// - /// Gets the list of forwarded ports. - /// - IEnumerable ForwardedPorts { get; } - - /// - /// Adds the forwarded port. - /// - /// The port. - /// Forwarded port is already added to a different client. - /// is . - /// Client is not connected. - public void AddForwardedPort(ForwardedPort port); - - /// - /// Stops and removes the forwarded port from the list. - /// - /// Forwarded port. - /// is . - public void RemoveForwardedPort(ForwardedPort port); - - /// - /// Creates the command to be executed. - /// - /// The command text. - /// object. - /// Client is not connected. - public SshCommand CreateCommand(string commandText); - - /// - /// Creates the command to be executed with specified encoding. - /// - /// The command text. - /// The encoding to use for results. - /// object which uses specified encoding. - /// This method will change current default encoding. - /// Client is not connected. - /// or is . - public SshCommand CreateCommand(string commandText, Encoding encoding); - - /// - /// Creates and executes the command. - /// - /// The command text. - /// Returns an instance of with execution results. - /// This method internally uses asynchronous calls. - /// CommandText property is empty. - /// Invalid Operation - An existing channel was used to execute this command. - /// Asynchronous operation is already in progress. - /// Client is not connected. - /// is . - public SshCommand RunCommand(string commandText); - - /// - /// Creates the shell. - /// - /// The input. - /// The output. - /// The extended output. - /// Name of the terminal. - /// The columns. - /// The rows. - /// The width. - /// The height. - /// The terminal mode. - /// Size of the internal read buffer. - /// - /// Returns a representation of a object. - /// - /// Client is not connected. - public Shell CreateShell(Stream input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary? terminalModes, int bufferSize); - - /// - /// Creates the shell. - /// - /// The input. - /// The output. - /// The extended output. - /// Name of the terminal. - /// The columns. - /// The rows. - /// The width. - /// The height. - /// The terminal mode. - /// - /// Returns a representation of a object. - /// - /// Client is not connected. - public Shell CreateShell(Stream input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary terminalModes); - - /// - /// Creates the shell. - /// - /// The input. - /// The output. - /// The extended output. - /// - /// Returns a representation of a object. - /// - /// Client is not connected. - public Shell CreateShell(Stream input, Stream output, Stream extendedOutput); - - /// - /// Creates the shell. - /// - /// The encoding to use to send the input. - /// The input. - /// The output. - /// The extended output. - /// Name of the terminal. - /// The columns. - /// The rows. - /// The width. - /// The height. - /// The terminal mode. - /// Size of the internal read buffer. - /// - /// Returns a representation of a object. - /// - /// Client is not connected. - public Shell CreateShell(Encoding encoding, string input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary? terminalModes, int bufferSize); - - /// - /// Creates the shell. - /// - /// The encoding. - /// The input. - /// The output. - /// The extended output. - /// Name of the terminal. - /// The columns. - /// The rows. - /// The width. - /// The height. - /// The terminal modes. - /// - /// Returns a representation of a object. - /// - /// Client is not connected. - public Shell CreateShell(Encoding encoding, string input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary terminalModes); - - /// - /// Creates the shell. - /// - /// The encoding. - /// The input. - /// The output. - /// The extended output. - /// - /// Returns a representation of a object. - /// - /// Client is not connected. - public Shell CreateShell(Encoding encoding, string input, Stream output, Stream extendedOutput); - - /// - /// Creates the shell without allocating a pseudo terminal, - /// similar to the ssh -T option. - /// - /// The input. - /// The output. - /// The extended output. - /// Size of the internal read buffer. - /// - /// Returns a representation of a object. - /// - /// Client is not connected. - public Shell CreateShellNoTerminal(Stream input, Stream output, Stream extendedOutput, int bufferSize = -1); - - /// - /// Creates the shell stream. - /// - /// The TERM environment variable. - /// The terminal width in columns. - /// The terminal height in rows. - /// The terminal width in pixels. - /// The terminal height in pixels. - /// The size of the buffer. - /// - /// The created instance. - /// - /// Client is not connected. - /// - /// - /// The TERM environment variable contains an identifier for the text window's capabilities. - /// You can get a detailed list of these capabilities by using the ‘infocmp’ command. - /// - /// - /// The column/row dimensions override the pixel dimensions (when nonzero). Pixel dimensions refer - /// to the drawable area of the window. - /// - /// - public ShellStream CreateShellStream(string terminalName, uint columns, uint rows, uint width, uint height, int bufferSize); + /// + public new SshCommand CreateCommand(string commandText); - /// - /// Creates the shell stream. - /// - /// The TERM environment variable. - /// The terminal width in columns. - /// The terminal height in rows. - /// The terminal width in pixels. - /// The terminal height in pixels. - /// The size of the buffer. - /// The terminal mode values. - /// - /// The created instance. - /// - /// Client is not connected. - /// - /// - /// The TERM environment variable contains an identifier for the text window's capabilities. - /// You can get a detailed list of these capabilities by using the ‘infocmp’ command. - /// - /// - /// The column/row dimensions override the pixel dimensions (when non-zero). Pixel dimensions refer - /// to the drawable area of the window. - /// - /// - public ShellStream CreateShellStream(string terminalName, uint columns, uint rows, uint width, uint height, int bufferSize, IDictionary? terminalModeValues); + /// + public new SshCommand CreateCommand(string commandText, Encoding encoding); - /// - /// Creates the shell stream without allocating a pseudo terminal, - /// similar to the ssh -T option. - /// - /// The size of the buffer. - /// - /// The created instance. - /// - /// Client is not connected. - public ShellStream CreateShellStreamNoTerminal(int bufferSize = -1); + /// + public new SshCommand RunCommand(string commandText); } } diff --git a/src/Renci.SshNet/SshClient.cs b/src/Renci.SshNet/SshClient.cs index 331ccaf69..cbda3d5fc 100644 --- a/src/Renci.SshNet/SshClient.cs +++ b/src/Renci.SshNet/SshClient.cs @@ -1,339 +1,45 @@ -#nullable enable using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Text; - -using Renci.SshNet.Common; namespace Renci.SshNet { - /// - public class SshClient : BaseClient, ISshClient + /// + [Obsolete($"Use {nameof(V2.SshClient)} instead.")] + public class SshClient : V2.SshClient { - /// - /// Holds the list of forwarded ports. - /// - private readonly List _forwardedPorts; - - /// - /// Holds a value indicating whether the current instance is disposed. - /// - /// - /// if the current instance is disposed; otherwise, . - /// - private bool _isDisposed; - - private MemoryStream? _inputStream; - /// - public IEnumerable ForwardedPorts - { - get - { - return _forwardedPorts.AsReadOnly(); - } - } - - /// - /// Initializes a new instance of the class. - /// - /// The connection info. - /// is . public SshClient(ConnectionInfo connectionInfo) - : this(connectionInfo, ownsConnectionInfo: false) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Connection host. - /// Connection port. - /// Authentication username. - /// Authentication password. - /// is . - /// is invalid, or is or contains only whitespace characters. - /// is not within and . - public SshClient(string host, int port, string username, string password) - : this(new PasswordConnectionInfo(host, port, username, password), ownsConnectionInfo: true) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Connection host. - /// Authentication username. - /// Authentication password. - /// is . - /// is invalid, or is or contains only whitespace characters. - public SshClient(string host, string username, string password) - : this(host, ConnectionInfo.DefaultPort, username, password) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Connection host. - /// Connection port. - /// Authentication username. - /// Authentication private key file(s) . - /// is . - /// is invalid, -or- is or contains only whitespace characters. - /// is not within and . - public SshClient(string host, int port, string username, params IPrivateKeySource[] keyFiles) - : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles), ownsConnectionInfo: true) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Connection host. - /// Authentication username. - /// Authentication private key file(s) . - /// is . - /// is invalid, -or- is or contains only whitespace characters. - public SshClient(string host, string username, params IPrivateKeySource[] keyFiles) - : this(host, ConnectionInfo.DefaultPort, username, keyFiles) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The connection info. - /// Specified whether this instance owns the connection info. - /// is . - /// - /// If is , then the - /// connection info will be disposed when this instance is disposed. - /// - private SshClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo) - : this(connectionInfo, ownsConnectionInfo, new ServiceFactory()) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The connection info. - /// Specified whether this instance owns the connection info. - /// The factory to use for creating new services. - /// is . - /// is . - /// - /// If is , then the - /// connection info will be disposed when this instance is disposed. - /// - internal SshClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo, IServiceFactory serviceFactory) - : base(connectionInfo, ownsConnectionInfo, serviceFactory) - { - _forwardedPorts = new List(); - } - - /// - /// Called when client is disconnecting from the server. - /// - protected override void OnDisconnecting() - { - base.OnDisconnecting(); - - foreach (var port in _forwardedPorts) - { - port.Stop(); - } - } - - /// - public void AddForwardedPort(ForwardedPort port) - { - ArgumentNullException.ThrowIfNull(port); - - EnsureSessionIsOpen(); - - AttachForwardedPort(port); - _forwardedPorts.Add(port); - } - - /// - public void RemoveForwardedPort(ForwardedPort port) - { - ArgumentNullException.ThrowIfNull(port); - - // Stop port forwarding before removing it - port.Stop(); - - DetachForwardedPort(port); - _ = _forwardedPorts.Remove(port); - } - - private void AttachForwardedPort(ForwardedPort port) - { - if (port.Session != null && port.Session != Session) - { - throw new InvalidOperationException("Forwarded port is already added to a different client."); - } - - port.Session = Session; - } - - private static void DetachForwardedPort(ForwardedPort port) - { - port.Session = null; - } - - /// - public SshCommand CreateCommand(string commandText) - { - return CreateCommand(commandText, ConnectionInfo.Encoding); - } - - /// - public SshCommand CreateCommand(string commandText, Encoding encoding) + : base(connectionInfo) { - EnsureSessionIsOpen(); - - ConnectionInfo.Encoding = encoding; - return new SshCommand(Session!, commandText, encoding); - } - - /// - public SshCommand RunCommand(string commandText) - { - var cmd = CreateCommand(commandText); - _ = cmd.Execute(); - return cmd; - } - - /// - public Shell CreateShell(Stream input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary? terminalModes, int bufferSize) - { - EnsureSessionIsOpen(); - - return new Shell(Session, input, output, extendedOutput, terminalName, columns, rows, width, height, terminalModes, bufferSize); } /// - public Shell CreateShell(Stream input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary terminalModes) - { - return CreateShell(input, output, extendedOutput, terminalName, columns, rows, width, height, terminalModes, 1024); - } - - /// - public Shell CreateShell(Stream input, Stream output, Stream extendedOutput) - { - return CreateShell(input, output, extendedOutput, string.Empty, 0, 0, 0, 0, terminalModes: null, 1024); - } - - /// - public Shell CreateShell(Encoding encoding, string input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary? terminalModes, int bufferSize) - { - /* - * TODO Issue #1224: let shell dispose of input stream when we own the stream! - */ - - _inputStream = new MemoryStream(); - - using (var writer = new StreamWriter(_inputStream, encoding, bufferSize: 1024, leaveOpen: true)) - { - writer.Write(input); - writer.Flush(); - } - - _ = _inputStream.Seek(0, SeekOrigin.Begin); - - return CreateShell(_inputStream, output, extendedOutput, terminalName, columns, rows, width, height, terminalModes, bufferSize); - } - - /// - public Shell CreateShell(Encoding encoding, string input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary terminalModes) - { - return CreateShell(encoding, input, output, extendedOutput, terminalName, columns, rows, width, height, terminalModes, 1024); - } - - /// - public Shell CreateShell(Encoding encoding, string input, Stream output, Stream extendedOutput) + public SshClient(string host, string username, string password) + : base(host, username, password) { - return CreateShell(encoding, input, output, extendedOutput, string.Empty, 0, 0, 0, 0, terminalModes: null, 1024); } /// - public Shell CreateShellNoTerminal(Stream input, Stream output, Stream extendedOutput, int bufferSize = -1) + public SshClient(string host, string username, params IPrivateKeySource[] keyFiles) + : base(host, username, keyFiles) { - EnsureSessionIsOpen(); - - return new Shell(Session, input, output, extendedOutput, bufferSize); } /// - public ShellStream CreateShellStream(string terminalName, uint columns, uint rows, uint width, uint height, int bufferSize) + public SshClient(string host, int port, string username, string password) + : base(host, port, username, password) { - return CreateShellStream(terminalName, columns, rows, width, height, bufferSize, terminalModeValues: null); } /// - public ShellStream CreateShellStream(string terminalName, uint columns, uint rows, uint width, uint height, int bufferSize, IDictionary? terminalModeValues) + public SshClient(string host, int port, string username, params IPrivateKeySource[] keyFiles) + : base(host, port, username, keyFiles) { - EnsureSessionIsOpen(); - - return ServiceFactory.CreateShellStream(Session, terminalName, columns, rows, width, height, terminalModeValues, bufferSize); } /// - public ShellStream CreateShellStreamNoTerminal(int bufferSize = -1) - { - EnsureSessionIsOpen(); - - return ServiceFactory.CreateShellStreamNoTerminal(Session, bufferSize); - } - - /// - /// Stops forwarded ports. - /// - protected override void OnDisconnected() - { - base.OnDisconnected(); - - for (var i = _forwardedPorts.Count - 1; i >= 0; i--) - { - var port = _forwardedPorts[i]; - DetachForwardedPort(port); - _forwardedPorts.RemoveAt(i); - } - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// to release both managed and unmanaged resources; to release only unmanaged resources. - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - - if (_isDisposed) - { - return; - } - - if (disposing) - { - _inputStream?.Dispose(); - _inputStream = null; - - _isDisposed = true; - } - } - - private void EnsureSessionIsOpen() + internal SshClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo, IServiceFactory serviceFactory) + : base(connectionInfo, ownsConnectionInfo, serviceFactory) { - if (Session is null) - { - throw new SshConnectionException("Client not connected."); - } } } } diff --git a/src/Renci.SshNet/SshCommand.cs b/src/Renci.SshNet/SshCommand.cs index ce1042244..a11f00049 100644 --- a/src/Renci.SshNet/SshCommand.cs +++ b/src/Renci.SshNet/SshCommand.cs @@ -1,672 +1,164 @@ -#nullable enable +#nullable enable + using System; -using System.Diagnostics; using System.IO; -using System.Text; using System.Threading; using System.Threading.Tasks; -using Renci.SshNet.Channels; -using Renci.SshNet.Common; -using Renci.SshNet.Messages.Connection; -using Renci.SshNet.Messages.Transport; - namespace Renci.SshNet { - /// - /// Represents an SSH command that can be executed. - /// - public sealed class SshCommand : IDisposable + /// + [Obsolete($"Use {nameof(V2.ISshCommand)} instead.")] + public sealed class SshCommand : V2.ISshCommand { - private readonly ISession _session; - private readonly Encoding _encoding; - - private IChannelSession _channel; - private TaskCompletionSource? _tcs; - private CancellationTokenSource? _cts; - private CancellationTokenRegistration _tokenRegistration; - private string? _stdOut; - private string? _stdErr; - private bool _hasError; - private bool _isDisposed; - private ChannelInputStream? _inputStream; - private TimeSpan _commandTimeout; - - /// - /// The token supplied as an argument to . - /// - private CancellationToken _userToken; - - /// - /// Whether has been called - /// (either by a token or manually). - /// - private bool _cancellationRequested; + private readonly V2.SshCommand _sshCommand; - private int _exitStatus; - private volatile bool _haveExitStatus; // volatile to prevent re-ordering of reads/writes of _exitStatus. + internal SshCommand(V2.SshCommand sshCommand) + { + _sshCommand = sshCommand; + } - /// - /// Gets the command text. - /// - public string CommandText { get; private set; } + /// + public string CommandText + { + get + { + return _sshCommand.CommandText; + } + } - /// - /// Gets or sets the command timeout. - /// - /// - /// The command timeout. - /// + /// public TimeSpan CommandTimeout { get { - return _commandTimeout; + return _sshCommand.CommandTimeout; } + set { - value.EnsureValidTimeout(nameof(CommandTimeout)); - - _commandTimeout = value; + _sshCommand.CommandTimeout = value; } } - /// - /// 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 { - return _haveExitStatus ? _exitStatus : null; + return _sshCommand.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() + /// + public string? ExitSignal { - if (!_channel.IsOpen) - { - throw new InvalidOperationException("The input stream can be used only during execution."); - } - - if (_inputStream != null) + get { - throw new InvalidOperationException("The input stream already exists."); + return _sshCommand.ExitSignal; } - - _inputStream = new ChannelInputStream(_channel); - return _inputStream; } - /// - /// Gets the standard output of the command by reading . - /// - public string Result + /// + public Stream OutputStream { get { - if (_stdOut is not null) - { - return _stdOut; - } - - if (_tcs is null) - { - return string.Empty; - } - - using (var sr = new StreamReader(OutputStream, _encoding)) - { - return _stdOut = sr.ReadToEnd(); - } + return _sshCommand.OutputStream; } } - /// - /// Gets the standard error of the command by reading , - /// when extended data has been sent which has been marked as stderr. - /// - public string Error + /// + public Stream ExtendedOutputStream { get { - if (_stdErr is not null) - { - return _stdErr; - } - - if (_tcs is null || !_hasError) - { - return string.Empty; - } - - using (var sr = new StreamReader(ExtendedOutputStream, _encoding)) - { - return _stdErr = sr.ReadToEnd(); - } + return _sshCommand.ExtendedOutputStream; } } - /// - /// Initializes a new instance of the class. - /// - /// The session. - /// The command text. - /// The encoding to use for the results. - /// Either , is . - internal SshCommand(ISession session, string commandText, Encoding encoding) - { - ArgumentNullException.ThrowIfNull(session); - ArgumentNullException.ThrowIfNull(commandText); - ArgumentNullException.ThrowIfNull(encoding); - - _session = session; - CommandText = commandText; - _encoding = encoding; - CommandTimeout = Timeout.InfiniteTimeSpan; - OutputStream = new PipeStream(); - ExtendedOutputStream = new PipeStream(); - _session.Disconnected += Session_Disconnected; - _session.ErrorOccured += Session_ErrorOccurred; - _channel = _session.CreateChannelSession(); - } - - /// - /// 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) + /// + public string Result { - ObjectDisposedException.ThrowIf(_isDisposed, this); - - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - - if (_tcs is not null) - { - if (!_tcs.Task.IsCompleted) - { - throw new InvalidOperationException("Asynchronous operation is already in progress."); - } - - UnsubscribeFromChannelEvents(dispose: true); - - OutputStream.Dispose(); - ExtendedOutputStream.Dispose(); - - // Initialise output streams. We already initialised them for the first - // execution in the constructor (to allow passing them around before execution) - // so we just need to reinitialise them for subsequent executions. - OutputStream = new PipeStream(); - ExtendedOutputStream = new PipeStream(); - _channel = _session.CreateChannelSession(); - } - - _exitStatus = default; - _haveExitStatus = false; - ExitSignal = null; - _stdOut = null; - _stdErr = null; - _hasError = false; - _tokenRegistration.Dispose(); - _tokenRegistration = default; - _cts?.Dispose(); - _cts = null; - _cancellationRequested = false; - - _tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - _userToken = cancellationToken; - - _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) + get { - _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - _cts.CancelAfter(CommandTimeout); - cancellationToken = _cts.Token; + return _sshCommand.Result; } + } - if (cancellationToken.CanBeCanceled) + /// + public string Error + { + get { - _tokenRegistration = cancellationToken.Register(static cmd => - { - try - { - ((SshCommand)cmd!).CancelAsync(); - } - catch - { - // Swallow exceptions which would otherwise be unhandled. - } - }, - this); + return _sshCommand.Error; } - - return _tcs.Task; } -#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); + return _sshCommand.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. + /// public IAsyncResult BeginExecute(AsyncCallback? callback) { - return BeginExecute(callback, state: null); + return _sshCommand.BeginExecute(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. + /// public IAsyncResult BeginExecute(AsyncCallback? callback, object? state) { - return TaskToAsyncResult.Begin(ExecuteAsync(), callback, state); + return _sshCommand.BeginExecute(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) { - ArgumentNullException.ThrowIfNull(commandText); - - CommandText = commandText; - - 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); - - if (executeTask != _tcs?.Task) - { - throw new ArgumentException("Argument does not correspond to the currently executing command.", nameof(asyncResult)); - } - - executeTask.GetAwaiter().GetResult(); - - return Result; + return _sshCommand.BeginExecute(commandText, callback, state); } - /// - /// 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) - { - throw new InvalidOperationException("Command has not been started."); - } - - if (_tcs.Task.IsCompleted) - { - return; - } - - _cancellationRequested = true; - Interlocked.MemoryBarrier(); // ensure fresh read in SetAsyncComplete (possibly unnecessary) - - try - { - // Try to send the cancellation signal. - if (_channel?.SendSignalRequest(forceKill ? "KILL" : "TERM") is null) - { - // Command has completed (in the meantime since the last check). - return; - } - - // Having sent the "signal" message, we expect to receive "exit-signal" - // and then a close message. But since a server may not implement signals, - // we can't guarantee that, so we wait a short time for that to happen and - // if it doesn't, just complete the task ourselves to unblock waiters. - - _ = _tcs.Task.Wait(millisecondsTimeout); - } - catch (AggregateException) - { - // We expect to be here from the call to Wait if the server implements signals. - // But we don't want to propagate the exception on the task from here. - } - finally - { - SetAsyncComplete(); - } - } - - /// - /// Executes the command specified by . - /// - /// . - /// Client is not connected. - /// Operation has timed out. - public string Execute() - { - ExecuteAsync().GetAwaiter().GetResult(); - - return Result; - } - - /// - /// Executes the specified command. - /// - /// The command text. - /// . - /// Client is not connected. - /// Operation has timed out. - public string Execute(string commandText) - { - CommandText = commandText; - - return Execute(); - } - - private void Session_Disconnected(object? sender, EventArgs e) - { - _ = _tcs?.TrySetException(new SshConnectionException("An established connection was aborted by the software in your host machine.", DisconnectReason.ConnectionLost)); - - SetAsyncComplete(setResult: false); - } - - private void Session_ErrorOccurred(object? sender, ExceptionEventArgs e) - { - _ = _tcs?.TrySetException(e.Exception); - - SetAsyncComplete(setResult: false); - } - - private void SetAsyncComplete(bool setResult = true) - { - Interlocked.MemoryBarrier(); // ensure fresh read of _cancellationRequested (possibly unnecessary) - - if (setResult) - { - Debug.Assert(_tcs is not null, "Should only be completing the task if we've started one."); - - if (_userToken.IsCancellationRequested) - { - _ = _tcs.TrySetCanceled(_userToken); - } - else if (_cts?.Token.IsCancellationRequested == true) - { - _ = _tcs.TrySetException(new SshOperationTimeoutException($"Command '{CommandText}' timed out. ({nameof(CommandTimeout)}: {CommandTimeout}).")); - } - else if (_cancellationRequested) - { - _ = _tcs.TrySetCanceled(); - } - else - { - _ = _tcs.TrySetResult(null!); - } - } - - // We don't dispose the channel here to avoid a race condition - // where SSH_MSG_CHANNEL_CLOSE arrives before _channel starts - // waiting for a response in _channel.SendExecRequest(). - UnsubscribeFromChannelEvents(dispose: false); - - OutputStream.Dispose(); - ExtendedOutputStream.Dispose(); - } - - private void Channel_Closed(object? sender, ChannelEventArgs e) - { - SetAsyncComplete(); + _sshCommand.CancelAsync(forceKill, millisecondsTimeout); } - private void Channel_RequestReceived(object? sender, ChannelRequestEventArgs e) + /// + public Stream CreateInputStream() { - if (e.Info is ExitStatusRequestInfo exitStatusInfo) - { - _exitStatus = (int)exitStatusInfo.ExitStatus; - _haveExitStatus = true; - - Debug.Assert(!exitStatusInfo.WantReply, "exit-status is want_reply := false by definition."); - } - else if (e.Info is ExitSignalRequestInfo exitSignalInfo) - { - ExitSignal = exitSignalInfo.SignalName; - - Debug.Assert(!exitSignalInfo.WantReply, "exit-signal is want_reply := false by definition."); - } - else if (e.Info.WantReply && sender is IChannel { RemoteChannelNumber: uint remoteChannelNumber }) - { - var replyMessage = new ChannelFailureMessage(remoteChannelNumber); - _session.SendMessage(replyMessage); - } + return _sshCommand.CreateInputStream(); } - private void Channel_ExtendedDataReceived(object? sender, ChannelExtendedDataEventArgs e) + /// + public void Dispose() { - ExtendedOutputStream.Write(e.Data.Array!, e.Data.Offset, e.Data.Count); - - if (e.DataTypeCode == 1) - { - _hasError = true; - } + _sshCommand.Dispose(); } - private void Channel_DataReceived(object? sender, ChannelDataEventArgs e) + /// + public string EndExecute(IAsyncResult asyncResult) { - OutputStream.Write(e.Data.Array!, e.Data.Offset, e.Data.Count); + return _sshCommand.EndExecute(asyncResult); } - /// - /// Unsubscribes the current from channel events, and optionally, - /// disposes . - /// - private void UnsubscribeFromChannelEvents(bool dispose) + /// + public string Execute() { - var channel = _channel; - - // unsubscribe from events as we do not want to be signaled should these get fired - // during the dispose of the channel - channel.DataReceived -= Channel_DataReceived; - channel.ExtendedDataReceived -= Channel_ExtendedDataReceived; - channel.RequestReceived -= Channel_RequestReceived; - channel.Closed -= Channel_Closed; - - if (dispose) - { - channel.Dispose(); - } + return _sshCommand.Execute(); } - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() + /// + public string Execute(string commandText) { - Dispose(disposing: true); - GC.SuppressFinalize(this); + return _sshCommand.Execute(commandText); } - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// to release both managed and unmanaged resources; to release only unmanaged resources. - private void Dispose(bool disposing) + /// + public Task ExecuteAsync(CancellationToken cancellationToken = default) { - if (_isDisposed) - { - return; - } - - if (disposing) - { - // unsubscribe from session events to ensure other objects that we're going to dispose - // are not accessed while disposing - _session.Disconnected -= Session_Disconnected; - _session.ErrorOccured -= Session_ErrorOccurred; - - // unsubscribe from channel events to ensure other objects that we're going to dispose - // are not accessed while disposing - UnsubscribeFromChannelEvents(dispose: true); - - _inputStream?.Dispose(); - _inputStream = null; - - OutputStream.Dispose(); - ExtendedOutputStream.Dispose(); - - _tokenRegistration.Dispose(); - _tokenRegistration = default; - _cts?.Dispose(); - _cts = null; - - if (_tcs is { Task.IsCompleted: false } tcs) - { - // In case an operation is still running, try to complete it with an ObjectDisposedException. - _ = tcs.TrySetException(new ObjectDisposedException(GetType().FullName)); - } - - _isDisposed = true; - } + return _sshCommand.ExecuteAsync(cancellationToken); } } -} +} \ No newline at end of file diff --git a/src/Renci.SshNet/V2/ISshClient.cs b/src/Renci.SshNet/V2/ISshClient.cs new file mode 100644 index 000000000..2542b5d30 --- /dev/null +++ b/src/Renci.SshNet/V2/ISshClient.cs @@ -0,0 +1,246 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +using Renci.SshNet.Common; + +namespace Renci.SshNet.V2 +{ + /// + /// Provides client connection to SSH server. + /// + public interface ISshClient : IBaseClient + { + /// + /// Gets the list of forwarded ports. + /// + IEnumerable ForwardedPorts { get; } + + /// + /// Adds the forwarded port. + /// + /// The port. + /// Forwarded port is already added to a different client. + /// is . + /// Client is not connected. + public void AddForwardedPort(ForwardedPort port); + + /// + /// Stops and removes the forwarded port from the list. + /// + /// Forwarded port. + /// is . + public void RemoveForwardedPort(ForwardedPort port); + + /// + /// Creates the command to be executed. + /// + /// The command text. + /// object. + /// Client is not connected. + public ISshCommand CreateCommand(string commandText); + + /// + /// Creates the command to be executed with specified encoding. + /// + /// The command text. + /// The encoding to use for results. + /// object which uses specified encoding. + /// This method will change current default encoding. + /// Client is not connected. + /// or is . + public ISshCommand CreateCommand(string commandText, Encoding encoding); + + /// + /// Creates and executes the command. + /// + /// The command text. + /// Returns an instance of with execution results. + /// This method internally uses asynchronous calls. + /// CommandText property is empty. + /// Invalid Operation - An existing channel was used to execute this command. + /// Asynchronous operation is already in progress. + /// Client is not connected. + /// is . + public ISshCommand RunCommand(string commandText); + + /// + /// Creates the shell. + /// + /// The input. + /// The output. + /// The extended output. + /// Name of the terminal. + /// The columns. + /// The rows. + /// The width. + /// The height. + /// The terminal mode. + /// Size of the internal read buffer. + /// + /// Returns a representation of a object. + /// + /// Client is not connected. + public Shell CreateShell(Stream input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary? terminalModes, int bufferSize); + + /// + /// Creates the shell. + /// + /// The input. + /// The output. + /// The extended output. + /// Name of the terminal. + /// The columns. + /// The rows. + /// The width. + /// The height. + /// The terminal mode. + /// + /// Returns a representation of a object. + /// + /// Client is not connected. + public Shell CreateShell(Stream input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary terminalModes); + + /// + /// Creates the shell. + /// + /// The input. + /// The output. + /// The extended output. + /// + /// Returns a representation of a object. + /// + /// Client is not connected. + public Shell CreateShell(Stream input, Stream output, Stream extendedOutput); + + /// + /// Creates the shell. + /// + /// The encoding to use to send the input. + /// The input. + /// The output. + /// The extended output. + /// Name of the terminal. + /// The columns. + /// The rows. + /// The width. + /// The height. + /// The terminal mode. + /// Size of the internal read buffer. + /// + /// Returns a representation of a object. + /// + /// Client is not connected. + public Shell CreateShell(Encoding encoding, string input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary? terminalModes, int bufferSize); + + /// + /// Creates the shell. + /// + /// The encoding. + /// The input. + /// The output. + /// The extended output. + /// Name of the terminal. + /// The columns. + /// The rows. + /// The width. + /// The height. + /// The terminal modes. + /// + /// Returns a representation of a object. + /// + /// Client is not connected. + public Shell CreateShell(Encoding encoding, string input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary terminalModes); + + /// + /// Creates the shell. + /// + /// The encoding. + /// The input. + /// The output. + /// The extended output. + /// + /// Returns a representation of a object. + /// + /// Client is not connected. + public Shell CreateShell(Encoding encoding, string input, Stream output, Stream extendedOutput); + + /// + /// Creates the shell without allocating a pseudo terminal, + /// similar to the ssh -T option. + /// + /// The input. + /// The output. + /// The extended output. + /// Size of the internal read buffer. + /// + /// Returns a representation of a object. + /// + /// Client is not connected. + public Shell CreateShellNoTerminal(Stream input, Stream output, Stream extendedOutput, int bufferSize = -1); + + /// + /// Creates the shell stream. + /// + /// The TERM environment variable. + /// The terminal width in columns. + /// The terminal height in rows. + /// The terminal width in pixels. + /// The terminal height in pixels. + /// The size of the buffer. + /// + /// The created instance. + /// + /// Client is not connected. + /// + /// + /// The TERM environment variable contains an identifier for the text window's capabilities. + /// You can get a detailed list of these capabilities by using the ‘infocmp’ command. + /// + /// + /// The column/row dimensions override the pixel dimensions (when nonzero). Pixel dimensions refer + /// to the drawable area of the window. + /// + /// + public ShellStream CreateShellStream(string terminalName, uint columns, uint rows, uint width, uint height, int bufferSize); + + /// + /// Creates the shell stream. + /// + /// The TERM environment variable. + /// The terminal width in columns. + /// The terminal height in rows. + /// The terminal width in pixels. + /// The terminal height in pixels. + /// The size of the buffer. + /// The terminal mode values. + /// + /// The created instance. + /// + /// Client is not connected. + /// + /// + /// The TERM environment variable contains an identifier for the text window's capabilities. + /// You can get a detailed list of these capabilities by using the ‘infocmp’ command. + /// + /// + /// The column/row dimensions override the pixel dimensions (when non-zero). Pixel dimensions refer + /// to the drawable area of the window. + /// + /// + public ShellStream CreateShellStream(string terminalName, uint columns, uint rows, uint width, uint height, int bufferSize, IDictionary? terminalModeValues); + + /// + /// Creates the shell stream without allocating a pseudo terminal, + /// similar to the ssh -T option. + /// + /// The size of the buffer. + /// + /// The created instance. + /// + /// Client is not connected. + public ShellStream CreateShellStreamNoTerminal(int bufferSize = -1); + } +} diff --git a/src/Renci.SshNet/V2/ISshCommand.cs b/src/Renci.SshNet/V2/ISshCommand.cs new file mode 100644 index 000000000..e663ce9f2 --- /dev/null +++ b/src/Renci.SshNet/V2/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.V2 +{ + /// + /// 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; } + + /// + /// 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; } + + /// + /// 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); + + /// + /// 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); + + /// + /// 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 (ISshCommand 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(); + + /// + /// 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); + + /// + /// 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); + + /// + /// 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); + } +} diff --git a/src/Renci.SshNet/V2/SshClient.cs b/src/Renci.SshNet/V2/SshClient.cs new file mode 100644 index 000000000..df805bde7 --- /dev/null +++ b/src/Renci.SshNet/V2/SshClient.cs @@ -0,0 +1,328 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; + +using Renci.SshNet.Common; + +namespace Renci.SshNet.V2 +{ + /// + public class SshClient : BaseClient, ISshClient + { + private readonly List _forwardedPorts; + private bool _isDisposed; + private MemoryStream? _inputStream; + + /// + public IEnumerable ForwardedPorts + { + get + { + return _forwardedPorts.AsReadOnly(); + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The connection info. + /// is . + public SshClient(ConnectionInfo connectionInfo) + : this(connectionInfo, ownsConnectionInfo: false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Connection host. + /// Connection port. + /// Authentication username. + /// Authentication password. + /// is . + /// is invalid, or is or contains only whitespace characters. + /// is not within and . + public SshClient(string host, int port, string username, string password) + : this(new PasswordConnectionInfo(host, port, username, password), ownsConnectionInfo: true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Connection host. + /// Authentication username. + /// Authentication password. + /// is . + /// is invalid, or is or contains only whitespace characters. + public SshClient(string host, string username, string password) + : this(host, ConnectionInfo.DefaultPort, username, password) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Connection host. + /// Connection port. + /// Authentication username. + /// Authentication private key file(s) . + /// is . + /// is invalid, -or- is or contains only whitespace characters. + /// is not within and . + public SshClient(string host, int port, string username, params IPrivateKeySource[] keyFiles) + : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles), ownsConnectionInfo: true) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Connection host. + /// Authentication username. + /// Authentication private key file(s) . + /// is . + /// is invalid, -or- is or contains only whitespace characters. + public SshClient(string host, string username, params IPrivateKeySource[] keyFiles) + : this(host, ConnectionInfo.DefaultPort, username, keyFiles) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The connection info. + /// Specified whether this instance owns the connection info. + /// is . + /// + /// If is , then the + /// connection info will be disposed when this instance is disposed. + /// + private SshClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo) + : this(connectionInfo, ownsConnectionInfo, new ServiceFactory()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The connection info. + /// Specified whether this instance owns the connection info. + /// The factory to use for creating new services. + /// is . + /// is . + /// + /// If is , then the + /// connection info will be disposed when this instance is disposed. + /// + internal SshClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo, IServiceFactory serviceFactory) + : base(connectionInfo, ownsConnectionInfo, serviceFactory) + { + _forwardedPorts = new List(); + } + + /// + /// Called when client is disconnecting from the server. + /// + protected override void OnDisconnecting() + { + base.OnDisconnecting(); + + foreach (var port in _forwardedPorts) + { + port.Stop(); + } + } + + /// + public void AddForwardedPort(ForwardedPort port) + { + ArgumentNullException.ThrowIfNull(port); + + EnsureSessionIsOpen(); + + AttachForwardedPort(port); + _forwardedPorts.Add(port); + } + + /// + public void RemoveForwardedPort(ForwardedPort port) + { + ArgumentNullException.ThrowIfNull(port); + + // Stop port forwarding before removing it + port.Stop(); + + DetachForwardedPort(port); + _ = _forwardedPorts.Remove(port); + } + + private void AttachForwardedPort(ForwardedPort port) + { + if (port.Session != null && port.Session != Session) + { + throw new InvalidOperationException("Forwarded port is already added to a different client."); + } + + port.Session = Session; + } + + private static void DetachForwardedPort(ForwardedPort port) + { + port.Session = null; + } + + /// + public ISshCommand CreateCommand(string commandText) + { + return CreateCommand(commandText, ConnectionInfo.Encoding); + } + + /// + public ISshCommand CreateCommand(string commandText, Encoding encoding) + { + EnsureSessionIsOpen(); + + ConnectionInfo.Encoding = encoding; + return new SshCommand(Session!, commandText, encoding); + } + + /// + public ISshCommand RunCommand(string commandText) + { + var cmd = CreateCommand(commandText); + _ = cmd.Execute(); + return cmd; + } + + /// + public Shell CreateShell(Stream input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary? terminalModes, int bufferSize) + { + EnsureSessionIsOpen(); + + return new Shell(Session, input, output, extendedOutput, terminalName, columns, rows, width, height, terminalModes, bufferSize); + } + + /// + public Shell CreateShell(Stream input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary terminalModes) + { + return CreateShell(input, output, extendedOutput, terminalName, columns, rows, width, height, terminalModes, 1024); + } + + /// + public Shell CreateShell(Stream input, Stream output, Stream extendedOutput) + { + return CreateShell(input, output, extendedOutput, string.Empty, 0, 0, 0, 0, terminalModes: null, 1024); + } + + /// + public Shell CreateShell(Encoding encoding, string input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary? terminalModes, int bufferSize) + { + /* + * TODO Issue #1224: let shell dispose of input stream when we own the stream! + */ + + _inputStream = new MemoryStream(); + + using (var writer = new StreamWriter(_inputStream, encoding, bufferSize: 1024, leaveOpen: true)) + { + writer.Write(input); + writer.Flush(); + } + + _ = _inputStream.Seek(0, SeekOrigin.Begin); + + return CreateShell(_inputStream, output, extendedOutput, terminalName, columns, rows, width, height, terminalModes, bufferSize); + } + + /// + public Shell CreateShell(Encoding encoding, string input, Stream output, Stream extendedOutput, string terminalName, uint columns, uint rows, uint width, uint height, IDictionary terminalModes) + { + return CreateShell(encoding, input, output, extendedOutput, terminalName, columns, rows, width, height, terminalModes, 1024); + } + + /// + public Shell CreateShell(Encoding encoding, string input, Stream output, Stream extendedOutput) + { + return CreateShell(encoding, input, output, extendedOutput, string.Empty, 0, 0, 0, 0, terminalModes: null, 1024); + } + + /// + public Shell CreateShellNoTerminal(Stream input, Stream output, Stream extendedOutput, int bufferSize = -1) + { + EnsureSessionIsOpen(); + + return new Shell(Session, input, output, extendedOutput, bufferSize); + } + + /// + public ShellStream CreateShellStream(string terminalName, uint columns, uint rows, uint width, uint height, int bufferSize) + { + return CreateShellStream(terminalName, columns, rows, width, height, bufferSize, terminalModeValues: null); + } + + /// + public ShellStream CreateShellStream(string terminalName, uint columns, uint rows, uint width, uint height, int bufferSize, IDictionary? terminalModeValues) + { + EnsureSessionIsOpen(); + + return ServiceFactory.CreateShellStream(Session, terminalName, columns, rows, width, height, terminalModeValues, bufferSize); + } + + /// + public ShellStream CreateShellStreamNoTerminal(int bufferSize = -1) + { + EnsureSessionIsOpen(); + + return ServiceFactory.CreateShellStreamNoTerminal(Session, bufferSize); + } + + /// + /// Stops forwarded ports. + /// + protected override void OnDisconnected() + { + base.OnDisconnected(); + + for (var i = _forwardedPorts.Count - 1; i >= 0; i--) + { + var port = _forwardedPorts[i]; + DetachForwardedPort(port); + _forwardedPorts.RemoveAt(i); + } + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// to release both managed and unmanaged resources; to release only unmanaged resources. + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (_isDisposed) + { + return; + } + + if (disposing) + { + _inputStream?.Dispose(); + _inputStream = null; + + _isDisposed = true; + } + } + + private void EnsureSessionIsOpen() + { + if (Session is null) + { + throw new SshConnectionException("Client not connected."); + } + } + } +} diff --git a/src/Renci.SshNet/V2/SshCommand.cs b/src/Renci.SshNet/V2/SshCommand.cs new file mode 100644 index 000000000..0cf69ba3c --- /dev/null +++ b/src/Renci.SshNet/V2/SshCommand.cs @@ -0,0 +1,515 @@ +#nullable enable +using System; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +using Renci.SshNet.Channels; +using Renci.SshNet.Common; +using Renci.SshNet.Messages.Connection; +using Renci.SshNet.Messages.Transport; + +namespace Renci.SshNet.V2 +{ + /// + public sealed class SshCommand : ISshCommand + { + private readonly ISession _session; + private readonly Encoding _encoding; + + private IChannelSession _channel; + private TaskCompletionSource? _tcs; + private CancellationTokenSource? _cts; + private CancellationTokenRegistration _tokenRegistration; + private string? _stdOut; + private string? _stdErr; + private bool _hasError; + private bool _isDisposed; + private ChannelInputStream? _inputStream; + private TimeSpan _commandTimeout; + + /// + /// The token supplied as an argument to . + /// + private CancellationToken _userToken; + + /// + /// Whether has been called + /// (either by a token or manually). + /// + private bool _cancellationRequested; + + private int _exitStatus; + private volatile bool _haveExitStatus; // volatile to prevent re-ordering of reads/writes of _exitStatus. + + /// + public string CommandText { get; private set; } + + /// + public TimeSpan CommandTimeout + { + get + { + return _commandTimeout; + } + set + { + value.EnsureValidTimeout(nameof(CommandTimeout)); + + _commandTimeout = value; + } + } + + /// + public int? ExitStatus + { + get + { + return _haveExitStatus ? _exitStatus : null; + } + } + + /// + public string? ExitSignal { get; private set; } + + /// + public Stream OutputStream { get; private set; } + + /// + public Stream ExtendedOutputStream { get; private set; } + + /// + public Stream CreateInputStream() + { + if (!_channel.IsOpen) + { + throw new InvalidOperationException("The input stream can be used only during execution."); + } + + if (_inputStream != null) + { + throw new InvalidOperationException("The input stream already exists."); + } + + _inputStream = new ChannelInputStream(_channel); + return _inputStream; + } + + /// + public string Result + { + get + { + if (_stdOut is not null) + { + return _stdOut; + } + + if (_tcs is null) + { + return string.Empty; + } + + using (var sr = new StreamReader(OutputStream, _encoding)) + { + return _stdOut = sr.ReadToEnd(); + } + } + } + + /// + public string Error + { + get + { + if (_stdErr is not null) + { + return _stdErr; + } + + if (_tcs is null || !_hasError) + { + return string.Empty; + } + + using (var sr = new StreamReader(ExtendedOutputStream, _encoding)) + { + return _stdErr = sr.ReadToEnd(); + } + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The session. + /// The command text. + /// The encoding to use for the results. + /// Either , is . + internal SshCommand(ISession session, string commandText, Encoding encoding) + { + ArgumentNullException.ThrowIfNull(session); + ArgumentNullException.ThrowIfNull(commandText); + ArgumentNullException.ThrowIfNull(encoding); + + _session = session; + CommandText = commandText; + _encoding = encoding; + CommandTimeout = Timeout.InfiniteTimeSpan; + OutputStream = new PipeStream(); + ExtendedOutputStream = new PipeStream(); + _session.Disconnected += Session_Disconnected; + _session.ErrorOccured += Session_ErrorOccurred; + _channel = _session.CreateChannelSession(); + } + + /// +#pragma warning disable CA1849 // Call async methods when in an async method; PipeStream.DisposeAsync would complete synchronously anyway. + public Task ExecuteAsync(CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(_isDisposed, this); + + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + if (_tcs is not null) + { + if (!_tcs.Task.IsCompleted) + { + throw new InvalidOperationException("Asynchronous operation is already in progress."); + } + + UnsubscribeFromChannelEvents(dispose: true); + + OutputStream.Dispose(); + ExtendedOutputStream.Dispose(); + + // Initialise output streams. We already initialised them for the first + // execution in the constructor (to allow passing them around before execution) + // so we just need to reinitialise them for subsequent executions. + OutputStream = new PipeStream(); + ExtendedOutputStream = new PipeStream(); + _channel = _session.CreateChannelSession(); + } + + _exitStatus = default; + _haveExitStatus = false; + ExitSignal = null; + _stdOut = null; + _stdErr = null; + _hasError = false; + _tokenRegistration.Dispose(); + _tokenRegistration = default; + _cts?.Dispose(); + _cts = null; + _cancellationRequested = false; + + _tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + _userToken = cancellationToken; + + _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(cancellationToken); + _cts.CancelAfter(CommandTimeout); + cancellationToken = _cts.Token; + } + + if (cancellationToken.CanBeCanceled) + { + _tokenRegistration = cancellationToken.Register(static cmd => + { + try + { + ((ISshCommand)cmd!).CancelAsync(); + } + catch + { + // Swallow exceptions which would otherwise be unhandled. + } + }, + this); + } + + return _tcs.Task; + } +#pragma warning restore CA1849 + + /// + public IAsyncResult BeginExecute() + { + return BeginExecute(callback: null, state: null); + } + + /// + public IAsyncResult BeginExecute(AsyncCallback? callback) + { + return BeginExecute(callback, state: null); + } + + /// + public IAsyncResult BeginExecute(AsyncCallback? callback, object? state) + { + return TaskToAsyncResult.Begin(ExecuteAsync(), callback, state); + } + + /// + public IAsyncResult BeginExecute(string commandText, AsyncCallback? callback, object? state) + { + ArgumentNullException.ThrowIfNull(commandText); + + CommandText = commandText; + + return BeginExecute(callback, state); + } + + /// + public string EndExecute(IAsyncResult asyncResult) + { + var executeTask = TaskToAsyncResult.Unwrap(asyncResult); + + if (executeTask != _tcs?.Task) + { + throw new ArgumentException("Argument does not correspond to the currently executing command.", nameof(asyncResult)); + } + + executeTask.GetAwaiter().GetResult(); + + return Result; + } + + /// + public void CancelAsync(bool forceKill = false, int millisecondsTimeout = 500) + { + if (_tcs is null) + { + throw new InvalidOperationException("Command has not been started."); + } + + if (_tcs.Task.IsCompleted) + { + return; + } + + _cancellationRequested = true; + Interlocked.MemoryBarrier(); // ensure fresh read in SetAsyncComplete (possibly unnecessary) + + try + { + // Try to send the cancellation signal. + if (_channel?.SendSignalRequest(forceKill ? "KILL" : "TERM") is null) + { + // Command has completed (in the meantime since the last check). + return; + } + + // Having sent the "signal" message, we expect to receive "exit-signal" + // and then a close message. But since a server may not implement signals, + // we can't guarantee that, so we wait a short time for that to happen and + // if it doesn't, just complete the task ourselves to unblock waiters. + + _ = _tcs.Task.Wait(millisecondsTimeout); + } + catch (AggregateException) + { + // We expect to be here from the call to Wait if the server implements signals. + // But we don't want to propagate the exception on the task from here. + } + finally + { + SetAsyncComplete(); + } + } + + /// + public string Execute() + { + ExecuteAsync().GetAwaiter().GetResult(); + + return Result; + } + + /// + public string Execute(string commandText) + { + CommandText = commandText; + + return Execute(); + } + + private void Session_Disconnected(object? sender, EventArgs e) + { + _ = _tcs?.TrySetException(new SshConnectionException("An established connection was aborted by the software in your host machine.", DisconnectReason.ConnectionLost)); + + SetAsyncComplete(setResult: false); + } + + private void Session_ErrorOccurred(object? sender, ExceptionEventArgs e) + { + _ = _tcs?.TrySetException(e.Exception); + + SetAsyncComplete(setResult: false); + } + + private void SetAsyncComplete(bool setResult = true) + { + Interlocked.MemoryBarrier(); // ensure fresh read of _cancellationRequested (possibly unnecessary) + + if (setResult) + { + Debug.Assert(_tcs is not null, "Should only be completing the task if we've started one."); + + if (_userToken.IsCancellationRequested) + { + _ = _tcs.TrySetCanceled(_userToken); + } + else if (_cts?.Token.IsCancellationRequested == true) + { + _ = _tcs.TrySetException(new SshOperationTimeoutException($"Command '{CommandText}' timed out. ({nameof(CommandTimeout)}: {CommandTimeout}).")); + } + else if (_cancellationRequested) + { + _ = _tcs.TrySetCanceled(); + } + else + { + _ = _tcs.TrySetResult(null!); + } + } + + // We don't dispose the channel here to avoid a race condition + // where SSH_MSG_CHANNEL_CLOSE arrives before _channel starts + // waiting for a response in _channel.SendExecRequest(). + UnsubscribeFromChannelEvents(dispose: false); + + OutputStream.Dispose(); + ExtendedOutputStream.Dispose(); + } + + private void Channel_Closed(object? sender, ChannelEventArgs e) + { + SetAsyncComplete(); + } + + private void Channel_RequestReceived(object? sender, ChannelRequestEventArgs e) + { + if (e.Info is ExitStatusRequestInfo exitStatusInfo) + { + _exitStatus = (int)exitStatusInfo.ExitStatus; + _haveExitStatus = true; + + Debug.Assert(!exitStatusInfo.WantReply, "exit-status is want_reply := false by definition."); + } + else if (e.Info is ExitSignalRequestInfo exitSignalInfo) + { + ExitSignal = exitSignalInfo.SignalName; + + Debug.Assert(!exitSignalInfo.WantReply, "exit-signal is want_reply := false by definition."); + } + else if (e.Info.WantReply && sender is IChannel { RemoteChannelNumber: uint remoteChannelNumber }) + { + var replyMessage = new ChannelFailureMessage(remoteChannelNumber); + _session.SendMessage(replyMessage); + } + } + + private void Channel_ExtendedDataReceived(object? sender, ChannelExtendedDataEventArgs e) + { + ExtendedOutputStream.Write(e.Data.Array!, e.Data.Offset, e.Data.Count); + + if (e.DataTypeCode == 1) + { + _hasError = true; + } + } + + private void Channel_DataReceived(object? sender, ChannelDataEventArgs e) + { + OutputStream.Write(e.Data.Array!, e.Data.Offset, e.Data.Count); + } + + /// + /// Unsubscribes the current from channel events, and optionally, + /// disposes . + /// + private void UnsubscribeFromChannelEvents(bool dispose) + { + var channel = _channel; + + // unsubscribe from events as we do not want to be signaled should these get fired + // during the dispose of the channel + channel.DataReceived -= Channel_DataReceived; + channel.ExtendedDataReceived -= Channel_ExtendedDataReceived; + channel.RequestReceived -= Channel_RequestReceived; + channel.Closed -= Channel_Closed; + + if (dispose) + { + channel.Dispose(); + } + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// to release both managed and unmanaged resources; to release only unmanaged resources. + private void Dispose(bool disposing) + { + if (_isDisposed) + { + return; + } + + if (disposing) + { + // unsubscribe from session events to ensure other objects that we're going to dispose + // are not accessed while disposing + _session.Disconnected -= Session_Disconnected; + _session.ErrorOccured -= Session_ErrorOccurred; + + // unsubscribe from channel events to ensure other objects that we're going to dispose + // are not accessed while disposing + UnsubscribeFromChannelEvents(dispose: true); + + _inputStream?.Dispose(); + _inputStream = null; + + OutputStream.Dispose(); + ExtendedOutputStream.Dispose(); + + _tokenRegistration.Dispose(); + _tokenRegistration = default; + _cts?.Dispose(); + _cts = null; + + if (_tcs is { Task.IsCompleted: false } tcs) + { + // In case an operation is still running, try to complete it with an ObjectDisposedException. + _ = tcs.TrySetException(new ObjectDisposedException(GetType().FullName)); + } + + _isDisposed = true; + } + } + } +} From 3868831db6c96fd7fe0ba808f5f7cd60a102a545 Mon Sep 17 00:00:00 2001 From: Martin Hansen Date: Mon, 23 Mar 2026 12:44:14 +0100 Subject: [PATCH 2/3] Update examples in doc files --- README.md | 2 +- docfx/examples.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f1382e365..c58cceee3 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ SSH.NET is a Secure Shell (SSH-2) library for .NET, optimized for parallelism. using (var client = new SshClient("sftp.foo.com", "guest", new PrivateKeyFile("path/to/my/key"))) { client.Connect(); - using SshCommand cmd = client.RunCommand("echo 'Hello World!'"); + using ISshCommand cmd = client.RunCommand("echo 'Hello World!'"); Console.WriteLine(cmd.Result); // "Hello World!\n" } ``` diff --git a/docfx/examples.md b/docfx/examples.md index fd2ab53b1..306b27747 100644 --- a/docfx/examples.md +++ b/docfx/examples.md @@ -9,7 +9,7 @@ Getting Started using (var client = new SshClient("sftp.foo.com", "guest", new PrivateKeyFile("path/to/my/key"))) { client.Connect(); - using SshCommand cmd = client.RunCommand("echo 'Hello World!'"); + using ISshCommand cmd = client.RunCommand("echo 'Hello World!'"); Console.WriteLine(cmd.Result); // "Hello World!\n" } ``` @@ -134,7 +134,7 @@ using (var client = new SshClient("sftp.foo.com", "guest", "pwd")) client.Connect(); // Make the server echo back the input file with "cat" - using (SshCommand command = client.CreateCommand("cat")) + using (ISshCommand command = client.CreateCommand("cat")) { Task executeTask = command.ExecuteAsync(CancellationToken.None); From 7cfdf4fbb5290e496d845403e15f5e2862354cef Mon Sep 17 00:00:00 2001 From: Martin Hansen Date: Mon, 23 Mar 2026 12:52:21 +0100 Subject: [PATCH 3/3] Update tests --- .../SshClientBenchmark.cs | 13 ++--- .../OldIntegrationTests/SshCommandTest.cs | 51 ++++++++++--------- .../SshClientTests.cs | 6 ++- .../SshConnectionDisruptor.cs | 8 +-- .../SshConnectionRestorer.cs | 8 +-- .../Renci.SshNet.IntegrationTests/SshTests.cs | 31 +++++------ ...AndBufferSizeAndTerminalModes_Connected.cs | 5 +- ...ndWidthAndHeightAndBufferSize_Connected.cs | 5 +- ...entTest_Disconnect_ForwardedPortStarted.cs | 6 ++- .../SshClientTest_Dispose_Connected.cs | 6 ++- .../SshClientTest_Dispose_Disconnected.cs | 6 ++- .../Classes/SshClientTest_Dispose_Disposed.cs | 6 ++- ...ClientTest_Dispose_ForwardedPortStarted.cs | 6 ++- ...okedOnAsyncResultFromPreviousInvocation.cs | 5 +- ...okedOnAsyncResultFromPreviousInvocation.cs | 5 +- .../Classes/SshCommandTest_Dispose.cs | 5 +- ...EndExecute_AsyncResultFromOtherInstance.cs | 9 ++-- ...ommandTest_EndExecute_AsyncResultIsNull.cs | 5 +- .../SshCommandTest_EndExecute_ChannelOpen.cs | 5 +- 19 files changed, 109 insertions(+), 82 deletions(-) diff --git a/test/Renci.SshNet.IntegrationBenchmarks/SshClientBenchmark.cs b/test/Renci.SshNet.IntegrationBenchmarks/SshClientBenchmark.cs index 47c169e74..a6b555b6b 100644 --- a/test/Renci.SshNet.IntegrationBenchmarks/SshClientBenchmark.cs +++ b/test/Renci.SshNet.IntegrationBenchmarks/SshClientBenchmark.cs @@ -2,6 +2,7 @@ using Renci.SshNet.Common; using Renci.SshNet.IntegrationTests.TestsFixtures; +using Renci.SshNet.V2; namespace Renci.SshNet.IntegrationBenchmarks { @@ -15,7 +16,7 @@ public class SshClientBenchmark : IntegrationBenchmarkBase }; private readonly InfrastructureFixture _infrastructureFixture; - private SshClient? _sshClient; + private V2.SshClient? _sshClient; public SshClientBenchmark() { @@ -26,7 +27,7 @@ public SshClientBenchmark() public async Task Setup() { await GlobalSetup().ConfigureAwait(false); - _sshClient = new SshClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password); + _sshClient = new V2.SshClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password); await _sshClient.ConnectAsync(CancellationToken.None).ConfigureAwait(false); } @@ -39,21 +40,21 @@ public async Task Cleanup() [Benchmark] public void Connect() { - using var sshClient = new SshClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password); + using var sshClient = new V2.SshClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password); sshClient.Connect(); } [Benchmark] public async Task ConnectAsync() { - using var sshClient = new SshClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password); + using var sshClient = new V2.SshClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password); await sshClient.ConnectAsync(CancellationToken.None).ConfigureAwait(false); } [Benchmark] public string ConnectAndRunCommand() { - using var sshClient = new SshClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password); + using var sshClient = new V2.SshClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password); sshClient.Connect(); return sshClient.RunCommand("echo $'test !@#$%^&*()_+{}:,./<>[];\\|'").Result; } @@ -61,7 +62,7 @@ public string ConnectAndRunCommand() [Benchmark] public async Task ConnectAsyncAndRunCommand() { - using var sshClient = new SshClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password); + using var sshClient = new V2.SshClient(_infrastructureFixture.SshServerHostName, _infrastructureFixture.SshServerPort, _infrastructureFixture.User.UserName, _infrastructureFixture.User.Password); await sshClient.ConnectAsync(CancellationToken.None).ConfigureAwait(false); return sshClient.RunCommand("echo $'test !@#$%^&*()_+{}:,./<>[];\\|'").Result; } diff --git a/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SshCommandTest.cs b/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SshCommandTest.cs index c5a26342e..df164817d 100644 --- a/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SshCommandTest.cs +++ b/test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SshCommandTest.cs @@ -4,6 +4,7 @@ using Renci.SshNet.Abstractions; #endif using Renci.SshNet.Common; +using Renci.SshNet.V2; namespace Renci.SshNet.IntegrationTests.OldIntegrationTests { @@ -16,7 +17,7 @@ public class SshCommandTest : IntegrationTestBase [TestMethod] public void Test_Run_SingleCommand() { - using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + using (var client = new V2.SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { #region Example SshCommand RunCommand Result client.Connect(); @@ -36,7 +37,7 @@ public void Test_Run_SingleCommand() [TestMethod] public void Test_Execute_SingleCommand() { - using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + using (var client = new V2.SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { #region Example SshCommand CreateCommand Execute client.Connect(); @@ -56,7 +57,7 @@ public void Test_Execute_SingleCommand() [TestMethod] public void Test_CancelAsync_Unfinished_Command() { - using var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password); + using var client = new V2.SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password); client.Connect(); var testValue = Guid.NewGuid().ToString(); using var cmd = client.CreateCommand($"sleep 15s; echo {testValue}"); @@ -77,7 +78,7 @@ public void Test_CancelAsync_Unfinished_Command() [TestMethod] public async Task Test_CancelAsync_Kill_Unfinished_Command() { - using var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password); + using var client = new V2.SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password); client.Connect(); var testValue = Guid.NewGuid().ToString(); using var cmd = client.CreateCommand($"sleep 15s; echo {testValue}"); @@ -100,7 +101,7 @@ public async Task Test_CancelAsync_Kill_Unfinished_Command() [TestMethod] public void Test_CancelAsync_Finished_Command() { - using var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password); + using var client = new V2.SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password); client.Connect(); var testValue = Guid.NewGuid().ToString(); using var cmd = client.CreateCommand($"echo -n {testValue}"); @@ -122,7 +123,7 @@ public void Test_CancelAsync_Finished_Command() [TestMethod] public async Task Test_ExecuteAsync_CancellationToken() { - using var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password); + using var client = new V2.SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password); client.Connect(); var testValue = Guid.NewGuid().ToString(); using var cmd = client.CreateCommand($"sleep 15s; echo {testValue}"); @@ -143,7 +144,7 @@ public async Task Test_ExecuteAsync_CancellationToken() [TestMethod] public void Test_Execute_ExtendedOutputStream() { - using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + using (var client = new V2.SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { #region Example SshCommand CreateCommand Execute ExtendedOutputStream @@ -163,7 +164,7 @@ public void Test_Execute_ExtendedOutputStream() [TestMethod] public void Test_Execute_Timeout() { - using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + using (var client = new V2.SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { client.Connect(); using var cmd = client.CreateCommand("sleep 10s"); @@ -176,7 +177,7 @@ public void Test_Execute_Timeout() [TestMethod] public async Task Test_ExecuteAsync_Timeout() { - using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + using (var client = new V2.SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { client.Connect(); using var cmd = client.CreateCommand("sleep 10s"); @@ -194,7 +195,7 @@ public async Task Test_ExecuteAsync_Timeout() [TestMethod] public async Task Test_ExecuteAsync_Disconnect() { - using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + using (var client = new V2.SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { client.Connect(); using var cmd = client.CreateCommand("sleep 10s"); @@ -212,7 +213,7 @@ public async Task Test_ExecuteAsync_Disconnect() [TestMethod] public void Test_Execute_InvalidCommand() { - using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + using (var client = new V2.SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { client.Connect(); @@ -231,7 +232,7 @@ public void Test_Execute_InvalidCommand() [TestMethod] public void Test_Execute_InvalidCommand_Then_Execute_ValidCommand() { - using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + using (var client = new V2.SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { client.Connect(); using var cmd = client.CreateCommand(";"); @@ -253,7 +254,7 @@ public void Test_Execute_InvalidCommand_Then_Execute_ValidCommand() [TestMethod] public void Test_Execute_Command_Reconnect_Execute_Command() { - using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + using (var client = new V2.SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { client.Connect(); var result = ExecuteTestCommand(client); @@ -270,7 +271,7 @@ public void Test_Execute_Command_Reconnect_Execute_Command() [TestMethod] public void Test_Execute_Command_ExitStatus() { - using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + using (var client = new V2.SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { client.Connect(); @@ -284,7 +285,7 @@ public void Test_Execute_Command_ExitStatus() [TestMethod] public void Test_Execute_Command_Asynchronously() { - using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + using (var client = new V2.SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { client.Connect(); @@ -315,7 +316,7 @@ public void Test_Execute_Command_Asynchronously() [TestMethod] public void Test_Execute_Command_Asynchronously_With_Error() { - using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + using (var client = new V2.SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { client.Connect(); @@ -335,7 +336,7 @@ public void Test_Execute_Command_Asynchronously_With_Error() [TestMethod] public void Test_Execute_Command_Asynchronously_With_Callback_On_Different_Thread() { - using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + using (var client = new V2.SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { client.Connect(); @@ -366,7 +367,7 @@ public void Test_Execute_Command_Asynchronously_With_Callback_On_Different_Threa [WorkItem(563), TestMethod] public void Test_Execute_Command_Same_Object_Different_Commands() { - using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + using (var client = new V2.SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { client.Connect(); using var cmd = client.CreateCommand("echo 12345"); @@ -381,7 +382,7 @@ public void Test_Execute_Command_Same_Object_Different_Commands() [TestMethod] public void Test_Get_Result_Without_Execution() { - using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + using (var client = new V2.SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { client.Connect(); using var cmd = client.CreateCommand("ls -l"); @@ -395,7 +396,7 @@ public void Test_Get_Result_Without_Execution() [WorkItem(703), TestMethod] public void Test_EndExecute_Before_BeginExecute() { - using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + using (var client = new V2.SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { client.Connect(); using var cmd = client.CreateCommand("ls -l"); @@ -413,7 +414,7 @@ public void BeginExecuteTest() string expected = "123\n"; string result; - using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + using (var client = new V2.SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { #region Example SshCommand CreateCommand BeginExecute IsCompleted EndExecute @@ -449,7 +450,7 @@ public void Test_MultipleThread_100_MultipleConnections() Parallel.For(0, 100, options, () => { - var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password); + var client = new V2.SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password); client.Connect(); return client; }, @@ -476,7 +477,7 @@ public void Test_MultipleThread_100_MultipleConnections() [TestMethod] public void Test_MultipleThread_100_MultipleSessions() { - using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + using (var client = new V2.SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { client.Connect(); Parallel.For(0, 100, @@ -495,7 +496,7 @@ public void Test_MultipleThread_100_MultipleSessions() [TestMethod] public void Test_ExecuteAsync_Dispose_CommandFinishes() { - using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) + using (var client = new V2.SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password)) { client.Connect(); @@ -510,7 +511,7 @@ public void Test_ExecuteAsync_Dispose_CommandFinishes() } } - private static bool ExecuteTestCommand(SshClient s) + private static bool ExecuteTestCommand(V2.SshClient s) { var testValue = Guid.NewGuid().ToString(); var command = string.Format("echo -n {0}", testValue); diff --git a/test/Renci.SshNet.IntegrationTests/SshClientTests.cs b/test/Renci.SshNet.IntegrationTests/SshClientTests.cs index ffe670afb..bfc1aecfc 100644 --- a/test/Renci.SshNet.IntegrationTests/SshClientTests.cs +++ b/test/Renci.SshNet.IntegrationTests/SshClientTests.cs @@ -1,3 +1,5 @@ +using Renci.SshNet.V2; + namespace Renci.SshNet.IntegrationTests { /// @@ -6,11 +8,11 @@ namespace Renci.SshNet.IntegrationTests [TestClass] public class SshClientTests : IntegrationTestBase, IDisposable { - private readonly SshClient _sshClient; + private readonly V2.SshClient _sshClient; public SshClientTests() { - _sshClient = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password); + _sshClient = new V2.SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password); _sshClient.Connect(); } diff --git a/test/Renci.SshNet.IntegrationTests/SshConnectionDisruptor.cs b/test/Renci.SshNet.IntegrationTests/SshConnectionDisruptor.cs index ae6d4c5b8..1a740e066 100644 --- a/test/Renci.SshNet.IntegrationTests/SshConnectionDisruptor.cs +++ b/test/Renci.SshNet.IntegrationTests/SshConnectionDisruptor.cs @@ -1,4 +1,6 @@ -namespace Renci.SshNet.IntegrationTests +using Renci.SshNet.V2; + +namespace Renci.SshNet.IntegrationTests { internal class SshConnectionDisruptor { @@ -11,7 +13,7 @@ public SshConnectionDisruptor(IConnectionInfoFactory connectionInfoFactory) public SshConnectionRestorer BreakConnections() { - var client = new SshClient(_connectionInfoFactory.Create()); + var client = new V2.SshClient(_connectionInfoFactory.Create()); client.Connect(); @@ -20,7 +22,7 @@ public SshConnectionRestorer BreakConnections() return new SshConnectionRestorer(client); } - private static void PauseSshd(SshClient client) + private static void PauseSshd(V2.SshClient client) { using (var command = client.CreateCommand("sudo echo 'DenyUsers sshnet' >> /etc/ssh/sshd_config")) { diff --git a/test/Renci.SshNet.IntegrationTests/SshConnectionRestorer.cs b/test/Renci.SshNet.IntegrationTests/SshConnectionRestorer.cs index d7c6437db..05f689cb7 100644 --- a/test/Renci.SshNet.IntegrationTests/SshConnectionRestorer.cs +++ b/test/Renci.SshNet.IntegrationTests/SshConnectionRestorer.cs @@ -1,10 +1,12 @@ -namespace Renci.SshNet.IntegrationTests +using Renci.SshNet.V2; + +namespace Renci.SshNet.IntegrationTests { internal class SshConnectionRestorer : IDisposable { - private SshClient _sshClient; + private V2.SshClient _sshClient; - public SshConnectionRestorer(SshClient sshClient) + public SshConnectionRestorer(V2.SshClient sshClient) { _sshClient = sshClient; } diff --git a/test/Renci.SshNet.IntegrationTests/SshTests.cs b/test/Renci.SshNet.IntegrationTests/SshTests.cs index 07daf548f..bb72b14f5 100644 --- a/test/Renci.SshNet.IntegrationTests/SshTests.cs +++ b/test/Renci.SshNet.IntegrationTests/SshTests.cs @@ -8,6 +8,7 @@ using Renci.SshNet.Common; using Renci.SshNet.IntegrationTests.Common; using Renci.SshNet.Tests.Common; +using Renci.SshNet.V2; namespace Renci.SshNet.IntegrationTests { @@ -44,7 +45,7 @@ public void TearDown() [TestMethod] public void Ssh_ShellStream_Exit() { - using (var client = new SshClient(_connectionInfoFactory.Create())) + using (var client = new V2.SshClient(_connectionInfoFactory.Create())) { client.Connect(); @@ -84,7 +85,7 @@ public void Ssh_ShellStream_Exit() [TestMethod] public void Ssh_CreateShellStreamNoTerminal() { - using (var client = new SshClient(_connectionInfoFactory.Create())) + using (var client = new V2.SshClient(_connectionInfoFactory.Create())) { client.Connect(); @@ -128,7 +129,7 @@ public void Ssh_ShellStream_IntermittendOutput() scriptBuilder.Append("echo Line 6 \n"); scriptBuilder.Append("exit 13\n"); - using (var sshClient = new SshClient(_connectionInfoFactory.Create())) + using (var sshClient = new V2.SshClient(_connectionInfoFactory.Create())) { sshClient.Connect(); @@ -171,7 +172,7 @@ public void Ssh_ShellStream_IntermittendOutput() [TestMethod] public void Ssh_CreateShell() { - using (var client = new SshClient(_connectionInfoFactory.Create())) + using (var client = new V2.SshClient(_connectionInfoFactory.Create())) { client.Connect(); @@ -196,7 +197,7 @@ public void Ssh_CreateShell() [TestMethod] public void Ssh_CreateShellNoTerminal() { - using (var client = new SshClient(_connectionInfoFactory.Create())) + using (var client = new V2.SshClient(_connectionInfoFactory.Create())) { client.Connect(); @@ -254,7 +255,7 @@ public void Ssh_Command_IntermittentOutput_EndExecute() scriptBuilder.Append("echo Line 6 \n"); scriptBuilder.Append("exit 13\n"); - using (var sshClient = new SshClient(_connectionInfoFactory.Create())) + using (var sshClient = new V2.SshClient(_connectionInfoFactory.Create())) { sshClient.Connect(); @@ -311,7 +312,7 @@ public async Task Ssh_Command_IntermittentOutput_OutputStream() scriptBuilder.Append("echo Line 6 \n"); scriptBuilder.Append("exit 13\n"); - using (var sshClient = new SshClient(_connectionInfoFactory.Create())) + using (var sshClient = new V2.SshClient(_connectionInfoFactory.Create())) { sshClient.Connect(); @@ -365,7 +366,7 @@ public void Ssh_DynamicPortForwarding_DisposeSshClientWithoutStoppingPort() var httpGetRequest = Encoding.ASCII.GetBytes($"GET / HTTP/1.1\r\nHost: {hostName}\r\n\r\n"); Socket socksSocket; - using (var client = new SshClient(_connectionInfoFactory.Create())) + using (var client = new V2.SshClient(_connectionInfoFactory.Create())) { client.ConnectionInfo.Timeout = TimeSpan.FromSeconds(200); client.Connect(); @@ -410,7 +411,7 @@ public void Ssh_DynamicPortForwarding_DomainName() try { - using (var client = new SshClient(_connectionInfoFactory.Create())) + using (var client = new V2.SshClient(_connectionInfoFactory.Create())) { client.ConnectionInfo.Timeout = TimeSpan.FromSeconds(200); client.Connect(); @@ -479,7 +480,7 @@ public void Ssh_DynamicPortForwarding_IPv4() var ipv4 = Dns.GetHostAddresses(hostName).FirstOrDefault(p => p.AddressFamily == AddressFamily.InterNetwork); Assert.IsNotNull(ipv4, $@"No IPv4 address found for '{hostName}'."); - using (var client = new SshClient(_connectionInfoFactory.Create())) + using (var client = new V2.SshClient(_connectionInfoFactory.Create())) { client.ConnectionInfo.Timeout = TimeSpan.FromSeconds(200); client.Connect(); @@ -523,7 +524,7 @@ public void Ssh_LocalPortForwardingCloseChannels() var connectionInfo = _connectionInfoFactory.Create(); connectionInfo.MaxSessions = 1; - using (var client = new SshClient(connectionInfo)) + using (var client = new V2.SshClient(connectionInfo)) { client.Connect(); @@ -578,7 +579,7 @@ public void Ssh_LocalPortForwarding() try { - using (var client = new SshClient(_connectionInfoFactory.Create())) + using (var client = new V2.SshClient(_connectionInfoFactory.Create())) { client.Connect(); @@ -639,7 +640,7 @@ public void Ssh_RemotePortForwarding() using (var socketListener2 = new AsyncSocketListener(endpoint2)) using (var bytesReceivedEventOnListener1 = new AutoResetEvent(false)) using (var bytesReceivedEventOnListener2 = new AutoResetEvent(false)) - using (var client = new SshClient(_connectionInfoFactory.Create())) + using (var client = new V2.SshClient(_connectionInfoFactory.Create())) { socketListener1.BytesReceived += (received, socket) => { @@ -740,7 +741,7 @@ public void Ssh_ExecuteShellScript() } } - using (var client = new SshClient(_connectionInfoFactory.Create())) + using (var client = new V2.SshClient(_connectionInfoFactory.Create())) { client.Connect(); @@ -991,7 +992,7 @@ private static void CreateShellScript(IConnectionInfoFactory connectionInfoFacto } } - private static void RemoveFileOrDirectory(SshClient client, string remoteFile) + private static void RemoveFileOrDirectory(V2.SshClient client, string remoteFile) { using (var cmd = client.CreateCommand("rm -Rf " + remoteFile)) { diff --git a/test/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected.cs b/test/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected.cs index bff51de39..b28cb3648 100644 --- a/test/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected.cs +++ b/test/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected.cs @@ -8,13 +8,14 @@ using Renci.SshNet.Channels; using Renci.SshNet.Common; +using Renci.SshNet.V2; namespace Renci.SshNet.Tests.Classes { [TestClass] public class SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected : BaseClientTestBase { - private SshClient _sshClient; + private V2.SshClient _sshClient; private ConnectionInfo _connectionInfo; private string _terminalName; private uint _widthColumns; @@ -71,7 +72,7 @@ protected override void Arrange() { base.Arrange(); - _sshClient = new SshClient(_connectionInfo, false, ServiceFactoryMock.Object); + _sshClient = new V2.SshClient(_connectionInfo, false, ServiceFactoryMock.Object); _sshClient.Connect(); } diff --git a/test/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected.cs b/test/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected.cs index 2733997c9..485819d85 100644 --- a/test/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected.cs +++ b/test/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected.cs @@ -6,13 +6,14 @@ using Moq; using Renci.SshNet.Channels; +using Renci.SshNet.V2; namespace Renci.SshNet.Tests.Classes { [TestClass] public class SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected : BaseClientTestBase { - private SshClient _sshClient; + private V2.SshClient _sshClient; private ConnectionInfo _connectionInfo; private string _terminalName; private uint _widthColumns; @@ -67,7 +68,7 @@ protected override void Arrange() { base.Arrange(); - _sshClient = new SshClient(_connectionInfo, false, ServiceFactoryMock.Object); + _sshClient = new V2.SshClient(_connectionInfo, false, ServiceFactoryMock.Object); _sshClient.Connect(); } diff --git a/test/Renci.SshNet.Tests/Classes/SshClientTest_Disconnect_ForwardedPortStarted.cs b/test/Renci.SshNet.Tests/Classes/SshClientTest_Disconnect_ForwardedPortStarted.cs index 3f437fa0f..95545563a 100644 --- a/test/Renci.SshNet.Tests/Classes/SshClientTest_Disconnect_ForwardedPortStarted.cs +++ b/test/Renci.SshNet.Tests/Classes/SshClientTest_Disconnect_ForwardedPortStarted.cs @@ -4,13 +4,15 @@ using Moq; +using Renci.SshNet.V2; + namespace Renci.SshNet.Tests.Classes { [TestClass] public class SshClientTest_Disconnect_ForwardedPortStarted : BaseClientTestBase { private Mock _forwardedPortMock; - private SshClient _sshClient; + private V2.SshClient _sshClient; private ConnectionInfo _connectionInfo; protected override void CreateMocks() @@ -46,7 +48,7 @@ protected override void Arrange() { base.Arrange(); - _sshClient = new SshClient(_connectionInfo, false, ServiceFactoryMock.Object); + _sshClient = new V2.SshClient(_connectionInfo, false, ServiceFactoryMock.Object); _sshClient.Connect(); _sshClient.AddForwardedPort(_forwardedPortMock.Object); diff --git a/test/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Connected.cs b/test/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Connected.cs index 8a96ed49f..3c0f7100c 100644 --- a/test/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Connected.cs +++ b/test/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Connected.cs @@ -2,12 +2,14 @@ using Moq; +using Renci.SshNet.V2; + namespace Renci.SshNet.Tests.Classes { [TestClass] public class SshClientTest_Dispose_Connected : BaseClientTestBase { - private SshClient _sshClient; + private V2.SshClient _sshClient; private ConnectionInfo _connectionInfo; protected override void SetupData() @@ -34,7 +36,7 @@ protected override void Arrange() { base.Arrange(); - _sshClient = new SshClient(_connectionInfo, false, ServiceFactoryMock.Object); + _sshClient = new V2.SshClient(_connectionInfo, false, ServiceFactoryMock.Object); _sshClient.Connect(); } diff --git a/test/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Disconnected.cs b/test/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Disconnected.cs index ff475de9c..09297511f 100644 --- a/test/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Disconnected.cs +++ b/test/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Disconnected.cs @@ -2,12 +2,14 @@ using Moq; +using Renci.SshNet.V2; + namespace Renci.SshNet.Tests.Classes { [TestClass] public class SshClientTest_Dispose_Disconnected : BaseClientTestBase { - private SshClient _sshClient; + private V2.SshClient _sshClient; private ConnectionInfo _connectionInfo; protected override void SetupData() @@ -34,7 +36,7 @@ protected override void Arrange() { base.Arrange(); - _sshClient = new SshClient(_connectionInfo, false, ServiceFactoryMock.Object); + _sshClient = new V2.SshClient(_connectionInfo, false, ServiceFactoryMock.Object); _sshClient.Connect(); _sshClient.Disconnect(); } diff --git a/test/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Disposed.cs b/test/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Disposed.cs index 47b7d0071..14a11b308 100644 --- a/test/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Disposed.cs +++ b/test/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Disposed.cs @@ -2,12 +2,14 @@ using Moq; +using Renci.SshNet.V2; + namespace Renci.SshNet.Tests.Classes { [TestClass] public class SshClientTest_Dispose_Disposed : BaseClientTestBase { - private SshClient _sshClient; + private V2.SshClient _sshClient; private ConnectionInfo _connectionInfo; protected override void SetupData() @@ -34,7 +36,7 @@ protected override void Arrange() { base.Arrange(); - _sshClient = new SshClient(_connectionInfo, false, ServiceFactoryMock.Object); + _sshClient = new V2.SshClient(_connectionInfo, false, ServiceFactoryMock.Object); _sshClient.Connect(); _sshClient.Dispose(); } diff --git a/test/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_ForwardedPortStarted.cs b/test/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_ForwardedPortStarted.cs index de213dc19..5fc46ba83 100644 --- a/test/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_ForwardedPortStarted.cs +++ b/test/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_ForwardedPortStarted.cs @@ -5,13 +5,15 @@ using Moq; +using Renci.SshNet.V2; + namespace Renci.SshNet.Tests.Classes { [TestClass] public class SshClientTest_Dispose_ForwardedPortStarted : BaseClientTestBase { private Mock _forwardedPortMock; - private SshClient _sshClient; + private V2.SshClient _sshClient; private ConnectionInfo _connectionInfo; protected override void CreateMocks() @@ -47,7 +49,7 @@ protected override void Arrange() { base.Arrange(); - _sshClient = new SshClient(_connectionInfo, false, ServiceFactoryMock.Object); + _sshClient = new V2.SshClient(_connectionInfo, false, ServiceFactoryMock.Object); _sshClient.Connect(); _sshClient.AddForwardedPort(_forwardedPortMock.Object); diff --git a/test/Renci.SshNet.Tests/Classes/SshCommandTest_BeginExecute_EndExecuteInvokedOnAsyncResultFromPreviousInvocation.cs b/test/Renci.SshNet.Tests/Classes/SshCommandTest_BeginExecute_EndExecuteInvokedOnAsyncResultFromPreviousInvocation.cs index 676cec5f6..46deb2657 100644 --- a/test/Renci.SshNet.Tests/Classes/SshCommandTest_BeginExecute_EndExecuteInvokedOnAsyncResultFromPreviousInvocation.cs +++ b/test/Renci.SshNet.Tests/Classes/SshCommandTest_BeginExecute_EndExecuteInvokedOnAsyncResultFromPreviousInvocation.cs @@ -10,6 +10,7 @@ using Renci.SshNet.Channels; using Renci.SshNet.Common; using Renci.SshNet.Tests.Common; +using Renci.SshNet.V2; namespace Renci.SshNet.Tests.Classes { @@ -21,7 +22,7 @@ public class SshCommandTest_BeginExecute_EndExecuteInvokedOnAsyncResultFromPrevi private Mock _channelSessionBMock; private string _commandText; private Encoding _encoding; - private SshCommand _sshCommand; + private V2.SshCommand _sshCommand; private IAsyncResult _asyncResultA; private IAsyncResult _asyncResultB; @@ -54,7 +55,7 @@ private void Arrange() .Raises(c => c.Closed += null, new ChannelEventArgs(5)); _channelSessionAMock.InSequence(seq).Setup(p => p.Dispose()); - _sshCommand = new SshCommand(_sessionMock.Object, _commandText, _encoding); + _sshCommand = new V2.SshCommand(_sessionMock.Object, _commandText, _encoding); _asyncResultA = _sshCommand.BeginExecute(); _sshCommand.EndExecute(_asyncResultA); diff --git a/test/Renci.SshNet.Tests/Classes/SshCommandTest_BeginExecute_EndExecuteNotInvokedOnAsyncResultFromPreviousInvocation.cs b/test/Renci.SshNet.Tests/Classes/SshCommandTest_BeginExecute_EndExecuteNotInvokedOnAsyncResultFromPreviousInvocation.cs index 49efb4eae..e7c68936b 100644 --- a/test/Renci.SshNet.Tests/Classes/SshCommandTest_BeginExecute_EndExecuteNotInvokedOnAsyncResultFromPreviousInvocation.cs +++ b/test/Renci.SshNet.Tests/Classes/SshCommandTest_BeginExecute_EndExecuteNotInvokedOnAsyncResultFromPreviousInvocation.cs @@ -9,6 +9,7 @@ using Renci.SshNet.Channels; using Renci.SshNet.Tests.Common; +using Renci.SshNet.V2; namespace Renci.SshNet.Tests.Classes { @@ -19,7 +20,7 @@ public class SshCommandTest_BeginExecute_EndExecuteNotInvokedOnAsyncResultFromPr private Mock _channelSessionMock; private string _commandText; private Encoding _encoding; - private SshCommand _sshCommand; + private V2.SshCommand _sshCommand; private InvalidOperationException _actualException; protected override void OnInit() @@ -45,7 +46,7 @@ private void Arrange() _channelSessionMock.InSequence(seq).Setup(p => p.Open()); _channelSessionMock.InSequence(seq).Setup(p => p.SendExecRequest(_commandText)).Returns(true); - _sshCommand = new SshCommand(_sessionMock.Object, _commandText, _encoding); + _sshCommand = new V2.SshCommand(_sessionMock.Object, _commandText, _encoding); _sshCommand.BeginExecute(); } diff --git a/test/Renci.SshNet.Tests/Classes/SshCommandTest_Dispose.cs b/test/Renci.SshNet.Tests/Classes/SshCommandTest_Dispose.cs index 60804ee63..59322932d 100644 --- a/test/Renci.SshNet.Tests/Classes/SshCommandTest_Dispose.cs +++ b/test/Renci.SshNet.Tests/Classes/SshCommandTest_Dispose.cs @@ -11,6 +11,7 @@ using Renci.SshNet.Channels; using Renci.SshNet.Common; using Renci.SshNet.Tests.Common; +using Renci.SshNet.V2; namespace Renci.SshNet.Tests.Classes { @@ -21,7 +22,7 @@ public class SshCommandTest_Dispose : TestBase private Mock _channelSessionMock; private string _commandText; private Encoding _encoding; - private SshCommand _sshCommand; + private V2.SshCommand _sshCommand; private Stream _outputStream; private Stream _extendedOutputStream; @@ -48,7 +49,7 @@ private void Arrange() _channelSessionMock.InSequence(seq).Setup(p => p.SendExecRequest(_commandText)).Returns(true); _channelSessionMock.InSequence(seq).Setup(p => p.Dispose()); - _sshCommand = new SshCommand(_sessionMock.Object, _commandText, _encoding); + _sshCommand = new V2.SshCommand(_sessionMock.Object, _commandText, _encoding); _sshCommand.BeginExecute(); _outputStream = _sshCommand.OutputStream; diff --git a/test/Renci.SshNet.Tests/Classes/SshCommandTest_EndExecute_AsyncResultFromOtherInstance.cs b/test/Renci.SshNet.Tests/Classes/SshCommandTest_EndExecute_AsyncResultFromOtherInstance.cs index 89398051f..9239d5105 100644 --- a/test/Renci.SshNet.Tests/Classes/SshCommandTest_EndExecute_AsyncResultFromOtherInstance.cs +++ b/test/Renci.SshNet.Tests/Classes/SshCommandTest_EndExecute_AsyncResultFromOtherInstance.cs @@ -9,6 +9,7 @@ using Renci.SshNet.Channels; using Renci.SshNet.Tests.Common; +using Renci.SshNet.V2; namespace Renci.SshNet.Tests.Classes { @@ -20,8 +21,8 @@ public class SshCommandTest_EndExecute_AsyncResultFromOtherInstance : TestBase private Mock _channelSessionBMock; private string _commandText; private Encoding _encoding; - private SshCommand _sshCommandA; - private SshCommand _sshCommandB; + private V2.SshCommand _sshCommandA; + private V2.SshCommand _sshCommandB; private ArgumentException _actualException; private IAsyncResult _asyncResultB; @@ -62,10 +63,10 @@ private void Arrange() .Setup(p => p.SendExecRequest(_commandText)) .Returns(true); - _sshCommandA = new SshCommand(_sessionMock.Object, _commandText, _encoding); + _sshCommandA = new V2.SshCommand(_sessionMock.Object, _commandText, _encoding); _ = _sshCommandA.BeginExecute(); - _sshCommandB = new SshCommand(_sessionMock.Object, _commandText, _encoding); + _sshCommandB = new V2.SshCommand(_sessionMock.Object, _commandText, _encoding); _asyncResultB = _sshCommandB.BeginExecute(); } diff --git a/test/Renci.SshNet.Tests/Classes/SshCommandTest_EndExecute_AsyncResultIsNull.cs b/test/Renci.SshNet.Tests/Classes/SshCommandTest_EndExecute_AsyncResultIsNull.cs index 8105fa0e4..542a55264 100644 --- a/test/Renci.SshNet.Tests/Classes/SshCommandTest_EndExecute_AsyncResultIsNull.cs +++ b/test/Renci.SshNet.Tests/Classes/SshCommandTest_EndExecute_AsyncResultIsNull.cs @@ -8,6 +8,7 @@ using Moq; using Renci.SshNet.Tests.Common; +using Renci.SshNet.V2; namespace Renci.SshNet.Tests.Classes { @@ -17,7 +18,7 @@ public class SshCommandTest_EndExecute_AsyncResultIsNull : TestBase private Mock _sessionMock; private string _commandText; private Encoding _encoding; - private SshCommand _sshCommand; + private V2.SshCommand _sshCommand; private IAsyncResult _asyncResult; private ArgumentNullException _actualException; @@ -37,7 +38,7 @@ private void Arrange() _encoding = Encoding.UTF8; _asyncResult = null; - _sshCommand = new SshCommand(_sessionMock.Object, _commandText, _encoding); + _sshCommand = new V2.SshCommand(_sessionMock.Object, _commandText, _encoding); } private void Act() diff --git a/test/Renci.SshNet.Tests/Classes/SshCommandTest_EndExecute_ChannelOpen.cs b/test/Renci.SshNet.Tests/Classes/SshCommandTest_EndExecute_ChannelOpen.cs index af8a9f56e..7e339803a 100644 --- a/test/Renci.SshNet.Tests/Classes/SshCommandTest_EndExecute_ChannelOpen.cs +++ b/test/Renci.SshNet.Tests/Classes/SshCommandTest_EndExecute_ChannelOpen.cs @@ -11,6 +11,7 @@ using Renci.SshNet.Common; using Renci.SshNet.Messages.Connection; using Renci.SshNet.Tests.Common; +using Renci.SshNet.V2; namespace Renci.SshNet.Tests.Classes { @@ -21,7 +22,7 @@ public class SshCommandTest_EndExecute_ChannelOpen : TestBase private Mock _channelSessionMock; private string _commandText; private Encoding _encoding; - private SshCommand _sshCommand; + private V2.SshCommand _sshCommand; private IAsyncResult _asyncResult; private string _actual; private string _dataA; @@ -61,7 +62,7 @@ private void Arrange() .Returns(true); _channelSessionMock.InSequence(seq).Setup(p => p.Dispose()); - _sshCommand = new SshCommand(_sessionMock.Object, _commandText, _encoding); + _sshCommand = new V2.SshCommand(_sessionMock.Object, _commandText, _encoding); _asyncResult = _sshCommand.BeginExecute(); _channelSessionMock.Raise(c => c.DataReceived += null,