SFTP stands for SSH File Transfer Protocol or Secure File Transfer Protocol, is a separate protocol packaged with SSH that works in a similar way over a totally secure connection. Although SFTP is integrated into many graphical tools that your users could use, if you are a developer, you can integrate such feature in your app. In this way you can handle what the users really do when they work with this tool and they'll keep using a single application for everything, namely yours.
In this article we'll show you how to install and how to achieve some tipical duties when you work with SFTP in WinForms C# using the widely known SSH.NET library.
1. Install SSH.NET
As first step, proceed to install the SSH.NET library in your project via NuGET. SSH.NET is a Secure Shell (SSH) library for .NET, optimized for parallelism. This project was inspired by Sharp.SSH library which was ported from java and it seems like was not supported for quite some time. This library is a complete rewrite using .NET 4.0, without any third party dependencies, using parallelism to achieve the best performance possible. It provides SFTP functionality for both synchronous and asynchronous operations, that's exactly what we need.
Open your project in Visual Studio and go to the Solution Explorer at the top right area of the window and do right click on the solution of your project. From the context menu select the Manage NuGet packages option:
From the emergent window (or tab) navigate to the Browse tab and search for SSH.NET. From the result list select the first option by the authro Renci and proceed to install:
Once the installation of the library finishes you will be able to use it on your project without a problem. Don't forget to include the SshNet type at the top of your class (where you want to use it) and others:
using Renci.SshNet;
using Renci.SshNet.Sftp;
using System.IO;
2. Usage
The usage will be shown basically with a lot of examples about how you can achieve the most tipical tasks that you need to achieve with SFTP:
List files from a directory
You can list the content of a directory using the following snippet (synchronously) that uses the SFTPClient.ListDirectory
method:
/// <summary>
/// List a remote directory in the console.
/// </summary>
private void listFiles()
{
string host = @"yourSftpServer.com";
string username = "root";
string password = @"p4ssw0rd";
string remoteDirectory = "/some/example/directory";
using (SftpClient sftp = new SftpClient(host, username, password))
{
try
{
sftp.Connect();
var files = sftp.ListDirectory(remoteDirectory);
foreach (var file in files)
{
Console.WriteLine(file.Name);
}
sftp.Disconnect();
}
catch (Exception e)
{
Console.WriteLine("An exception has been caught " + e.ToString());
}
}
}
Or asynchronously by running the code in another thread:
Thread myThread = new System.Threading.Thread(delegate () {
string remoteDirectory = "/some/example/directory";
string host = @"yourSftpServer.com";
string username = "root";
string password = @"p4ssw0rd";
using (SftpClient sftp = new SftpClient(host, username, password))
{
try
{
sftp.Connect();
var files = sftp.ListDirectory(remoteDirectory);
foreach (var file in files)
{
Console.WriteLine(file.Name);
}
sftp.Disconnect();
}
catch (Exception er)
{
Console.WriteLine("An exception has been caught " + er.ToString());
}
}
});
myThread.Start();
Connecting with key file and password
If you use a private key file and simultaneously a password to connect to your SFTP server, then you can use the following snippet to create your connection:
string host = @"yourSftpServer.com";
string username = "root";
string password = @"p4ssw0rd";
PrivateKeyFile keyFile = new PrivateKeyFile(@"path/to/OpenSsh-RSA-key.ppk");
var keyFiles = new[] { keyFile };
var methods = new List<AuthenticationMethod>();
methods.Add(new PasswordAuthenticationMethod(username, password));
methods.Add(new PrivateKeyAuthenticationMethod(username, keyFiles));
ConnectionInfo con = new ConnectionInfo(host, 22, username, methods.ToArray());
using (var client = new SftpClient(con))
{
client.Connect();
// Do what you need with the client !
client.Disconnect();
}
Downloading a single file
To download a file you can use the SFTPClient.DownloadFile
method and to write it locally using System.IO.File.OpenWrite
method:
/// <summary>
/// Downloads a file in the desktop synchronously
/// </summary>
public void downloadFile()
{
string host = @"yourSftpServer.com";
string username = "root";
string password = @"p4ssw0rd";
// Path to file on SFTP server
string pathRemoteFile = "/var/www/vhosts/some-folder/file_server.txt";
// Path where the file should be saved once downloaded (locally)
string pathLocalFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "download_sftp_file.txt");
using (SftpClient sftp = new SftpClient(host, username, password))
{
try
{
sftp.Connect();
Console.WriteLine("Downloading {0}", pathRemoteFile);
using (Stream fileStream = File.OpenWrite(pathLocalFile))
{
sftp.DownloadFile(pathRemoteFile, fileStream);
}
sftp.Disconnect();
}
catch (Exception er)
{
Console.WriteLine("An exception has been caught " + er.ToString());
}
}
}
Or you can make it asynchronously creating another thread and executing your code inside:
Thread myThread = new System.Threading.Thread(delegate () {
string host = @"yourSftpServer.com";
string username = "root";
string password = @"p4ssw0rd";
// Path to file on SFTP server
string pathRemoteFile = "/var/www/vhosts/some-folder/file_server.txt";
// Path where the file should be saved once downloaded (locally)
string pathLocalFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "download_sftp_file.txt");
using (SftpClient sftp = new SftpClient(host, username, password))
{
try
{
sftp.Connect();
Console.WriteLine("Downloading {0}", pathRemoteFile);
using (Stream fileStream = File.OpenWrite(pathLocalFile))
{
sftp.DownloadFile(pathRemoteFile, fileStream);
}
sftp.Disconnect();
}
catch (Exception er)
{
Console.WriteLine("An exception has been caught " + er.ToString());
}
}
});
myThread.Start();
Downloading an entire directory
If you need to download an entire directory (even subfolders and subfile) you will need to create 2 functions and one of them recursive. The first function will be DownloadFile, that allows you through a remote directory and the local path as arguments download the file. The second functions is the DownloadDirectory method, that will list all the files in the providen directory and will iterate over them. Respectively, if the item is a file then it will use the DownloadFile method to download it or if it's a folder, then it will create it:
/// <summary>
/// Downloads a remote directory into a local directory
/// </summary>
/// <param name="client"></param>
/// <param name="source"></param>
/// <param name="destination"></param>
private void DownloadDirectory(SftpClient client, string source, string destination, bool recursive = false)
{
// List the files and folders of the directory
var files = client.ListDirectory(source);
// Iterate over them
foreach (SftpFile file in files)
{
// If is a file, download it
if (!file.IsDirectory && !file.IsSymbolicLink)
{
DownloadFile(client, file, destination);
}
// If it's a symbolic link, ignore it
else if (file.IsSymbolicLink)
{
Console.WriteLine("Symbolic link ignored: {0}", file.FullName);
}
// If its a directory, create it locally (and ignore the .. and .=)
//. is the current folder
//.. is the folder above the current folder -the folder that contains the current folder.
else if (file.Name != "." && file.Name != "..")
{
var dir = Directory.CreateDirectory(Path.Combine(destination, file.Name));
// and start downloading it's content recursively :) in case it's required
if (recursive)
{
DownloadDirectory(client, file.FullName, dir.FullName);
}
}
}
}
/// <summary>
/// Downloads a remote file through the client into a local directory
/// </summary>
/// <param name="client"></param>
/// <param name="file"></param>
/// <param name="directory"></param>
private 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);
}
}
As next, you can proceed to create the client with your credentials and start the download of your remote directory using the previously created method. Note that as the download of an entire directory will take time, it's recommendable to use it only in the asynchronous way (creating a thread), however this is just a recommendation and you're able to unwrap the code:
Thread myThread = new System.Threading.Thread(delegate () {
string host = @"yourSftpServer.com";
string username = "root";
string password = @"p4ssw0rd";
// Path to folder on SFTP server
string pathRemoteDirectory = "/var/www/vhosts/some-folder-to-download";
// Path where the file should be saved once downloaded (locally)
string pathLocalDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "example-local-folder");
using (SftpClient sftp = new SftpClient(host, username, password))
{
try
{
sftp.Connect();
// By default, the method doesn't download subdirectories
// therefore you need to indicate that you want their content too
bool recursiveDownload = true;
// Start download of the directory
DownloadDirectory(
sftp,
pathRemoteDirectory,
pathLocalDirectory,
recursiveDownload
);
sftp.Disconnect();
}
catch (Exception er)
{
Console.WriteLine("An exception has been caught " + er.ToString());
}
}
});
myThread.Start();
Delete remote file
To delete a file from a remote directory use the SFTPClient.DeleteFile
method:
/// <summary>
/// Delete a remote file
/// </summary>
private void deleteFile()
{
string host = @"yourSftpServer.com";
string username = "root";
string password = @"p4ssw0rd";
// Path to folder on SFTP server
string pathRemoteFileToDelete = "/var/www/vhosts/folder/somefile.txt";
using (SftpClient sftp = new SftpClient(host, username, password))
{
try
{
sftp.Connect();
// Delete file
sftp.DeleteFile(pathRemoteFileToDelete);
sftp.Disconnect();
}
catch (Exception er)
{
Console.WriteLine("An exception has been caught " + er.ToString());
}
}
}
Happy coding !