27

I'd like to automate the FTP download of a database backup file using PowerShell. The file name includes the date so I can't just run the same FTP script every day. Is there a clean way to do this built into PowerShell or using the .NET framework?

I want to use a secure FTP session.

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
Eric Ness
  • 10,119
  • 15
  • 48
  • 51
  • I know this is old, but I have not seen anyone mention the `Posh-SSH` module. I have used it successfully. https://github.com/darkoperator/Posh-SSH – lit Jun 08 '18 at 00:33

9 Answers9

24

After some experimentation I came up with this way to automate a secure FTP download in PowerShell. This script runs off the public test FTP server administered by Chilkat Software. So you can copy and paste this code and it will run without modification.

$sourceuri = "ftp://ftp.secureftp-test.com/hamlet.zip"
$targetpath = "C:\hamlet.zip"
$username = "test"
$password = "test"

# Create a FTPWebRequest object to handle the connection to the ftp server
$ftprequest = [System.Net.FtpWebRequest]::create($sourceuri)

# set the request's network credentials for an authenticated connection
$ftprequest.Credentials =
    New-Object System.Net.NetworkCredential($username,$password)

$ftprequest.Method = [System.Net.WebRequestMethods+Ftp]::DownloadFile
$ftprequest.UseBinary = $true
$ftprequest.KeepAlive = $false

# send the ftp request to the server
$ftpresponse = $ftprequest.GetResponse()

# get a download stream from the server response
$responsestream = $ftpresponse.GetResponseStream()

# create the target file on the local system and the download buffer
$targetfile = New-Object IO.FileStream ($targetpath,[IO.FileMode]::Create)
[byte[]]$readbuffer = New-Object byte[] 1024

# loop through the download stream and send the data to the target file
do{
    $readlength = $responsestream.Read($readbuffer,0,1024)
    $targetfile.Write($readbuffer,0,$readlength)
}
while ($readlength -ne 0)

$targetfile.close()

I found a lot of helpful information at these links

If you want to use an SSL connection you need to add the line

$ftprequest.EnableSsl = $true

to the script before you call GetResponse(). Sometimes you may need to deal with a server security certificate that is expired (like I unfortunately do). There is a page at the PowerShell Code Repository that has a code snippet to do that. The first 28 lines are the most relevant for the purposes of downloading a file.

Eric Ness
  • 10,119
  • 15
  • 48
  • 51
  • 1
    +1 for finding and posting a code solution on this. However, it would be great to see some comments in the code. By secure I assume you mean a user/pass is provided and not necessarily SFTP? – Scott Saad Nov 06 '08 at 15:48
  • @Scott - Thanks for your feedback. I added some comments to the code which will clarify things. I also added some information about connecting over SSL. I hadn't thought of this earlier. – Eric Ness Nov 06 '08 at 19:30
  • Useful code, but it doesn't solve your problem of dealing with the unique filename – David Gardiner Nov 03 '11 at 11:52
7

Taken from here:

$source = "ftp://ftp.microsoft.com/ResKit/win2000/dureg.zip"
$target = "c:\temp\dureg.zip"
$WebClient = New-Object System.Net.WebClient
$WebClient.DownloadFile($source, $target)

It works for me.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
PabloG
  • 25,761
  • 10
  • 46
  • 59
  • That is a valid 4th option that I should have thought of. If you will know the name of the file ahead of time then this is certainly a good solution and you should accept it as the answer. – EBGreen Nov 05 '08 at 15:31
3

As far as PowerShell goes, the /n Software NetCmdlets package includes FTP cmdlets (including support for both secure FTP types) that you could use pretty easily for this.

tomasr
  • 13,683
  • 3
  • 38
  • 30
1

The JAMS Job Scheduler offers some cmdlets that would make this task simple. It has a variety of FTP cmdlets for secure sessions as well as date cmdlets for converting natural dates into .NET date objects such as "Last day of month":

JAMS Job Scheduler Cmdlets

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
user695859
  • 19
  • 1
1

