SSH.NET is a .NET library implementing the SSH2 client protocol. It is inspired by a port of the Java library JSch called Sharp.SSH. It allows you to execute SSH commands and also provides both SCP and SFTP functionality.
In this article, I’ll show you how to download a complete directory tree using SSH.NET.
First you’ll need to add a few usings:
using System;
using System.IO;
using Renci.SshNet;
using Renci.SshNet.Common;
using Renci.SshNet.Sftp;
SSH.NET can be added to your project using NuGet.
In order to work with SFTP, you’ll need to get an instance of SftpClient. You can either directly give details like host, port, username and password or you can provide a ConnectionInfo object. In this example we’ll use a KeyboardInteractiveConnectionInfo. This is required if the server expects an interactive keyboard authentication providing the password. The alternatives are PasswordConnectionInfo and PrivateKeyConnectionInfo.
First we’ll create the ConnectionInfo object:
var connectionInfo = new KeyboardInteractiveConnectionInfo(Host, Port, Username);
Host, Port and Username are constants I’ve defined before.
Then we need to define a delegate which will get the prompts returned by the server and will send the password when requested:
connectionInfo.AuthenticationPrompt += delegate(object sender, AuthenticationPromptEventArgs e)
{
foreach (var prompt in e.Prompts)
{
if (prompt.Request.Equals("Password: ", StringComparison.InvariantCultureIgnoreCase))
{
prompt.Response = Password;
}
}
};
It waits for the prompt “Password: ” and sends the password I’ve defined in a constant called Password.
Then using this SFTP client, we’ll connect to the server and download the contents of the directory recursively:
using (var client = new SftpClient(connectionInfo))
{
client.Connect();
DownloadDirectory(client, Source, Destination);
}
Source is the directory you want to download on the remote server and destination is the local directory e.g.:
private const string Source = "/tmp";
private const string Destination = @"c:\temp";
Now we’ll define the DownloadDirectory method. It will get the directory listing and iterate through the entries. Files will be downloaded and for each directory in there, we’ll recursively call the DownloadDirectory method:
private static void DownloadDirectory(SftpClient client, string source, string destination)
{
var files = client.ListDirectory(source);
foreach (var file in files)
{
if (!file.IsDirectory && !file.IsSymbolicLink)
{
DownloadFile(client, file, destination);
}
else if (file.IsSymbolicLink)
{
Console.WriteLine("Ignoring symbolic link {0}", file.FullName);
}
else if (file.Name != "." && file.Name != "..")
{
var dir = Directory.CreateDirectory(Path.Combine(destination, file.Name));
DownloadDirectory(client, file.FullName, dir.FullName);
}
}
}
I am ignoring symbolic links because trying to download them just fails and the SftpFile class provides no way to find what this link points to. “.” and “..” are also ignored.
Now let’s see how to download a single file:
private static void DownloadFile(SftpClient client, SftpFile file, string directory)
{
Console.WriteLine("Downloading {0}", file.FullName);
using (Stream fileStream = File.OpenWrite(Path.Combine(directory, file.Name)))
{
client.DownloadFile(file.FullName, fileStream);
}
}
It’s pretty easy: you create a file stream to the destination file and use the DownloadFile method of the SFTP client to download the file.
That’s it ! Here the full code for your convenience:
using System;
using System.IO;
using Renci.SshNet;
using Renci.SshNet.Common;
using Renci.SshNet.Sftp;
namespace ConsoleApplication1
{
public static class SftpTest
{
private const string Host = "192.168.xxx.xxx";
private const int Port = 22;
private const string Username = "root";
private const string Password = "xxxxxxxx";
private const string Source = "/tmp";
private const string Destination = @"c:\temp";
public static void Main()
{
var connectionInfo = new KeyboardInteractiveConnectionInfo(Host, Port, Username);
connectionInfo.AuthenticationPrompt += delegate(object sender, AuthenticationPromptEventArgs e)
{
foreach (var prompt in e.Prompts)
{
if (prompt.Request.Equals("Password: ", StringComparison.InvariantCultureIgnoreCase))
{
prompt.Response = Password;
}
}
};
using (var client = new SftpClient(connectionInfo))
{
client.Connect();
DownloadDirectory(client, Source, Destination);
}
}
private static void DownloadDirectory(SftpClient client, string source, string destination)
{
var files = client.ListDirectory(source);
foreach (var file in files)
{
if (!file.IsDirectory && !file.IsSymbolicLink)
{
DownloadFile(client, file, destination);
}
else if (file.IsSymbolicLink)
{
Console.WriteLine("Ignoring symbolic link {0}", file.FullName);
}
else if (file.Name != "." && file.Name != "..")
{
var dir = Directory.CreateDirectory(Path.Combine(destination, file.Name));
DownloadDirectory(client, file.FullName, dir.FullName);
}
}
}
private static void DownloadFile(SftpClient client, SftpFile file, string directory)
{
Console.WriteLine("Downloading {0}", file.FullName);
using (Stream fileStream = File.OpenWrite(Path.Combine(directory, file.Name)))
{
client.DownloadFile(file.FullName, fileStream);
}
}
}
}
So except for the issue with the symbolic link, it works pretty good and is also quite fast.