How to avoid the double-hop problem with PowerShell How to test the PowerShell pending reboot module
X
Tip

How to upload and download files with PowerShell FTP script

By using the .NET WebClient class and PowerShell, Windows admins can upload and download files to FTP. Review the general process and corresponding examples to get started.

Editor's Note: Adam Bertram originally wrote this article and Brien Posey has expanded it.

Transferring files via FTP is one of those tasks every sysadmin has done at least once in their career. FTP and its secure components have been around for a long time. FTP copies files from one location to another either on a local network or across the internet.

Seemingly unrelated, PowerShell is a common, universal scripting language that's capable of automating many sysadmin tasks.

But what happens when you bring them together? You get a recipe for a powerful and free automation tool that's based on a ubiquitous scripting platform. Administrators can create powerful FTP scripts using PowerShell and the .NET classes.

Read on to learn how to combine the power of PowerShell with FTP to create PowerShell scripts to transfer files from IP address to IP address, source root folder to destination server and more.

Creating a simple FTP script

Let's start with a simple PowerShell FTP script that transfers a single file from one system to another using a username and password for authentication. We'll initially build the script in parts, but the full script is listed later on. If you want the full script, feel free to skip ahead.

1. Building the script

Open your favorite PowerShell script editor and save a PS1 script named Upload-File.ps1. This script will contain all the code you need to upload a file via FTP.

Create some parameters for the script. Because PowerShell scripts are tools, it's important to be able to reuse this script for other situations. Below, you'll see three parameters that will let you pass the username and password -- via a PSCredential object -- the local file path and the hostname of the FTP server.

[CmdletBinding()]
 param(
     [Parameter(Mandatory)]
     [pscredential]$Credential,
     [Parameter(Mandatory)]
     [string]$FilePath,
     [Parameter(Mandatory)]
     [string]$FtpServerHost
 )

Create a .NET WebClient object. This object has methods that invoke FTP transfers, but it also supports HTTP. Once defined, you must provide the username and password to the Credentials property, as shown below.

$webclient = New-Object System.Net.WebClient
 $ftpPassword = $Credential.GetNetworkCredential().Password
 $webclient.Credentials = New-Object System.Net.NetworkCredential($Credential.Username, $ftpPassword)

Once the WebClient object has been created and credentials defined, define where the file should go. To do this, you must create a System.Uri .NET object, as shown below.

$remoteFileName = $FilePath | Split-Path -Leaf
 $uri = New-Object System.Uri("ftp://$FtpServerHost/$remoteFileName")

Finally, invoke the UploadFile() method and use the recently created URI and the path received via the script. The code below will upload the local file to the root directory of the FTP server.

$webclient.UploadFile($uri, $FilePath)

The full script should look like this.

[CmdletBinding()]
 param(
     [Parameter(Mandatory)]
     [pscredential]$Credential,
     [Parameter(Mandatory)]
     [string]$FilePath,
     [Parameter(Mandatory)]
     [string]$FtpServerHost
 )
 $webclient = New-Object System.Net.WebClient
 $ftpPassword = $Credential.GetNetworkCredential().Password
 $webclient.Credentials = New-Object System.Net.NetworkCredential($Credential.Username, $ftpPassword)
 $remoteFileName = $FilePath | Split-Path -Leaf
 $uri = New-Object System.Uri("ftp://$FtpServerHost/$remoteFileName")
 $webclient.UploadFile($uri, $FilePath)
 $webclient.Dispose()

2. Running the script

To run the script, invoke it as shown in the command below. The command will prompt you for a username and password. It will then use that username and password to authenticate to the ftpserver.domainname.com FTP server and transfer the C:.txt file to the FTP server's root directory.

.\Upload-File.ps1 -Credential (Get-Credential) -FilePath 'C:\Demo\file.txt' -FtpServerHost 'ftpserver.domainname.com'

Building an advanced FTP script

Now let's make that simple script more advanced. Say you want to copy a file using FTPS to encrypt the transfer across the network. To do that, you must use a .NET WebRequest object.

Using a similar technique, you can replace the Upload-Script.ps1 script with the code below. The script serves the same purpose and transfers files unencrypted but now provides the option to securely transfer files as well.

