Recursively download directory tree with custom error handling

The simplest way to download a directory tree is by using Session.GetFiles, providing path to a root of the tree as a source. This way a batch operation however stops on any error by default.

The following two approaches show how to override the default behaviour.

Particularly the first approach with Session.QueryReceived event is relevant for other batch operations as well, including Session.PutFiles and Session.SynchronizeDirectories.

Advertisement

Handling Session.QueryReceived event

You can choose how an error is processed by handling Session.QueryReceived event.

This example shows a basic implementation that outputs any errors encountered and continues.

C# Example

session.QueryReceived += (sender, e) =>
{
    Console.WriteLine("Error: {0}", e);
    e.Continue();
};
 
session.GetFiles("/home/user/*", @"d:\download\").Check();

Advertisement

VB.NET Example

AddHandler session.QueryReceived,
    Sub(sender As Object, e As QueryReceivedEventArgs)
        Console.WriteLine("Error: {0}", e.Message)
        e.Continue()
    End Sub
 
session.GetFiles("/home/user/*", "d:\download\").Check()

PowerShell Example

$session.add_QueryReceived( { 
    Write-Host "Error: $($_.Message)"
    $_.Continue()
} )
 
$session.GetFiles("/home/user/*", "d:\download\").Check()

Upload

The same mechanism can be used for uploads with Session.PutFiles or synchronization with Session.SynchronizeDirectories.

Explicit Implementation of a File Tree Download

In case you need even more control over a download process, you can implement walking of a directory tree explicitly and handle each file individually, as you need.

This example shows a basic implementation that outputs any errors encountered and continues.

C# Example

using System;
using System.Collections.Generic;
using System.IO;
using WinSCP;
 
class Example
{
    public static int Main()
    {
        try
        {
            // Setup session options
            SessionOptions sessionOptions = new SessionOptions
            {
                Protocol = Protocol.Sftp,
                HostName = "example.com",
                UserName = "user",
                Password = "mypassword",
                SshHostKeyFingerprint = "ssh-rsa 2048 xxxxxxxxxxx..."
            };
 
            string localPath = @"d:\backup";
            string remotePath = "/home/user";
 
            using (Session session = new Session())
            {
                // Connect
                session.Open(sessionOptions);
 
                // Enumerate files and directories to download
                var opts = WinSCP.EnumerationOptions.EnumerateDirectories |
                           WinSCP.EnumerationOptions.AllDirectories;
                IEnumerable<RemoteFileInfo> fileInfos =
                    session.EnumerateRemoteFiles(remotePath, null, opts);
 
                foreach (RemoteFileInfo fileInfo in fileInfos)
                {
                    string localFilePath =
                        RemotePath.TranslateRemotePathToLocal(
                            fileInfo.FullName, remotePath, localPath);
 
                    if (fileInfo.IsDirectory)
                    {
                        // Create local subdirectory, if it does not exist yet
                        if (!Directory.Exists(localFilePath))
                        {
                            Directory.CreateDirectory(localFilePath);
                        }
                    }
                    else
                    {
                        Console.WriteLine("Downloading file {0}...", fileInfo.FullName);
                        // Download file
                        string remoteFilePath = RemotePath.EscapeFileMask(fileInfo.FullName);
                        TransferOperationResult transferResult =
                            session.GetFiles(remoteFilePath, localFilePath);
 
                        // Did the download succeeded?
                        if (!transferResult.IsSuccess)
                        {
                            // Print error (but continue with other files)
                            Console.WriteLine(
                                "Error downloading file {0}: {1}",
                                fileInfo.FullName, transferResult.Failures[0].Message);
                        }
                    }
                }
            }
 
            return 0;
        }
        catch (Exception e)
        {
            Console.WriteLine("Error: {0}", e);
            return 1;
        }
    }
}

Advertisement

PowerShell Example

param (
    # Use Generate Session URL function to obtain a value for -sessionUrl parameter.
    [Parameter(Mandatory = $True)]
    $sessionUrl = "sftp://user:mypassword;fingerprint=ssh-rsa-xxxxxxxxxxx...@example.com/",
    [Parameter(Mandatory = $True)]
    $remotePath,
    [Parameter(Mandatory = $True)]
    $localPath
)
 
try
{
    # Load WinSCP .NET assembly
    Add-Type -Path "WinSCPnet.dll"
 
    # Setup session options from URL
    $sessionOptions = New-Object WinSCP.SessionOptions
    $sessionOptions.ParseUrl($sessionUrl)
 
    $session = New-Object WinSCP.Session
    $session.SessionLogPath = "session.log"
 
    try
    {
        # Connect
        $session.Open($sessionOptions)
 
        # Enumerate files and directories to download
        $fileInfos =
            $session.EnumerateRemoteFiles(
                $remotePath, $Null,
                ([WinSCP.EnumerationOptions]::EnumerateDirectories -bor
                    [WinSCP.EnumerationOptions]::AllDirectories))
 
        foreach ($fileInfo in $fileInfos)
        {
            $localFilePath =
                [WinSCP.RemotePath]::TranslateRemotePathToLocal(
                    $fileInfo.FullName, $remotePath, $localPath)
 
            if ($fileInfo.IsDirectory)
            {
                # Create local subdirectory, if it does not exist yet
                if (!(Test-Path $localFilePath))
                {
                    New-Item $localFilePath -ItemType directory | Out-Null
                }
            }
            else
            {
                Write-Host "Downloading file $($fileInfo.FullName)..."
                # Download file
                $remoteFilePath = [WinSCP.RemotePath]::EscapeFileMask($fileInfo.FullName)
                $transferResult = $session.GetFiles($remoteFilePath, $localFilePath)
 
                # Did the download succeeded?
                if (!$transferResult.IsSuccess)
                {
                    # Print error (but continue with other files)
                    Write-Host (
                        "Error downloading file $($fileInfo.FullName): " +
                        "$($transferResult.Failures[0].Message)")
                }
            }
        }
    }
    finally
    {
        # Disconnect, clean up
        $session.Dispose()
    }
 
    exit 0
}
catch
{
    Write-Host "Error: $($_.Exception.Message)"
    exit 1
}

Advertisement

Upload

For an example of walking a local tree to upload files individually, see Recursively move files in directory tree to/from SFTP/FTP server while preserving source directory structure.

The upload example calls OperationResultBase.Check, so it aborts on any error. Just replace the call with OperationResultBase.IsSuccess test, as the download example above does. See Capturing results of operations.

Last modified: by martin