Downloading a directory using SSH.NET SFTP in C#

BandaidMan picture BandaidMan · Sep 18, 2018 · Viewed 14.8k times · Source

I am using Renci.SSH and C# to connect to my Unix server from a Windows machine. My code works as expected when the directory contents are only files, but if the directory contains a folder, I get this

Renci.SshNet.Common.SshException: 'Failure'

This is my code, how can I update this to also download a directory (if exists)

private static void DownloadFile(string arc, string username, string password)
{
    string fullpath;
    string fp;
    var options = new ProgressBarOptions
    {
        ProgressCharacter = '.',
        ProgressBarOnBottom = true
    };

    using (var sftp = new SftpClient(Host, username, password))
    {
        sftp.Connect();
        fp = RemoteDir + "/" + arc;
        if (sftp.Exists(fp))     
            fullpath = fp;
        else
            fullpath = SecondaryRemoteDir + d + "/" + arc;

        if (sftp.Exists(fullpath))
        {
            var files = sftp.ListDirectory(fullpath);
            foreach (var file in files)
            {
                if (file.Name.ToLower().Substring(0, 1) != ".")
                {
                    Console.WriteLine("Downloading file from the server...");
                    Console.WriteLine();
                    using (var pbar = new ProgressBar(100, "Downloading " + file.Name + "....", options))
                    {
                        SftpFileAttributes att = sftp.GetAttributes(fullpath + "/" + file.Name);
                        var fileSize = att.Size;
                        var ms = new MemoryStream();
                        IAsyncResult asyncr = sftp.BeginDownloadFile(fullpath + "/" + file.Name, ms);
                        SftpDownloadAsyncResult sftpAsyncr = (SftpDownloadAsyncResult)asyncr;
                        int lastpct = 0;
                        while (!sftpAsyncr.IsCompleted)
                        {
                            int pct = (int)((long)sftpAsyncr.DownloadedBytes / fileSize) * 100;
                            if (pct > lastpct)
                                for (int i = 1; i < pct - lastpct; i++)
                                    pbar.Tick();
                        }
                        sftp.EndDownloadFile(asyncr);
                        Console.WriteLine("Writing File to disk...");
                        Console.WriteLine();
                        string localFilePath = "C:\" + file.Name;
                        var fs = new FileStream(localFilePath, FileMode.Create, FileAccess.Write);
                        ms.WriteTo(fs);
                        fs.Close();
                        ms.Close();
                    }
                }
            }
        }
        else
        {
            Console.WriteLine("The arc " + arc + " does not exist");
            Console.WriteLine();
            Console.WriteLine("Please press any key to close this window");
            Console.ReadKey();
        }
    }
}

Answer

Martin Prikryl picture Martin Prikryl · Sep 20, 2018

BeginDownloadFile downloads a file. You cannot use it to download a folder. For that you need to download contained files one by one.

The following example uses synchronous download (DownloadFile instead of BeginDownloadFile) for simplicity. After all, you are synchronously waiting for asynchronous download to complete anyway. To implement a progress bar with synchronous download, see Displaying progress of file download in a ProgressBar with SSH.NET.

public static void DownloadDirectory(
    SftpClient sftpClient, string sourceRemotePath, string destLocalPath)
{
    Directory.CreateDirectory(destLocalPath);
    IEnumerable<SftpFile> files = sftpClient.ListDirectory(sourceRemotePath);
    foreach (SftpFile file in files)
    {
        if ((file.Name != ".") && (file.Name != ".."))
        {
            string sourceFilePath = sourceRemotePath + "/" + file.Name;
            string destFilePath = Path.Combine(destLocalPath, file.Name);
            if (file.IsDirectory)
            {
                DownloadDirectory(sftpClient, sourceFilePath, destFilePath);
            }
            else
            {
                using (Stream fileStream = File.Create(destFilePath))
                {
                    sftpClient.DownloadFile(sourceFilePath, fileStream);
                }
            }
        }
    }
}