Laden Sie Dateien mit PowerShell über FTP hoch

73

Ich möchte PowerShell verwenden, um Dateien mit FTP auf einen anonymen FTP-Server zu übertragen. Ich würde keine zusätzlichen Pakete verwenden. Wie?

Es darf kein Risiko bestehen, dass das Skript hängt oder abstürzt.

Magol
quelle
Der JAMS Job Scheduler bietet Cmdlets , die sichere Dateiübertragungen vereinfachen. Die Cmdlets erleichtern die Automatisierung von Übertragungen und Verbindungen mithilfe verschiedener Protokolle. (FTP, SFTP, etc ...)
user695859

Antworten:

83

Ich bin nicht sicher, ob Sie das Skript zu 100% kugelsicher machen können, damit es nicht hängt oder abstürzt, da es Dinge gibt, die außerhalb Ihrer Kontrolle liegen (was ist, wenn der Server während des Uploads die Stromversorgung verliert?) - aber dies sollte eine solide Grundlage für den Einstieg bieten:

# 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()
Goyuix
quelle
Wie fange ich Fehler ab? Was ist, wenn ich keine Verbindung herstellen kann? kann die Datei nicht senden? die Verbindung unterbrochen? Ich möchte Fehler behandeln und den Benutzer benachrichtigen.
Magol
15
Dies sind alles wirklich gute Einzelfragen, die sich auf PowerShell-Skripte im Allgemeinen beziehen und auf viel mehr Szenarien angewendet werden können als nur auf die Abwicklung von FTP-Transaktionen. Mein Rat: Durchsuchen Sie das PowerShell-Tag hier und informieren Sie sich über die Fehlerbehandlung. Das meiste, was in diesem Skript schief gehen könnte, löst eine Ausnahme aus. Wickeln Sie das Skript einfach in etwas ein, das damit umgehen kann.
Goyuix
2
Keine gute Lösung für große Zip-Dateien. Wenn ich "$ content = gc -en Byte C: \ mybigfile.zip" versuche, hat die Verarbeitung von Powershell lange gedauert. Die von @CyrilGupta vorgeschlagene Lösung funktioniert für mich besser.
Wallybh
1
Wahrscheinlich sollte die Datei immer in Blöcke aufgeteilt werden, um zu vermeiden, dass $ content länger als möglich verarbeitet wird. So etwas wie das asynchrone Beispiel in der Dokumentation .
jl.
Nur eine kurze Anmerkung aus meiner Erfahrung - dies hat bei mir nicht funktioniert, bis ich die Anmeldeinformationszeile entfernt habe (mit anonymem Zugriff) - nicht sicher warum!
Dewi Rees
47

Es gibt auch andere Möglichkeiten. Ich habe das folgende Skript verwendet:

$File = "D:\Dev\somefilename.zip";
$ftp = "ftp://username:[email protected]/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);

Mit dem folgenden Befehl können Sie ein Skript für das Windows-FTP-Befehlszeilenprogramm ausführen

ftp -s:script.txt 

(Lesen Sie diesen Artikel )

Die folgende Frage zu SO beantwortet auch diese Frage: Wie schreibe ich FTP-Uploads und -Downloads?

Cyril Gupta
quelle
Es scheint keine Möglichkeit zu geben, den PASSIVEN Modus mit der hier vorgestellten ersten Option auszuschalten.
Dan
2
Wenn Ihr Kennwort Zeichen enthält, die in einer URL nicht zulässig sind, wird beim Erstellen der Kennwörter $uriein Fehler ausgegeben. Ich ziehe es vor, die Anmeldeinformationen auf dem Client $webclient.Credentials = New-Object System.Net.NetworkCredential($user,$pass)
festzulegen
Das passive Problem war tatsächlich ein Vorteil beim Umgang mit dem FTP-Dienst von box.com (der nur den passiven Modus unterstützt). In erneut nicht zugelassenen Zeichen in URL: Dies sollte hilfreich sein ... eingebautes Dienstprogramm zum Codieren / Decodieren von URLs
Justin
Diese Lösung funktioniert sogar mit PowerShell Core 6.1 unter macOS
HairOfTheDog
29