The most voted self-answer by @Eric is working, but it is:

  • inefficient with only 1KB buffer;
  • unnecessarily complicated with reimplementing, what can easily and more efficiently be done with Stream.CopyTo:
$fileUrl = "ftp://ftp.example.com/remote/path/file.zip"
$localFilePath = "C:\local\path\file.zip"

$downloadRequest = [Net.WebRequest]::Create($fileUrl)
$downloadRequest.Method = [System.Net.WebRequestMethods+Ftp]::DownloadFile
$downloadRequest.Credentials =
    New-Object System.Net.NetworkCredential("username", "password")
# Enable secure FTPS (FTP over TLS/SSL)
$downloadRequest.EnableSsl = $True

$sourceStream = $downloadRequest.GetResponse().GetResponseStream()
$targetStream = [System.IO.File]::Create($localFilePath)
$sourceStream.CopyTo($targetStream);

$sourceStream.Dispose()
$targetStream.Dispose()

For an upload, see Upload files with FTP using PowerShell.

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
0

Something like this could work:

$bkdir = "E:\BackupsPWS" #backups location directory
$7Zip = 'C:\"Program Files"\7-Zip\7z.exe' #compression utility
$files_to_transfer = New-Object System.Collections.ArrayList #list of zipped files to be   transferred over FTP
$ftp_uri="myftpserver"
$user="myftpusername"
$pass="myftppassword"

# Find .bak files not zipped yet, zip them, add them to the list to be transferrd
get-childitem -path $bkdir | Sort-Object length |
where { $_.extension -match ".(bak)" -and
        -not (test-path ($_.fullname -replace "(bak)", "7z")) } |
foreach {
    $zipfilename = ($_.fullname -replace "bak", "7z")
    Invoke-Expression "$7Zip a $zipfilename $($_.FullName)"
    $files_to_transfer.Add($zipfilename)
}

# Find .bak files, if they've been zipped, delete the .bak file
get-childitem -path $bkdir |
where { $_.extension -match ".(bak)" -and
        (test-path ($_.fullname -replace "(bak)", "7z")) } |
foreach { del $_.fullname }

# Transfer each zipped file over FTP
foreach ($file in $files_to_transfer)
{
    $webclient = New-Object System.Net.WebClient
    $webclient.Credentials = New-Object System.Net.NetworkCredential($user,$pass) # FTP credentials
    $ftp_urix = $ftp_uri + "/" + $file.Substring($bkdir.Length + 1) # ftp address where to transfer   the file
    $uri=[system.URI] $ftp_urix
    $webclient.UploadFile($uri, $file) #transfer the file
}

Check this: Powershell: compress backups and FTP transfer

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Nathan
  • 2,705
  • 23
  • 28
0

This isn't as simple as I wish it would be. There are three options that I know of.

  1. .NET - You can use the .NET framework to do this in PowerShell, but it involves raw socket manipulation that I wouldn't want to do in a script. If I were going this route, then I would wrap up all the FTP junk into a DLL in C# and then use that DLL from PowerShell.

  2. Manipulating a file - If you know the pattern of the name of the file that you need to get each day then you could just open your FTP script with PowerShell and change the name of the file in the script. Then run the script.

  3. Pipe text to FTP - The last option is to use PowerShell to pipe information into and out of your FTP session. See here.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
EBGreen
  • 36,735
  • 12
  • 65
  • 85
-1

I have successfully used the Indy Project .NET library to do FTP. And...ugh, looks like the hosted .NET build is no longer available.

Peter Seale
  • 4,835
  • 4
  • 37
  • 45
-1
$realdate = (Get-Date).ToString("yyyyMMdd")

$path = "D:\samplefolderstructure\filename" + $realdate + ".msi"

ftps -f $path -s:D:\FTP.txt

ftp.txt is the way we keep all of our connection information to the FTP server. ftps is the client we use obviously, so you might have to change a few things. But, this should help you get the idea.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Alex
  • 12,749
  • 3
  • 31
  • 45