Upload files with FTP using PowerShell
Asked Answered
P

11

84

I want to use PowerShell to transfer files with FTP to an anonymous FTP server. I would not use any extra packages. How?

Patsypatt answered 8/12, 2009 at 14:38 Comment(1)
The JAMS Job Scheduler offers cmdlets that make secure file transfers easy. The cmdlets make it simple to automate transfers and connect using a variety of protocols. (FTP, SFTP, etc...)Obala
P
91

I am not sure you can 100% bullet proof the script from not hanging or crashing, as there are things outside your control (what if the server loses power mid-upload?) - but this should provide a solid foundation for getting you started:

# 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()
Phillipp answered 8/12, 2009 at 17:3 Comment(6)
How do I catch errors? What if I can't connect? can't send the file? the connection go down? I want to handle errors and notify the user.Patsypatt
Those are all really good individual questions that pertain to PowerShell scripting in general and can be applied to many more scenarios than just handling ftp transactions. My advice: Browse the PowerShell tag here and read up on error handling. Most of what could go wrong in this script will throw an exception, just wrap the script in something that will handle that.Phillipp
Not a good solution for big zip files. When I try "$content = gc -en byte C:\mybigfile.zip" powershell took a long time to process. The solution proposed by @CyrilGupta works better for me.Womankind
Probably should always split the file up in chunks to avoid getting $content longer than you can handle. Something like the async example in the documentation.Adair
Just a quick note from my experience - this didn't work for me until I removed the credentials line (using anonymous access) - not sure why!Aideaidedecamp
Is there no way to use ftp in powershell without a script?!Cheree
S
53

There are some other ways too. I have used the following script:

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

And you could run a script against the windows FTP command line utility using the following command

ftp -s:script.txt 

(Check out this article)

The following question on SO also answers this: How to script FTP upload and download?

Salvadorsalvadore answered 21/3, 2010 at 2:47 Comment(4)
There doesn't seem to be a way to turn off PASSIVE mode using the first option presented here.Interconnect
If your password contains characters that are not allowed in a URL, then creating the $uri throws an error. I prefer setting the credentials on the client: $webclient.Credentials = New-Object System.Net.NetworkCredential($user,$pass)Coco
The passive issue was actually an advantage when dealing with box.com FTP service (which only supports passive mode). In re disallowed characters in URL: this should be helpful ... built-in utility to encode/decode URL and thus e.g. in Powershell ftps upload to box.com using passive modeColtin
This solution even works with PowerShell Core 6.1 on macOSKhedive
N
32

I'm not gonna claim that this is more elegant than the highest-voted solution...but this is cool (well, at least in my mind LOL) in its own way:

$server = "ftp.lolcats.com"
$filelist = "file1.txt file2.txt"   

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

As you can see, it uses that dinky built-in windows FTP client. Much shorter and straightforward, too. Yes, I've actually used this and it works!

Nela answered 28/12, 2011 at 20:13 Comment(3)
And if you ever use a different flavor of FTP, you're just piping to a different program. Nice.Orthochromatic
It's kind of tricky (if you break the user user pass in three lines it does not work, unlike using a script file) and undocumented (what it the -in switch in ftp), but it Worked!Adobe
Great suggestion. My tests show the correct FTP command is ftp.exe -i -n -d- these switches are all documented. Maybe functionality has changed in OS version, but I couldn't get the posted version running at all. The critical switch here is -n - disable autologon. Or else the USER command is invalid. This redirected input method fails if the creds are on separate lines, i.e. [USERNAME]⏎[PASS]⏎, as typical when running FTP commands. The input here must have USER [USERNAME] [PASS] on a single line after the OPEN [HOSTNAME], per the previous comment.Trivium
H
26

Easiest way

The most trivial way to upload a binary file to an FTP server using PowerShell is using 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")

Advanced options

If you need a greater control, that WebClient does not offer (like TLS/SSL encryption, etc), use FtpWebRequest. Easy way is to just copy a FileStream to FTP stream using 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()

Progress monitoring

If you need to monitor an upload progress, you have to copy the contents by chunks yourself:

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

Uploading folder

If you want to upload all files from a folder, see
PowerShell Script to upload an entire folder to FTP

Hoang answered 5/10, 2017 at 9:28 Comment(0)
I
7

I recently wrote for powershell several functions for communicating with FTP, see https://github.com/AstralisSomnium/PowerShell-No-Library-Just-Functions/blob/master/FTPModule.ps1. The second function below, you can send a whole local folder to FTP. In the module are even functions for removing / adding / reading folders and files recursively.

#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
    }
}
Ingenious answered 26/9, 2016 at 12:51 Comment(1)
The ReadAllBytes reads whole file to memory. That's not gonna work for large files. And it's inefficient even for medium sized files.Hoang
D
4

Here's my super cool version BECAUSE IT HAS A PROGRESS BAR :-)

Which is a completely useless feature, I know, but it still looks cool \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. Helps a lot, when I'm wondering "did it stop working, or is it just my slow ASDL connection?"

Dumbfound answered 25/5, 2017 at 17:8 Comment(1)
pretty neat. With PowerShell Core 6.1.0 on macOS the progress bar was displayed and the file did upload, but the progress bar never updated. (I tested with a 500MB file to be sure it had plenty of time to update)Khedive
M
3

You can simply handle file uploads through PowerShell, like this. Complete project is available on Github here 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
            }


        }  
    }
}   
Meeks answered 6/3, 2014 at 15:8 Comment(0)
Z
3

You can use this function :

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

This function send specified file by FTP. You must call the function with these parameters :

  • userFTP = "anonymous" by default or your username
  • passFTP = "anonymous" by default or your password
  • serverFTP = IP address of the FTP server
  • localFile = File to send
  • remotePath = the path on the FTP server

For example :

SendByFTP -userFTP "USERNAME" -passFTP "PASSWORD" -serverFTP "MYSERVER" -localFile "toto.zip" -remotePath "path/on/the/FTP/"
Zenia answered 25/4, 2019 at 14:18 Comment(3)
Please elaborate what your code does. Code-only answers are considered as bad quality in Stack Overflow.Mucus
You cannot use Join-Path on URL this way. Join-Path uses backslashes by default, while URL uses forward slashes + You also need to URL-encode userFTP and passFTP.Hoang
I was unable to get this function to work in my case until I realized it was because the username and password I was using contained punctuation characters, and needed to be escaped. So I modified the function by adding these lines to the start: $userFTP = [System.Uri]::EscapeDataString($userFTP) $passFTP = [System.Uri]::EscapeDataString($passFTP)Liam
B
2

Goyuix's solution works great, but as presented it gives me this error: "The requested FTP command is not supported when using HTTP proxy."

Adding this line after $ftp.UsePassive = $true fixed the problem for me:

$ftp.Proxy = $null;
Bontebok answered 17/10, 2012 at 12:0 Comment(0)
S
1

Simple solution if you can install curl.

curl.exe -p --insecure  "ftp://<ftp_server>" --user "user:password" -T "local_file_full_path"
Shipentine answered 5/5, 2022 at 18:22 Comment(0)
W
1

I loved the answer from dexter-legaspi, but I struggled to see exactly how it worked with the clever file-list parser. Please enjoy the reduced version...

"open <my.ftp.site>
user <username> <password>
binary  
put <filename.my>" | ftp -i -in
Wines answered 24/8, 2023 at 9:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.