【问题标题】:Upload files with FTP using PowerShell使用 PowerShell 通过 FTP 上传文件
【发布时间】:2010-12-24 10:54:31
【问题描述】:

我想使用 PowerShell 通过 FTP 将文件传输到匿名 FTP 服务器。我不会使用任何额外的软件包。怎么样?

【问题讨论】:

  • JAMS 作业计划程序提供cmdlets,使安全文件传输变得容易。 cmdlet 使使用各种协议自动传输和连接变得简单。 (FTP、SFTP 等...)

标签: powershell ftp


【解决方案1】:

最简单的方法

使用 PowerShell 将二进制文件上传到 FTP 服务器最简单的方法是使用WebClient.UploadFile

$client = New-Object System.Net.WebClient
$client.Credentials =
    New-Object System.Net.NetworkCredential("username", "password")
$client.UploadFile(
    "ftp://ftp.example.com/remote/path/file.zip", "C:\local\path\file.zip")

高级选项

如果您需要更大的控制权,而 WebClient 不提供(如 TLS/SSL encryption 等),请使用 FtpWebRequest。简单的方法是使用Stream.CopyToFileStream 复制到FTP 流:

$request = [Net.WebRequest]::Create("ftp://ftp.example.com/remote/path/file.zip")
$request.Credentials =
    New-Object System.Net.NetworkCredential("username", "password")
$request.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile 

$fileStream = [System.IO.File]::OpenRead("C:\local\path\file.zip")
$ftpStream = $request.GetRequestStream()

$fileStream.CopyTo($ftpStream)

$ftpStream.Dispose()
$fileStream.Dispose()

进度监控

如果你需要监控一个上传进度,你必须自己分块复制内容:

$request = [Net.WebRequest]::Create("ftp://ftp.example.com/remote/path/file.zip")
$request.Credentials =
    New-Object System.Net.NetworkCredential("username", "password")
$request.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile 

$fileStream = [System.IO.File]::OpenRead("C:\local\path\file.zip")
$ftpStream = $request.GetRequestStream()

$buffer = New-Object Byte[] 10240
while (($read = $fileStream.Read($buffer, 0, $buffer.Length)) -gt 0)
{
    $ftpStream.Write($buffer, 0, $read)
    $pct = ($fileStream.Position / $fileStream.Length)
    Write-Progress `
        -Activity "Uploading" -Status ("{0:P0} complete:" -f $pct) `
        -PercentComplete ($pct * 100)
}

$ftpStream.Dispose()
$fileStream.Dispose()

上传文件夹

如果您想上传文件夹中的所有文件,请参阅
PowerShell Script to upload an entire folder to FTP

