A detailed guide on creating a FTP script in PowerShell to move files between servers.
File transfers are not created equal and are of varying importance. One day you might be at home copying over some MP3s to your personal computer and the next you’re at work performing an application upgrade copying over critical files to keep the business running. We’re here today to talk about the second approach.
We’re here to build a tool in PowerShell that would seem overkill if performing a simple copy but when it comes to making copies of critical data, there’s no amount of safeguards that are too many.
Getting Started with the FTP Script
Let’s build a PowerShell function that’ll act as our scaffolding:
function Move-Stuff {
param(
[Parameter(Mandatory)]
[string]$Path,
[Parameter(Mandatory)]
[string]$DestinationPath
)
}
Now that we have a base to work from, let’s get to adding some code. Before we transfer any files over the network, we need to ensure the transport is efficient as possible. Since we’re working with lots of files, it’s generally faster to transfer one file vs. thousands. One way to get thousands of files across a network in one go as efficiently as possible is compressing them. Before we begin the transfer, we’ll compress all of these files into a ZIP file. I’ll use the Compress-Archive cmdlet for that.
$zipFile = Compress-Archive -Path $Path -DestinationPath "$Path\($(New-Guid).Guid).zip" -CompressionLevel Optimal -PassThru
You can see that I’m creating a zip file with a GUID in the same folder. I use a GUID because I know it’ll be unique and this ZIP file is just temporary anyway.
Now that we have our files compressed into a single file, we now need to take a hash of it. We’re taking a hash because we want to ensure the hash the file is before the transfer, and the one afterward are exactly the same. If they are not, something was modified in transit.
$beforeHash = (Get-FileHash -Path $zipFile.FullName).Hash
File Transfer Options with PowerShell
Now we can begin the file transfer. In PowerShell, we’ve got a few ways to transfer files. We can transfer files with the Copy-Item which is, by far, the most common but we’ve also got some cmdlets to transfer files via BITS. BITS is a smarter way to transfer files and uses a protocol that’s less susceptible to network flakiness among other things. First of all, we’re going to scrap the Copy-Item cmdlet and use BITS instead. PowerShell has many built-in cmdlets to interact with BITS. For this script, we’re going to be using Start-BitsTransfer just to get the transfer started.
## Transfer to a temp folder
$destComputer = $DestinationPath.Split('\')[2]
$remoteZipFilePath = "\\$destComputer\c$\Temp"
Start-BitsTransfer -Source $zipFile.FullName -Destination $remoteZipFilePath
The last of the tasks should be done locally on the remote server, so we’ll use PowerShell remoting so we’ll create a PSSession to use.
$destComputer = $DestinationPath.Split('\')[2]
$session = New-PSSession -ComputerName $destComputer
Once all of the files have been transferred, we then check to ensure the hash is the same by using our session.
## Assuming we're using the c$ admin share
$localFolderPath = $DestinationPath.Replace("\\$destComputer\c$\",'C:\')
$localZipFilePath = $remoteZipFilePath.Replace("\\$destComputer\c$\",'C:\')
$afterHash = Invoke-Command -Session $session -ScriptBlock { (Get-FileHash -Path "$using:localFolderPath\$localZipFilePath").Hash }
if ($beforeHash -ne $afterHash) {
throw 'File modified in transit!'
}
If they are the same, we can then unzip the file.
Invoke-Command -Session $session -ScriptBlock { Expand-Archive -Path "$using:localFolderPath\$localZipFilePath" -DestinationPath $using:localFolderPath }
PowerShell File Transfer Function
The final function will look like this:
function Move-Stuff {
param(
[Parameter(Mandatory)]
[string]$Path,
[Parameter(Mandatory)]
[string]$DestinationPath
)
try {
## Zip up the folder
$zipFile = Compress-Archive -Path $Path -DestinationPath "$Path\($(New-Guid).Guid).zip" -CompressionLevel Optimal -PassThru
## Create the before hash
$beforeHash = (Get-FileHash -Path $zipFile.FullName).Hash
## Transfer to a temp folder
$destComputer = $DestinationPath.Split('\')[2]
$remoteZipFilePath = "\\$destComputer\c$\Temp"
Start-BitsTransfer -Source $zipFile.FullName -Destination $remoteZipFilePath
## Create a PowerShell remoting session
$destComputer = $DestinationPath.Split('\')[2]
$session = New-PSSession -ComputerName $destComputer
## Compare the before and after hashes
## Assuming we're using the c$ admin share
$localFolderPath = $DestinationPath.Replace("\\$destComputer\c$\",'C:\')
$localZipFilePath = $remoteZipFilePath.Replace("\\$destComputer\c$\",'C:\')
$afterHash = Invoke-Command -Session $session -ScriptBlock { (Get-FileHash -Path "$using:localFolderPath\$localZipFilePath").Hash }
if ($beforeHash -ne $afterHash) {
throw 'File modified in transit!'
}
## Decompress the zip file
Invoke-Command -Session $session -ScriptBlock { Expand-Archive -Path "$using:localFolderPath\$localZipFilePath" -DestinationPath $using:localFolderPath }
} catch {
$PSCmdlet.ThrowTerminatingError($_)
} finally {
## Cleanup
Invoke-Command -Session $session -ScriptBlock { Remove-Item "$using:localFolderPath\$localZipFilePath" -ErrorAction Ignore }
Remove-Item -Path $zipFile.FullName -ErrorAction Ignore
Remove-PSSession -Session $session -ErrorAction Ignore
}
}
You now have a PowerShell function you can reuse to efficiently transfer a folder from one server to another!
Adam Bertram
Adam Bertram is a 25+ year IT veteran and an experienced online business professional. He’s a successful blogger, consultant, 6x Microsoft MVP, trainer, published author and freelance writer for dozens of publications. For how-to tech tutorials, catch up with Adam at adamtheautomator.com, connect on LinkedIn or follow him on X at @adbertram.