84

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

codewario
  • 19,553
  • 20
  • 90
  • 159
magol
  • 6,135
  • 17
  • 65
  • 120
  • The JAMS Job Scheduler offers [cmdlets](http://www.jamsscheduler.com/PowerShell.aspx) that make secure file transfers easy. The cmdlets make it simple to automate transfers and connect using a variety of protocols. (FTP, SFTP, etc...) – user695859 Apr 07 '11 at 00:55

11 Answers11

90

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()
rmc47
  • 1,364
  • 1
  • 13
  • 17
Goyuix
  • 23,614
  • 14
  • 84
  • 128
  • 2
    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. – magol Dec 09 '09 at 08:34
  • 18
    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. – Goyuix Dec 09 '09 at 15:44
  • 3
    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. – wallybh Feb 14 '12 at 17:12
  • 1
    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](http://msdn.microsoft.com/en-us/library/system.net.ftpwebrequest.aspx). – jl. Feb 18 '12 at 23:01
  • 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! – Dewi Rees Oct 16 '15 at 10:32
  • Is there no way to use ftp in powershell without a script?! – osprey Aug 25 '20 at 15:03
53

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

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

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?

Community
  • 1
  • 1
Cyril Gupta
  • 13,505
  • 11
  • 64
  • 87
  • There doesn't seem to be a way to turn off PASSIVE mode using the first option presented here. – dan Dec 10 '12 at 01:33
  • 3
    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)` – germankiwi May 06 '16 at 00:57
  • 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](http://stackoverflow.com/a/34448042/537243) and thus e.g. in [Powershell ftps upload to box.com using passive mode](http://stackoverflow.com/a/42985281/537243) – Justin Mar 23 '17 at 21:20
  • This solution even works with PowerShell Core 6.1 on macOS – HairOfTheDog Oct 25 '18 at 22:35
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!

Dexter Legaspi
  • 3,192
  • 1
  • 35
  • 26
  • 2
    And if you ever use a different flavor of FTP, you're just piping to a different program. Nice. – quillbreaker Feb 09 '12 at 22:31
  • 3
    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! – basos Feb 17 '17 at 13:47
  • 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. – LeeM Dec 10 '20 at 09:32
22

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

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
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
    }
}
LukeSavefrogs
  • 528
  • 6
  • 15
Patrick
  • 621
  • 2
  • 7
  • 21
  • The `ReadAllBytes` reads whole file to memory. That's not gonna work for large files. And it's inefficient even for medium sized files. – Martin Prikryl Sep 04 '20 at 07:15
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?"

Alex from Jitbit
  • 53,710
  • 19
  • 160
  • 149
  • 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) – HairOfTheDog Oct 26 '18 at 23:10
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
            }


        }  
    }
}   
Edouard Kombo
  • 505
  • 7
  • 7
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/"
ryryan
  • 3,890
  • 13
  • 43
  • 72
hasma
  • 39
  • 1
  • Please elaborate what your code does. Code-only answers are considered as bad quality in Stack Overflow. – quinz Apr 25 '19 at 14:36
  • 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`. – Martin Prikryl Apr 26 '19 at 06:33
  • 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)` – Karl von L Jun 29 '22 at 16:43
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;
Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
Mike Brady
  • 313
  • 2
  • 7
1

Simple solution if you can install curl.

curl.exe -p --insecure  "ftp://<ftp_server>" --user "user:password" -T "local_file_full_path"
0

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
Dave
  • 3,093
  • 35
  • 32