[CmdletBinding()]
 param(
     [Parameter(Mandatory)]
     [pscredential]$Credential,
     [Parameter(Mandatory)]
     [string]$FilePath,
     [Parameter(Mandatory)]
     [string]$FtpServerHost,
     [Parameter()]
     [switch]$SecureTransfer
 )
 ## Define the FTP URI and credentials to use for connection
 $ftpPassword = $Credential.GetNetworkCredential().Password
 $remoteFileName = $FilePath | Split-Path -Leaf
 $request = [Net.WebRequest]::Create("ftp://$FtpServerHost/$remoteFileName")
 $request.Credentials = New-Object System.Net.NetworkCredential($Credential.Username, $ftpPassword)
 ## Only if the $SecureTransfer switch parameter is used, transfer with FTPS
 if ($SecureTransfer.IsPresent) {
     $request.EnableSsl = $true
 }
 ## Tell the request object we'll be uploading a file
 $request.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile
 ## Open the local file and conver to a request stream for transferring
 $fileStream = [System.IO.File]::OpenRead($FilePath)
 $ftpStream = $request.GetRequestStream()
 ## Transfer the file
 $fileStream.CopyTo($ftpStream)
 ## Clean up the memory used for .NET objects
 $ftpStream.Dispose()
 $fileStream.Dispose()

Uploading or downloading an entire folder

Building on the advanced script above, perhaps you need to download a file. Likewise, what if you need to transfer all files in a folder rather than a single file? Below you'll find a script to do just that.

You'll find comments that give more information on what each line is doing.

[CmdletBinding()]
 param(
     [Parameter(Mandatory)]
     [pscredential]$Credential,
     [Parameter(Mandatory)]
     [string]$LocalPath,
     [Parameter(Mandatory)]
     [string]$FtpServerHost,
     [Parameter(Mandatory)]
     [ValidateSet('Download','Upload')]
     [string]$Direction
 )
 $webclient = New-Object System.Net.WebClient
 $ftpPassword = $Credential.GetNetworkCredential().Password
 $webclient.Credentials = New-Object System.Net.NetworkCredential($Credential.Username, $ftpPassword)
 if ($Direction -eq 'Download') {
     ## Find all files on the FTP server
     $request = [Net.WebRequest]::Create($url)
   $request.Method = [System.Net.WebRequestMethods+FTP]::ListDirectory
     $request.Credentials = $credentials
   $response = $request.GetResponse()
   $reader = New-Object IO.StreamReader $response.GetResponseStream()
     $files = while(-not $reader.EndOfStream) {
         $reader.ReadLine()
   }
   $reader.Close()
   $response.Close()
     ## Download each file
     foreach ($file in $files){
         $source = "$uri/$file"
     $destination = "$LocalPath/$file"
     $webclient.DownloadFile($source, $destination)
   }
 } elseif ($Direction -eq 'Upload') {
     ## Find all local files and upload them
     Get-ChildItem -Path $LocalPath | ForEach-Object {
         $uri = New-Object System.Uri("ftp://$FtpServerHost/$($_.Name)")
         $webclient.UploadFile($uri, $_.FullName)
     }
 }

Monitoring progress

Finally, if you're transferring a large file and need to know how much data has been transferred, there is an easy way to find out. One way to monitor progress in PowerShell is to use the Write-Progress cmdlet. This cmdlet provides a graphical progress bar that gives you an indication of how far along it is.

There are various ways to use a progress bar to track a file transfer's progress. One of the easiest options is to upload the file in chunks rather than all at once and use the Write-Progress cmdlet generate an ongoing progress bar, based on how many chunks have been transferred. The code below shows how to track the transfer progress in this way.

[CmdletBinding()]
 param(
     [Parameter(Mandatory)]
     [pscredential]$Credential,
     [Parameter(Mandatory)]
     [string]$LocalPath,
     [Parameter(Mandatory)]
     [string]$FtpServerHost
 )
 $remoteFileName = $FilePath | Split-Path -Leaf
 $request = [Net.WebRequest]::Create("ftp://$FtpServerHost/$remoteFileName")
 $ftpPassword = $Credential.GetNetworkCredential().Password
 $webclient.Credentials = New-Object System.Net.NetworkCredential($Credential.Username, $ftpPassword)
 $request.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile
 $fileStream = [System.IO.File]::OpenRead($LocalFilePath)
 $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)
         $prgParams = @{
             'Activity' =  'Uploading'
             'Status' =  ("{0:P0} complete:" -f $pct)
       'PercentComplete' = ($pct * 100)
         }
     Write-Progress @prgParams
 }
 $fileStream.CopyTo($ftpStream)
 $ftpStream.Dispose()
 $fileStream.Dispose()

If you're a sysadmin, you might not understand all the advanced .NET code in this article. But one of the best things about scripting out tasks like this is that you don't have to. Take advantage of the existing code provided here and begin running the scripts themselves rather than digging into the code.

Next Steps

7 PowerShell courses to help hone skills for all levels of expertise

PowerShell Move-Item examples for file, folder management

Best practices for using PowerShell ISE for scripting

Dig Deeper on Microsoft identity and access management