SmartFTP is a fantastic FTP application which handles syncing files very effectively. This means that when you upload your entire website, SmartFTP will automatically detect changes and only upload what is required (instead of overwriting all of the files like some FTP applications do). For each project at TheFARM we have build scripts which run and create a time stamped ZIP package for each deployment environment with all of the necessary files formatted appropriately for each. Our deployment process then involves unzipping the contents of this file, opening up SmartFTP, connecting to the deployment destination and transfering all of the deployment files up (which SmartFTP synchronizes for us).
I thought it would be much more efficient if we automated this process. So we did some investigation and it turns out the SmartFTP conveniently has an API! So we decided to see if we could write a PowerShell script to use the SmartFTP api to automagically transfer/sync all of our deployment files in our Zip package to the necessary FTP site and with a bit of trial and error we managed to do it! Now, I’m not PowerShell expert or anything, and in fact this was my very first PowerShell script ever written so I’m sure this could all be done a bit better, but it works! I’m not going to go into detail about the SmartFTP api or how to write PowerShell stuff because this script will work with some basic requirements:
FTPSync.ps1 123.123.123.123 MyUserName MyPassword 21 “C:\MyWebsiteFolder” “/websites/MyWebsite”
or you can just double click on the ps1 file and it will prompt you for these details.
#requires -version 2.0
# Define inputs
param (
[parameter(Mandatory=$true)]
[string]
$dest,
[parameter(Mandatory=$true)]
[string]
$user,
[parameter(Mandatory=$true)]
[string]
$pass,
[parameter(Mandatory=$true)]
[ValidatePattern('\d+')]
[int]
$port = 21,
[parameter(Mandatory=$false)]
[ValidateScript({ Test-Path -Path $_ -PathType Container })]
[string]
$source,
[parameter(Mandatory=$true)]
[ValidatePattern('\/+')]
[string]
$path
)
# get current folder
$currFolder = (Get-Location -PSProvider FileSystem).ProviderPath;
# set current folder
[Environment]::CurrentDirectory=$currFolder;
# if the source isn't set, then use the current folder
if ($source = "") {
$source = $currFolder;
}
Write-Host "------------------------------------------------------" -foregroundcolor yellow -backgroundcolor black
Write-Host("{0, -20}{1,20}" -f "Destination", $dest);
Write-Host("{0, -20}{1,20}" -f "User", $user);
Write-Host("{0, -20}{1,20}" -f "Pass", "********");
Write-Host("{0, -20}{1,20}" -f "Port", $port);
Write-Host "";
Write-Host "Source:";
Write-Host $source;
Write-Host "";
Write-Host "Path";
Write-Host $path;
Write-Host "------------------------------------------------------" -foregroundcolor yellow -backgroundcolor black
# Create application
$smartFTP = New-Object -comObject SmartFTP.Application;
$smartFTP.Visible = [bool]0;
$smartFTP.CloseAll();
# create temp favorite item
$fav = $smartFTP.CreateObject("sfFavorites.FavoriteItem");
$fav.Name = $user + " @ " + $dest + " (temp favorite by cmdInterface)";
# 1 = FTP standard protocol
$fav.Protocol = 1;
$fav.Host = $dest;
$fav.Port = $port;
$fav.Path = $path;
$fav.Username = $user;
$fav.Password = $pass;
# forces it not to be saved
$fav.Virtual = "true";
# Add temporary favorite to SmartFTPs FavoriteManager
$favMgr = $smartFTP.FavoritesManager;
$rootFolder = $favMgr.RootFolder;
$rootFolder.AddItem($fav);
# Get the transfer queue
$queue = $smartFTP.TransferQueue;
# stop the queue if it isn't already
if ($queue.State -ne 1) { $queue.Stop(); } # Stopped = 1
# clear the queue
foreach($item in $queue.Items) { $queue.RemoveItem($item); }
# set the thread count for the queue
$queue.MaxWorkers = 20;
#enable logging
$queue.Log = "true";
$queue.LogFolder = $currFolder + "\\LOG";
# create new transfer item
$newItem = $smartFTP.CreateObject("sfTransferQueue.TransferQueueItem");
# set the item as a folder and copy operation,
$newItem.type = 2; #FOLDER = 2
$newItem.Operation = 1; #COPY = 1
# Set the source
$newItem.Source.type = 1; #LOCAL = 1
$newItem.Source.Path = $source;
# Set the destination
$newItem.Destination.type = 2; #REMOTE = 2
$newItem.Destination.Path = $path;
$newItem.Destination.FavoriteIdAsString = $fav.IdAsString; #links up to our connection favorite
# and finally add it
$queue.AddItemTail($newItem);
Write-Host "STARTING" -foregroundcolor yellow -backgroundcolor black;
$queue.Start();
while ($queue.Items.Count -ne 0) {
Write-Host "Processing...bytes transfered: " $queue.TransferredBytes;
Start-Sleep -s 2; #wait 2 seconds
}
# store the total bytes
$totalBytes = $queue.TransferredBytes;
# cleanup smartftp app
$queue.Quit();
$smartFTP.Exit();
# parse logs
# regex to find "[DATE/TIME] STOR FILENAME
# which indicates a file transfer
$regex = new-object System.Text.RegularExpressions.Regex("\[[\w\-\:]*?\]\sSTOR\s(.+?)\[",,[System.Text.RegularExpressions.RegexOptions]::SingleLine);
$totalFiles = 0;
Write-Host "Files Transfered" -foregroundcolor cyan -backgroundcolor black
Get-ChildItem $queue.LogFolder -include *.log -Recurse | foreach ($_) {
$currFile = Get-Content $_.fullname;
$match = $regex.Matches($currFile);
if ($match.Count -gt 0) {
foreach($m in $match) { Write-Host $m.Groups[1]; }
$totalFiles++;
}
remove-item $_.fullname -Force -Recurse ;
}
Write-Host "COMPLETED (total bytes: " $totalBytes ", total files: )" $totalFiles -foregroundcolor cyan -backgroundcolor black;
"------------------------------------------------------"
# cleanup COM
Remove-Variable smartFTP