【讨论】:

    【解决方案2】:

    我最近为 powershell 编写了几个与 FTP 通信的函数,请参阅https://github.com/AstralisSomnium/PowerShell-No-Library-Just-Functions/blob/master/FTPModule.ps1。下面的第二个功能,您可以将整个本地文件夹发送到 FTP。模块中甚至还有递归删除/添加/读取文件夹和文件的功能。

    #Add-FtpFile -ftpFilePath "ftp://myHost.com/folder/somewhere/uploaded.txt" -localFile "C:\temp\file.txt" -userName "User" -password "pw"
    function Add-FtpFile($ftpFilePath, $localFile, $username, $password) {
        $ftprequest = New-FtpRequest -sourceUri $ftpFilePath -method ([System.Net.WebRequestMethods+Ftp]::UploadFile) -username $username -password $password
        Write-Host "$($ftpRequest.Method) for '$($ftpRequest.RequestUri)' complete'"
        $content = $content = [System.IO.File]::ReadAllBytes($localFile)
        $ftprequest.ContentLength = $content.Length
        $requestStream = $ftprequest.GetRequestStream()
        $requestStream.Write($content, 0, $content.Length)
        $requestStream.Close()
        $requestStream.Dispose()
    }
    
    #Add-FtpFolderWithFiles -sourceFolder "C:\temp\" -destinationFolder "ftp://myHost.com/folder/somewhere/" -userName "User" -password "pw"
    function Add-FtpFolderWithFiles($sourceFolder, $destinationFolder, $userName, $password) {
        Add-FtpDirectory $destinationFolder $userName $password
        $files = Get-ChildItem $sourceFolder -File
        foreach($file in $files) {
            $uploadUrl ="$destinationFolder/$($file.Name)"
            Add-FtpFile -ftpFilePath $uploadUrl -localFile $file.FullName -username $userName -password $password
        }
    }
    
    #Add-FtpFolderWithFilesRecursive -sourceFolder "C:\temp\" -destinationFolder "ftp://myHost.com/folder/" -userName "User" -password "pw"
    function Add-FtpFolderWithFilesRecursive($sourceFolder, $destinationFolder, $userName, $password) {
        Add-FtpFolderWithFiles -sourceFolder $sourceFolder -destinationFolder $destinationFolder -userName $userName -password $password
        $subDirectories = Get-ChildItem $sourceFolder -Directory
        $fromUri = new-object System.Uri($sourceFolder)
        foreach($subDirectory in $subDirectories) {
            $toUri  = new-object System.Uri($subDirectory.FullName)
            $relativeUrl = $fromUri.MakeRelativeUri($toUri)
            $relativePath = [System.Uri]::UnescapeDataString($relativeUrl.ToString())
            $lastFolder = $relativePath.Substring($relativePath.LastIndexOf("/")+1)
            Add-FtpFolderWithFilesRecursive -sourceFolder $subDirectory.FullName -destinationFolder "$destinationFolder/$lastFolder" -userName $userName -password $password
        }
    }
    

    【讨论】:

    • ReadAllBytes 将整个文件读入内存。这不适用于大文件。即使对于中等大小的文件,它也是低效的。
    【解决方案3】:

    你可以使用这个功能:

    function SendByFTP {
        param (
            $userFTP = "anonymous",
            $passFTP = "anonymous",
            [Parameter(Mandatory=$True)]$serverFTP,
            [Parameter(Mandatory=$True)]$localFile,
            [Parameter(Mandatory=$True)]$remotePath
        )
        if(Test-Path $localFile){
            $remoteFile = $localFile.Split("\")[-1]
            $remotePath = Join-Path -Path $remotePath -ChildPath $remoteFile
            $ftpAddr = "ftp://${userFTP}:${passFTP}@${serverFTP}/$remotePath"
            $browser = New-Object System.Net.WebClient
            $url = New-Object System.Uri($ftpAddr)
            $browser.UploadFile($url, $localFile)    
        }
        else{
            Return "Unable to find $localFile"
        }
    }
    

    这个函数通过FTP发送指定的文件。 您必须使用这些参数调用该函数:

    • userFTP = 默认为“匿名”或您的用户名
    • passFTP = "anonymous" 默认或您的密码
    • serverFTP = FTP 服务器的 IP 地址
    • localFile = 要发送的文件
    • remotePath = FTP 服务器上的路径

    例如:

    SendByFTP -userFTP "USERNAME" -passFTP "PASSWORD" -serverFTP "MYSERVER" -localFile "toto.zip" -remotePath "path/on/the/FTP/"
    

    【讨论】:

    • 请详细说明您的代码的作用。仅代码答案在 Stack Overflow 中被视为质量差。
    • 您不能以这种方式在 URL 上使用Join-PathJoin-Path 默认使用反斜杠,而 URL 使用正斜杠 + 您还需要对 userFTPpassFTP 进行 URL 编码。
    【解决方案4】:

    Goyuix's solution 效果很好,但它给了我这个错误:“使用 HTTP 代理时不支持请求的 FTP 命令。”

    $ftp.UsePassive = $true 之后添加这一行为我解决了这个问题:

    $ftp.Proxy = $null;
    

    【讨论】:

      【解决方案5】:

      这是我的超酷版本,因为它有一个进度条:-)

      这是一个完全没用的功能,我知道,但它看起来仍然很酷 \m/ \m/

      $webclient = New-Object System.Net.WebClient
      Register-ObjectEvent -InputObject $webclient -EventName "UploadProgressChanged" -Action { Write-Progress -Activity "Upload progress..." -Status "Uploading" -PercentComplete $EventArgs.ProgressPercentage } > $null
      
      $File = "filename.zip"
      $ftp = "ftp://user:password@server/filename.zip"
      $uri = New-Object System.Uri($ftp)
      try{
          $webclient.UploadFileAsync($uri, $File)
      }
      catch  [Net.WebException]
      {
          Write-Host $_.Exception.ToString() -foregroundcolor red
      }
      while ($webclient.IsBusy) { continue }
      

      PS。当我想知道“它是否停止工作,或者只是我的慢速 ASDL 连接?”时,很有帮助?

      【讨论】:

      • 相当整洁。在 macOS 上使用 PowerShell Core 6.1.0 时,会显示进度条并且确实上传了文件,但进度条从未更新。 (我测试了一个 500MB 的文件,以确保它有足够的时间更新)
      【解决方案6】:

      您可以像这样简单地通过 PowerShell 处理文件上传。 完整的项目可以在 Github 上找到 https://github.com/edouardkombo/PowerShellFtp

      #Directory where to find pictures to upload
      $Dir= 'c:\fff\medias\'
      
      #Directory where to save uploaded pictures
      $saveDir = 'c:\fff\save\'
      
      #ftp server params
      $ftp = 'ftp://10.0.1.11:21/'
      $user = 'user'
      $pass = 'pass'
      
      #Connect to ftp webclient
      $webclient = New-Object System.Net.WebClient 
      $webclient.Credentials = New-Object System.Net.NetworkCredential($user,$pass)  
      
      #Initialize var for infinite loop
      $i=0
      
      #Infinite loop
      while($i -eq 0){ 
      
          #Pause 1 seconde before continue
          Start-Sleep -sec 1
      
          #Search for pictures in directory
          foreach($item in (dir $Dir "*.jpg"))
          {
              #Set default network status to 1
              $onNetwork = "1"
      
              #Get picture creation dateTime...
              $pictureDateTime = (Get-ChildItem $item.fullName).CreationTime
      
              #Convert dateTime to timeStamp
              $pictureTimeStamp = (Get-Date $pictureDateTime).ToFileTime()
      
              #Get actual timeStamp
              $timeStamp = (Get-Date).ToFileTime() 
      
              #Get picture lifeTime
              $pictureLifeTime = $timeStamp - $pictureTimeStamp
      
              #We only treat pictures that are fully written on the disk
              #So, we put a 2 second delay to ensure even big pictures have been fully wirtten   in the disk
              if($pictureLifeTime -gt "2") {    
      
                  #If upload fails, we set network status at 0
                  try{
      
                      $uri = New-Object System.Uri($ftp+$item.Name)
      
                      $webclient.UploadFile($uri, $item.FullName)
      
                  } catch [Exception] {
      
                      $onNetwork = "0"
                      write-host $_.Exception.Message;
                  }
      
                  #If upload succeeded, we do further actions
                  if($onNetwork -eq "1"){
                      "Copying $item..."
                      Copy-Item -path $item.fullName -destination $saveDir$item 
      
                      "Deleting $item..."
                      Remove-Item $item.fullName
                  }
      
      
              }  
          }
      }   
      

      【讨论】:

        【解决方案7】:

        还有其他一些方法。我使用了以下脚本:

        $File = "D:\Dev\somefilename.zip";
        $ftp = "ftp://username:password@example.com/pub/incoming/somefilename.zip";
        
        Write-Host -Object "ftp url: $ftp";
        
        $webclient = New-Object -TypeName System.Net.WebClient;
        $uri = New-Object -TypeName System.Uri -ArgumentList $ftp;
        
        Write-Host -Object "Uploading $File...";
        
        $webclient.UploadFile($uri, $File);
        

        您可以使用以下命令针对 windows FTP 命令行实用程序运行脚本

        ftp -s:script.txt 
        

        (查看this article

        关于 SO 的以下问题也回答了这个问题:How to script FTP upload and download?

        【讨论】:

        • 似乎没有办法使用此处提供的第一个选项关闭被动模式。
        • 如果您的密码包含 URL 中不允许的字符,则创建 $uri 会引发错误。我更喜欢在客户端设置凭据:$webclient.Credentials = New-Object System.Net.NetworkCredential($user,$pass)
        • 被动问题在处理 box.com FTP 服务(仅支持被动模式)时实际上是一个优势。在 URL 中不允许使用的字符中:这应该是有帮助的 ... built-in utility to encode/decode URL ,因此例如在Powershell ftps upload to box.com using passive mode
        • 此解决方案甚至适用于 macOS 上的 PowerShell Core 6.1
        【解决方案8】:

        我不确定您能否 100% 防止脚本不挂起或崩溃,因为有些事情超出了您的控制范围(如果服务器在上传过程中断电怎么办?) - 但这应该为获取你开始了:

        # create the FtpWebRequest and configure it
        $ftp = [System.Net.FtpWebRequest]::Create("ftp://localhost/me.png")
        $ftp = [System.Net.FtpWebRequest]$ftp
        $ftp.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile
        $ftp.Credentials = new-object System.Net.NetworkCredential("anonymous","anonymous@localhost")
        $ftp.UseBinary = $true
        $ftp.UsePassive = $true
        # read in the file to upload as a byte array
        $content = [System.IO.File]::ReadAllBytes("C:\me.png")
        $ftp.ContentLength = $content.Length
        # get the request stream, and write the bytes into it
        $rs = $ftp.GetRequestStream()
        $rs.Write($content, 0, $content.Length)
        # be sure to clean up after ourselves
        $rs.Close()
        $rs.Dispose()
        

        【讨论】:

        • 如何捕捉错误?如果无法连接怎么办?无法发送文件?连接中断?我想处理错误并通知用户。
        • 这些都是非常好的个别问题,通常与 PowerShell 脚本相关,并且可以应用于更多场景,而不仅仅是处理 ftp 事务。我的建议:在此处浏览 PowerShell 标记并阅读错误处理。该脚本中的大部分可能出错的地方都会引发异常,只需将脚本包装在可以处理该问题的东西中即可。
        • 对于大 zip 文件来说不是一个好的解决方案。当我尝试“$content = gc -en byte C:\mybigfile.zip”时,powershell 需要很长时间才能处理。 @CyrilGupta 提出的解决方案对我来说效果更好。
        • 可能应该始终将文件拆分成块,以避免 $content 超出您的处理时间。类似于 documentation 中的异步示例。
        • 只是我的经验中的一个简短说明 - 在我删除凭据行(使用匿名访问)之前这对我不起作用 - 不知道为什么!
        【解决方案9】:

        我不会声称这比投票率最高的解决方案更优雅......但这很酷(至少在我看来,LOL)以自己的方式:

        $server = "ftp.lolcats.com"
        $filelist = "file1.txt file2.txt"   
        
        "open $server
        user $user $password
        binary  
        cd $dir     
        " +
        ($filelist.split(' ') | %{ "put ""$_""`n" }) | ftp -i -in
        

        如您所见,它使用了极简的内置 Windows FTP 客户端。也更短更直接。是的,我已经实际使用过它并且它有效!

        【讨论】:

        • 如果您曾经使用过不同风格的 FTP,那么您只是通过管道连接到不同的程序。不错。
        • 这有点棘手(如果您将用户 user pass 分成三行,它将不起作用,这与使用脚本文件不同)并且没有记录(它是 ftp 中的 -in 开关),但它有效!
        • 很好的建议。我的测试显示正确的 FTP 命令是 ftp.exe -i -n -d - 这些开关都已记录在案。也许操作系统版本的功能已经改变,但我根本无法运行发布的版本。这里的关键开关是-n - 禁用自动登录。否则USER 命令无效。如果凭据位于单独的行上,即[USERNAME]⏎[PASS]⏎,则此重定向输入方法将失败,这在运行 FTP 命令时很典型。根据之前的评论,此处的输入必须OPEN [HOSTNAME] 之后的单行中包含 USER [USERNAME] [PASS]
        猜你喜欢
        • 2011-05-19
        • 2011-02-17
        • 1970-01-01
        • 2012-04-26
        • 2013-03-24
        • 1970-01-01
        相关资源
        最近更新 更多