Recursively move files in directory tree to/from SFTP/FTP server while preserving source directory structure
When moving files to/from the server, WinSCP by defaults moves the subfolders too (removes them from the source directory).
If you want to preserve the source directory structure, you have to implement walking the source explicitly, moving file one by one, and thus preserving the directory structure.
Advertisement
Upload
C#
Use the DirectoryInfo.EnumerateFileSystemInfos
method to walk the source local tree.
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 = @"C:\data"; string remotePath = "/home/user/backup"; using (Session session = new Session()) { // Connect session.Open(sessionOptions); // Enumerate files and directories to upload IEnumerable<FileSystemInfo> fileInfos = new DirectoryInfo(localPath).EnumerateFileSystemInfos( "*", SearchOption.AllDirectories); foreach (FileSystemInfo fileInfo in fileInfos) { string remoteFilePath = RemotePath.TranslateLocalPathToRemote( fileInfo.FullName, localPath, remotePath); if (fileInfo.Attributes.HasFlag(FileAttributes.Directory)) { // Create remote subdirectory, if it does not exist yet if (!session.FileExists(remoteFilePath)) { session.CreateDirectory(remoteFilePath); } } else { Console.WriteLine("Moving file {0}...", fileInfo.FullName); // Upload file and remove original session.PutFiles(fileInfo.FullName, remoteFilePath, true).Check(); } } } return 0; } catch (Exception e) { Console.WriteLine("Error: {0}", e); return 1; } } }
Advertisement
PowerShell
You can install this script as an WinSCP extension by using this page URL in the Add Extension command.
# @name Upload and Delete Files # @command powershell.exe -ExecutionPolicy Bypass -File "%EXTENSION_PATH%" ^ # -sessionUrl "!E" -remotePath "!/" -sessionLogPath "%SessionLogPath%" ^ # -pause !& # @description Moves selected local files to a remote directory, ^ # but keeps local directory structure # @flag ApplyToDirectories # @version 5 # @homepage https://winscp.net/eng/docs/library_example_moves_files_keeping_directory_structure # @require WinSCP 5.16 # @option SessionLogPath -config sessionlogfile # @optionspage https://winscp.net/eng/docs/library_example_moves_files_keeping_directory_structure#options 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, $sessionLogPath = $Null, [Switch] $pause, [Parameter(Mandatory = $True, ValueFromRemainingArguments = $True, Position = 0)] $localPaths ) try { # Load WinSCP .NET assembly $assemblyPath = if ($env:WINSCP_PATH) { $env:WINSCP_PATH } else { $PSScriptRoot } Add-Type -Path (Join-Path $assemblyPath "WinSCPnet.dll") # Setup session options from URL $sessionOptions = New-Object WinSCP.SessionOptions $sessionOptions.ParseUrl($sessionUrl) $session = New-Object WinSCP.Session try { $session.SessionLogPath = $sessionLogPath # Connect $session.Open($sessionOptions) foreach ($localPath in $localPaths) { # If the selected item is file, find all contained files and folders recursively if (Test-Path $localPath -PathType container) { $files = @($localPath) + (Get-ChildItem $localPath -Recurse | Select-Object -ExpandProperty FullName) } else { $files = $localPath } $parentLocalPath = Split-Path -Parent (Resolve-Path $localPath) foreach ($localFilePath in $files) { $remoteFilePath = [WinSCP.RemotePath]::TranslateLocalPathToRemote( $localFilePath, $parentLocalPath, $remotePath) if (Test-Path $localFilePath -PathType container) { # Create remote subdirectory, if it does not exist yet if (!($session.FileExists($remoteFilePath))) { $session.CreateDirectory($remoteFilePath) } } else { Write-Host "Moving file $localFilePath to $remoteFilePath..." # Upload file and remove original $session.PutFiles($localFilePath, $remoteFilePath, $True).Check() } } } & "$env:WINSCP_PATH\WinSCP.exe" "$sessionUrl" /refresh "$remotePath" } finally { # Disconnect, clean up $session.Dispose() } $result = 0 } catch { Write-Host "Error: $($_.Exception.Message)" $result = 1 } # Pause if -pause switch was used if ($pause) { Write-Host "Press any key to exit..." [System.Console]::ReadKey() | Out-Null } exit $result
Advertisement
Options
In the Session log file, you can specify a path to a session log file.
In the Keyboard shortcut, you can specify a keyboard shortcut for the extension.
Download
For a download, you can use the code from the Explicit implementation of a file tree download section of Recursively download directory tree with custom error handling example.
Just pass a true
to the optional remove
parameter of the Session.GetFiles
.
C#
session.GetFiles(remoteFilePath, localFilePath, true).Check();
PowerShell
$session.GetFiles($remoteFilePath, $localFilePath, $True).Check()