问题中使用的 AlexFTPS 库似乎已死(自 2011 年以来未更新)。
没有外部库
您可以尝试在没有任何外部库的情况下实现它。但不幸的是,.NET Framework 和 PowerShell 都没有明确支持下载目录中的所有文件(只允许递归文件下载)。
您必须自己实现:
- 列出远程目录
- 迭代条目,下载文件(以及可选地递归到子目录 - 再次列出它们等)
棘手的部分是从子目录中识别文件。FtpWebRequest
使用 .NET 框架(或WebClient
)无法以可移植的方式做到这一点。不幸的是,.NET 框架不支持该MLSD
命令,这是在 FTP 协议中检索具有文件属性的目录列表的唯一可移植方式。另请参阅检查 FTP 服务器上的对象是文件还是目录。
您的选择是:
- 如果您知道该目录不包含任何子目录,请使用该
ListDirectory
方法(NLST
FTP 命令)并将所有“名称”下载为文件。
- 对文件名执行操作,对于文件肯定会失败,而对于目录会成功(反之亦然)。即您可以尝试下载“名称”。
- 您可能很幸运,在您的特定情况下,您可以通过文件名来区分目录中的文件(即所有文件都有扩展名,而子目录没有)
- 您使用长目录列表(
LIST
命令 =ListDirectoryDetails
方法)并尝试解析特定于服务器的列表。许多 FTP 服务器使用 *nix 样式的列表,您可以通过d
条目开头的 来标识目录。但是许多服务器使用不同的格式。以下示例使用这种方法(假设 *nix 格式)
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 (($name -ne ".") -and ($name -ne ".."))
{
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()
}
}
}
使用如下功能:
$credentials = New-Object System.Net.NetworkCredential("user", "mypassword")
$url = "ftp://ftp.example.com/directory/to/download/"
DownloadFtpDirectory $url $credentials "C:\target\directory"
该代码是从我在 C# 中的 C# 示例中翻译的,通过 FTP 下载所有文件和子目录。
使用第三方库
如果您想避免解析服务器特定目录列表格式的麻烦,请使用支持MLSD
命令和/或解析各种LIST
列表格式的第 3 方库。理想情况下,支持从目录下载所有文件,甚至递归下载。
例如,使用WinSCP .NET 程序集,您可以通过一次调用下载整个目录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()
}
MLSD
如果服务器支持,WinSCP 在内部使用该命令。如果没有,它会使用该LIST
命令并支持数十种不同的列表格式。
该Session.GetFiles
方法默认是递归的。
(我是WinSCP的作者)