PowerShell可以使用FTP在单个传输中检索远程文件夹和子文件夹目录数据吗?

时间:2017-11-24 18:05:07

标签: powershell ftp ftpwebrequest

我有一个PowerShell脚本,用于检查远程位置文件的现有时间戳数据,以便与本地文件进行比较。

使用此代码段检索本地信息:

$SrcEntries = Get-ChildItem $FolderLocation -Recurse

然后,我可以使用以下代码段获取有关文件夹和文件的具体信息:

$Srcfolders = $SrcEntries | Where-Object{$_.PSIsContainer}
$SrcFiles = $SrcEntries | Where-Object{!$_.PSIsContainer}

目前使用此代码段检索远程信息,该代码段针对每个文件单独执行:

$Credentials = New-Object System.Net.NetworkCredential($FTPUser,$FTPPassword)
$FTPrequest = [System.Net.FtpWebRequest]::Create($RemoteFilePath)
$FTPrequest.Method = [System.Net.WebRequestMethods+FTP]::GetDateTimestamp
$FTPrequest.Credentials = $Credentials
$response = $FTPrequest.GetResponse().StatusDescription

然后解析$response以检索远程文件的时间戳。

以与检索本地数据相同的方式检索远程数据似乎更好,即在单个操作中与上面的第一个代码段一样,以避免多次往返远程服务器。

是否可以使用PowerShell在单个快速FTP操作中递归获取文件夹和文件目录数据?我该怎么做?

1 个答案:

答案 0 :(得分:2)

在没有任何外部库的情况下实现它并不容易。不幸的是,.NET Framework和PowerShell都没有明确支持递归列出FTP文件夹中的文件。

你必须自己实现:

  • 列出远程文件夹
  • 迭代条目,递归到子文件夹 - 再次列出它们等等。

第一个棘手的部分是识别子文件夹中的文件。使用.NET框架(FtpWebRequest)以便携方式无法做到这一点。遗憾的是,.NET框架不支持MLSD命令,这是在FTP协议中使用文件属性检索目录列表的唯一可移植方式。另请参阅Checking if object on FTP server is file or directory

您的选择是:

  • 对文件名执行操作,该文件名对于文件肯定会失败并对目录成功(反之亦然)。即您可以尝试下载“名称”。
  • 您可能很幸运,在您的具体情况下,您可以通过文件名告诉目录中的文件(即所有文件都有扩展名,而子目录则没有)
  • 您使用长目录列表(LIST command = ListDirectoryDetails方法)并尝试解析特定于服务器的列表。许多FTP服务器使用* nix样式列表,您可以在条目的最开始通过d标识目录。但是许多服务器使用不同的格式。以下示例使用此方法(假设为* nix格式)
function ListFtpDirectory($url, $credentials)
{
    $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]

        if ($permissions[0] -eq 'd')
        {
            Write-Host "Directory $name"

            $fileUrl = ($url + $name)
            ListFtpDirectory ($fileUrl + "/") $credentials
        }
        else
        {
            Write-Host "File $name"
        }
    }
}

使用如下功能:

$credentials = New-Object System.Net.NetworkCredential("user", "mypassword") 
$url = "ftp://ftp.example.com/directory/to/list/"
ListFtpDirectory $url $credentials

虽然上面的代码只会列出名称。由于你也需要时间戳,你甚至必须解析那些。如果条目是文件夹或文件,那就像确定一样复杂。

我没有PowerShell代码,但请查看我对其他类似问题的回答,这些问题显示了C#代码。它应该很容易转换为PowerShell:

如果您想避免解析特定于服务器的目录列表格式的麻烦,请使用支持MLSD命令和/或解析各种LIST列表格式的第三方库。

例如,使用WinSCP .NET assembly,您可以通过一次调用Session.EnumerateRemoteFiles递归地列出整个目录:

# 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
    $options =
        [WinSCP.EnumerationOptions]::EnumerateDirectories -bor
        [WinSCP.EnumerationOptions]::AllDirectories
    $fileInfos = $session.EnumerateRemoteFiles("/directory/to/list", $Null, $options)
    foreach ($fileInfo in $fileInfos)
    {
        Write-Host $fileInfo.FullName
    }
}
finally
{
    # Disconnect, clean up
    $session.Dispose()
}    

不仅代码更简单,更容易和独立于平台。它还可以通过RemoteFileInfo class轻松获取所有其他文件属性(大小,修改时间,权限,所有权)。

如果服务器支持,WinSCP在内部使用MLSD命令。如果没有,它使用LIST命令并支持许多不同的列表格式。

虽然您似乎想要比较本地和远程时间戳以同步文件夹。

WinSCP .NET程序集可以使用Session.SynchronizeDirectories进行一次调用。

您也可能会发现这些问题(由我回答)很有用:

(我是WinSCP的作者)