Ich werde nicht behaupten, dass dies eleganter ist als die Lösung mit den höchsten Stimmen ... aber das ist auf seine eigene Weise cool (zumindest in meinen Augen 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

Wie Sie sehen können, wird dieser dinky integrierte Windows-FTP-Client verwendet. Viel kürzer und unkomplizierter. Ja, ich habe das tatsächlich benutzt und es funktioniert!

Dexter Legaspi
quelle
2
Und wenn Sie jemals eine andere FTP-Variante verwenden, leiten Sie einfach ein anderes Programm weiter. Nett.
Federbrecher
2
Es ist etwas knifflig (wenn Sie den Benutzer- Benutzer- Pass in drei Zeilen unterbrechen, funktioniert es nicht, im Gegensatz zur Verwendung einer Skriptdatei) und undokumentiert (was es der -in-Schalter in FTP ist), aber es hat funktioniert!
Basos
14

Einfachster Weg

Die einfachste Möglichkeit, eine Binärdatei mit PowerShell auf einen FTP-Server hochzuladen, ist die Verwendung von 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")

Erweiterte Optionen

Wenn Sie eine bessere Kontrolle benötigen, die WebClientdiese nicht bietet (wie TLS / SSL-Verschlüsselung usw.), verwenden Sie FtpWebRequest. Einfache Möglichkeit ist, einfach eine Kopie FileStreamzu FTP - Stream unter Verwendung von Stream.CopyTo:

$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()

Fortschrittsüberwachung

Wenn Sie den Upload-Fortschritt überwachen müssen, müssen Sie den Inhalt selbst in Blöcke kopieren:

$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)
}

$fileStream.CopyTo($ftpStream)

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

Ordner hochladen

Wenn Sie alle Dateien aus einem Ordner hochladen möchten, lesen Sie
PowerShell Script, um einen gesamten Ordner auf FTP hochzuladen

Martin Prikryl
quelle
6

Ich habe kürzlich für Powershell verschiedene Funktionen für die Kommunikation mit FTP geschrieben, siehe https://github.com/AstralisSomnium/PowerShell-No-Library-Just-Functions/blob/master/FTPModule.ps1 . Mit der zweiten Funktion unten können Sie einen ganzen lokalen Ordner an FTP senden. Im Modul befinden sich sogar Funktionen zum rekursiven Entfernen / Hinzufügen / Lesen von Ordnern und Dateien.

#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
    }
}
Patrick
quelle
Der ReadAllBytesliest die gesamte Datei in den Speicher. Das wird bei großen Dateien nicht funktionieren. Und es ist selbst für mittelgroße Dateien ineffizient.
Martin Prikryl
4

Hier ist meine super coole Version, WEIL ES EINE PROGRESS BAR HAT :-)

Das ist eine völlig nutzlose Funktion, ich weiß, aber sie sieht immer noch cool aus \ 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. Hilft sehr, wenn ich mich frage: "Hat es nicht mehr funktioniert oder ist es nur meine langsame ASDL-Verbindung?"

Alex
quelle
ziemlich ordentlich. Mit PowerShell Core 6.1.0 unter macOS wurde der Fortschrittsbalken angezeigt und die Datei hochgeladen, der Fortschrittsbalken wurde jedoch nie aktualisiert. (Ich habe mit einer 500-MB-Datei getestet, um sicherzugehen, dass genügend Zeit zum Aktualisieren vorhanden ist.)
HairOfTheDog
2

Sie können einfach das Hochladen von Dateien über PowerShell wie folgt durchführen. Das vollständige Projekt finden Sie auf Github unter 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
            }


        }  
    }
}   
Edouard Kombo
quelle
2

Die Lösung von Goyuix funktioniert hervorragend, aber wie dargestellt wird folgende Fehlermeldung angezeigt : "Der angeforderte FTP-Befehl wird bei Verwendung des HTTP-Proxys nicht unterstützt."

Das Hinzufügen dieser Zeile nach dem $ftp.UsePassive = $trueBeheben des Problems für mich:

$ftp.Proxy = $null;
Mike Brady
quelle
1

Sie können diese Funktion verwenden:

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"
    }
}

Diese Funktion sendet die angegebene Datei per FTP . Sie müssen die Funktion mit folgenden Parametern aufrufen:

  • userFTP = "anonym" standardmäßig oder Ihr Benutzername
  • passFTP = "anonym" standardmäßig oder Ihr Passwort
  • serverFTP = IP-Adresse des FTP-Servers
  • localFile = Zu sendende Datei
  • remotePath = der Pfad auf dem FTP-Server

Zum Beispiel :

SendByFTP -userFTP "USERNAME" -passFTP "PASSWORD" -serverFTP "MYSERVER" -localFile "toto.zip" -remotePath "path/on/the/FTP/"
hasma
quelle
Bitte erläutern Sie, was Ihr Code bewirkt. Nur-Code-Antworten werden im Stapelüberlauf als schlechte Qualität angesehen.
Quinz
Sie können die Join-PathURL auf diese Weise nicht verwenden . Join-PathVerwendet standardmäßig Backslashes, während URL Schrägstriche verwendet. + Sie müssen auch URL-codieren userFTPund passFTP.
Martin Prikryl