The AlexFTPS library used in this question seems dead (has not been updated since 2011).
No external libraries
Alternatively, you can try to implement this without any external library. But, unfortunately, neither the .NET Framework nor PowerShell have explicit support for downloading all files in a directory (they allow only recursive file downloads).
You must implement this yourself:
- List the remote directory
- Repeat entries by uploading files (and possibly repeating to subdirectories - listing them again, etc.)
The challenge is to identify files from subdirectories. There is no way to do this in portable form using the .NET Framework ( FtpWebRequest or WebClient ). Unfortunately, the .NET Framework does not support the MLSD command, which is the only portable way to get a list of directories with file attributes in the FTP protocol. See also Checking whether an object on an FTP server is a file or directory .
Your options:
- If you know that the directory does not contain any subdirectories, use the
ListDirectory method ( NLST FTP command) and just upload all the "names" as files. - Perform an operation with a file name, which for sure will fail for the file and will succeed for directories (or vice versa). That is, you can try to download the "name".
- You may be lucky, and in your particular case you can distinguish a file from a directory by file name (i.e. all your files have an extension, but subdirectories are not)
- You are using a long list of directories (
LIST method = ListDirectoryDetails method) and trying to ListDirectoryDetails server-specific list. Many FTP servers use * nix-style listing, where you identify the directory by d at the very beginning of the entry. But many servers use a different format. The following example uses this approach (in * nix format)
function DownloadFtpDirectory($url, $credentials, $localPath) { $listRequest = [Net.WebRequest]::Create($url) $listRequest.Method = [System.Net.WebRequestMethods+Ftp]::ListDirectoryDetails $listRequest.Credentials = $credentials $lines = New-Object System.Collections.ArrayList $listResponse = $listRequest.GetResponse() $listStream = $listResponse.GetResponseStream() $listReader = New-Object System.IO.StreamReader($listStream) while (!$listReader.EndOfStream) { $line = $listReader.ReadLine() $lines.Add($line) | Out-Null } $listReader.Dispose() $listStream.Dispose() $listResponse.Dispose() foreach ($line in $lines) { $tokens = $line.Split(" ", 9, [StringSplitOptions]::RemoveEmptyEntries) $name = $tokens[8] $permissions = $tokens[0] $localFilePath = Join-Path $localPath $name $fileUrl = ($url + $name) if ($permissions[0] -eq 'd') { if (!(Test-Path $localFilePath -PathType container)) { Write-Host "Creating directory $localFilePath" New-Item $localFilePath -Type directory | Out-Null } DownloadFtpDirectory ($fileUrl + "/") $credentials $localFilePath } else { Write-Host "Downloading $fileUrl to $localFilePath" $downloadRequest = [Net.WebRequest]::Create($fileUrl) $downloadRequest.Method = [System.Net.WebRequestMethods+Ftp]::DownloadFile $downloadRequest.Credentials = $credentials $downloadResponse = $downloadRequest.GetResponse() $sourceStream = $downloadResponse.GetResponseStream() $targetStream = [System.IO.File]::Create($localFilePath) $buffer = New-Object byte[] 10240 while (($read = $sourceStream.Read($buffer, 0, $buffer.Length)) -gt 0) { $targetStream.Write($buffer, 0, $read); } $targetStream.Dispose() $sourceStream.Dispose() $downloadResponse.Dispose() } } }
Use a function such as:
$credentials = New-Object System.Net.NetworkCredential("user", "mypassword") $url = "ftp://ftp.example.com/directory/to/download/" DownloadFtpDirectory $url $credentials "C:\target\directory"
Code translated from my C # example to C # Uploading all files and subdirectories via FTP .
Using a third-party library
If you want to avoid problems with parsing server-specific directory list formats, use a third-party library that supports the MLSD command and / or parses various LIST list formats. And ideally, with support for downloading all files from a directory or even recursive downloads.
For example, using the WinSCP.NET assembly, you can load the entire directory with a single call to Session.GetFiles :
# Load WinSCP .NET assembly Add-Type -Path "WinSCPnet.dll" # Setup session options $sessionOptions = New-Object WinSCP.SessionOptions -Property @{ Protocol = [WinSCP.Protocol]::Ftp HostName = "ftp.example.com" UserName = "user" Password = "mypassword" } $session = New-Object WinSCP.Session try { # Connect $session.Open($sessionOptions) # Download files $session.GetFiles("/directory/to/download/*", "C:\target\directory\*").Check() } finally { # Disconnect, clean up $session.Dispose() }
Internally, WinSCP uses the MLSD command, if supported by the server. Otherwise, it uses the LIST command and supports dozens of different list formats.
Session.GetFiles is the default recursive method .
(I am the author of WinSCP)