使用 PowerShell 从 FTP 下载最新文件

Download most recent file from FTP using PowerShell

我正在开发一个 PowerShell 脚本,它将从 FTP 站点提取文件。这些文件每小时上传到 FTP 站点,所以我需要下载最新的一个。我目前拥有的代码会下载今天的所有文件,而不仅仅是一个文件。如何让它只下载最新的文件?

这是我目前使用的代码

$ftpPath = 'ftp://***.***.*.*'
$ftpUser = '******'
$ftpPass = '******'
$localPath = 'C:\Temp'
$Date = get-date -Format "ddMMyyyy"
$Files = 'File1', 'File2'

function Get-FtpDir ($url, $credentials)
{
  $request = [Net.FtpWebRequest]::Create($url)
  if ($credentials) { $request.Credentials = $credentials }
  $request.Method = [System.Net.WebRequestMethods+FTP]::ListDirectory
  (New-Object IO.StreamReader $request.GetResponse().GetResponseStream()) -split "`r`n" 

}

$webclient = New-Object System.Net.WebClient 
$webclient.Credentials = New-Object System.Net.NetworkCredential($ftpUser,$ftpPass)  
$webclient.BaseAddress = $ftpPath

Foreach ( $item in $Files )
{
    Get-FTPDir $ftpPath $webclient.Credentials |
      ? { $_ -Like $item+$Date+'*' } |
      % {

          $webClient.DownloadFile($_, (Join-Path $localPath $_)) 
      }
}

FtpWebRequest 并不容易。对于您的任务,您需要知道文件时间戳。

不幸的是,没有真正可靠和有效的方法来使用 FtpWebRequest/.NET framework/PowerShell 提供的功能来检索时间戳,因为它们不支持 FTP MLSD命令。 MLSD 命令以标准化的机器可读格式提供远程目录列表。命令和格式由 RFC 3659.

标准化

.NET 框架支持的您可以使用的替代方案:

  • ListDirectoryDetails 方法(FTP LIST 命令)检索目录中所有文件的详细信息,然后处理 FTP 服务器特定格式的详细信息(*nix 格式类似于 ls *nix 命令是最常见的,缺点是格式可能会随着时间的推移而改变,对于较新的文件使用“5 月 8 日 17:48”格式,对于较旧的文件“ Oct 18 2009" 使用格式)
  • GetDateTimestamp 方法(FTP MDTM 命令)单独检索每个文件的时间戳。优点是响应由 RFC 3659 标准化为 YYYYMMDDHHMMSS[.sss]。缺点是你必须为每个文件发送一个单独的请求,效率很低。

一些参考资料:

  • C# class to parse WebRequestMethods.Ftp.ListDirectoryDetails FTP response
  • Parsing FtpWebRequest ListDirectoryDetails line
  • Retrieving creation date of file (FTP)

或者,使用支持 MLSD 命令的第 3 方 FTP 库,and/or 支持专有列表格式的解析。

例如WinSCP .NET assembly两者都支持。

示例代码:

# Load WinSCP .NET assembly
Add-Type -Path "WinSCPnet.dll"

# Setup session options
$sessionOptions = New-Object WinSCP.SessionOptions -Property @{
    Protocol = [WinSCP.Protocol]::Ftp
    HostName = "example.com"
    UserName = "user"
    Password = "mypassword"
}

$session = New-Object WinSCP.Session

# Connect
$session.Open($sessionOptions)

# Get list of files in the directory
$directoryInfo = $session.ListDirectory($remotePath)

# Select the most recent file
$latest =
    $directoryInfo.Files |
    Where-Object { -Not $_.IsDirectory } |
    Sort-Object LastWriteTime -Descending |
    Select-Object -First 1

# Any file at all?
if ($latest -eq $Null)
{
    Write-Host "No file found"
    exit 1
}

# Download the selected file
$sourcePath = [WinSCP.RemotePath]::EscapeFileMask($remotePath + $latest.Name)
$session.GetFiles($sourcePath, $localPath).Check()

有关完整代码,请参阅 Downloading the most recent file (PowerShell)

(我是WinSCP的作者)

我试过了,但出现错误:

Error: Exception calling "ListDirectory" with "1" argument(s): "Error listing directory '/path/'.
Could not retrieve directory listing
Can't open data connection for transfer of "/path/"

我在网上看了很多关于这个问题的资料,但找不到一个看起来很简单的解决方案,而且我不是网络设置向导。所以我选择了不同的方法。在我们的例子中,我想要自动下载的文件的文件名中指定了日期:backup_2018_08_03_020003_1048387.bak

所以我们可以在命令行 ftp 会话中使用 mget *2018_08_03* 来获取文件。

我们的备份程序是 运行 每天凌晨 1 点,所以我们每天都有一个可以获取的备份。

当然,如果有一个根据备份文件时间戳获取最新备份文件的脚本会更漂亮更好,以防万一最新备份出现问题或备份文件命名格式发生变化。该脚本只是一个为内部开发目的获取备份的脚本,所以如果它中断也没什么大不了的。稍后我会研究这个并检查我是否可以制定更清洁的解决方案。

我制作了一个批处理脚本,它只使用普通的 ftp 命令提示符脚本请求今天的备份文件。

正确设置今天日期的格式很重要。它必须正确匹配文件名中日期的格式。

如果你想使用脚本,你应该用你自己的信息替换变量。您还应该对 运行 它所在的目录具有写入权限。

这是我制作的脚本:

@Echo Off
Set _FTPServerName=xxx.xxx.xx.xxx
Set _UserName=Username
Set _Password=Password
Set _LocalFolder=C:\Temp
Set _RemoteFolder="/path/"
Set _Filename=*%date:~-4,4%_%date:~-7,2%_%date:~-10,2%*
Set _ScriptFile=ftptempscript
:: Create script
 >"%_ScriptFile%" Echo open %_FTPServerName%
>>"%_ScriptFile%" Echo %_UserName%
>>"%_ScriptFile%" Echo %_Password%
>>"%_ScriptFile%" Echo lcd %_LocalFolder%
>>"%_ScriptFile%" Echo cd %_RemoteFolder%
>>"%_ScriptFile%" Echo binary
>>"%_ScriptFile%" Echo mget -i %_Filename%
>>"%_ScriptFile%" Echo quit
:: Run script
ftp -s:"%_ScriptFile%"
del "%_ScriptFile%"