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.

To get started, 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

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

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!

Join the Jar Tippers on Patreon

It takes a lot of time to write detailed blog posts like this one. In a single-income family, this blog is one way I depend on to keep the lights on. I'd be eternally grateful if you could become a Patreon patron today!

Become a Patron!