Using WinSCP for SFTP connections from NAnt for Sitecore and EPiServer projects

Jun 25, 2012

Oshyn uses a combination of Jenkins and NAnt to do builds and deployments of our .NET projects (Sitecore and EPiServer). While this has worked great for us, one of the challenges has been the FTP deployments.  We had been using the FTPTask from Spin The Moose which was previously found here:  http://www.spinthemoose.com/~ftptask/.  

The NAnt tasks looked like this:

<loadtasks assembly="${path::combine(nant-ftptask.dir, 'bin/Release/ftptask.dll')}" />
 <connection id="ftp-conn" server="${server.ftp}" username="${user.ftp}" password="${password.ftp}" />
 <connection id="ftp-conn-cd" server="${cd.server.ftp}" username="${cd.user.ftp}" password="${cd.password.ftp}" />
 <ftp connection="ftp-conn" remotedir="${remotedir.ftp}">
 <put type="ascii" localdir="${website-source.ftp}" remotedir="">
 <include name="Web.config" />
 <include name="Global.asax" />
 <include name="ClientEventTracker.ashx" />
 <include name="ClientEventTracker.js" />
 <include name="LuceneSearch.css" />
 </put>
 <!-- Include in the following PUT all DLLs that are not part of the standard Sitecore installation -->
 <put type="bin" localdir="${website-source.ftp}/bin" remotedir="bin" createdirs="true">
 <include name="*.dll" />
 <include name="*.xml" />
 </put>
 <put type="ascii" localdir="${website-source.ftp}/layouts" remotedir="layouts" createdirs="true">
 <include name="**.ascx" />
 </put>
 <put type="ascii" localdir="${website-source.ftp}/xsl" remotedir="xsl" createdirs="true">
 <include name="**" />
 </put>
 <put type="ascii" localdir="${website-source.ftp}/js" remotedir="js" createdirs="true">
 <include name="**" />
 </put>
 <put type="bin" localdir="${website-source.ftp}/img" remotedir="img" createdirs="true">
 <include name="**" />
 </put>
 <put type="ascii" localdir="${website-source.ftp}/SearchUtils" remotedir="SearchUtils" createdirs="true">
 <include name="*.aspx" />
 </put>
 <put type="ascii" localdir="${website-source.ftp}/WebServices" remotedir="WebServices" createdirs="true">
 <include name="*.aspx" />
 </put>
 <put type="ascii" localdir="${website-source.ftp}/css" remotedir="css" createdirs="true">
 <include name="**" />
 </put>
 <put type="ascii" localdir="${website-source.ftp}/App_Config/Include" remotedir="App_Config/Include" createdirs="true">
 <include name="Sitecore.Analytics.ExcludeRobots.config" />
 <include name="Sitecore.Analytics.config" />
 <include name="ldap.config" />
 </put>
 <put type="ascii" localdir="${website-source.ftp}/App_Config/Security" remotedir="App_Config/Security" createdirs="true">
 <include name="*" />
 </put>
 <put type="ascii" localdir="${website-source.ftp}/App_Config" remotedir="App_Config" createdirs="true">
 <include name="*.config" />
 </put>
 <put type="ascii" localdir="${env-config-source.ftp}" remotedir="App_Config/Include" createdirs="true">
 <include name="Sitecore.Analytics.config" />
 <include name="ScalabilitySettings.config" />
 <include name="Forms.config" />
 </put>
 <put type="ascii" localdir="${env-config-source.ftp}" remotedir="App_Config" createdirs="true" unless="${string::contains(environment.param, 'Prod')}">
 <include name="*.config" />
 </put>
 <put type="ascii" localdir="${env-config-source.ftp}/CM" remotedir="App_Config" createdirs="true" if="${string::contains(environment.param, 'Prod')}">
 <include name="*.config" />
 </put>
 </ftp>
 <!-- The following FTP transfer is used only for Content Delivery server -->
 <ftp connection="ftp-conn-cd" if="${string::contains(environment.param, 'Prod')}">
 <put type="ascii" localdir="${env-config-source.ftp}/CD" remotedir="/Include">
 <include name="SwitchMasterToWeb.config" />
 </put>
 <put type="ascii" localdir="${env-config-source.ftp}/CD" remotedir="/">
 <include name="ConnectionStrings.config" />
 </put>
 </ftp>

However, as you can see if you followed the link, Dave Alpert has apparently stopped supporting the task.

We've been using this task for FTP protocol, but it does not support SFTP.  We quickly found there was no NAnt task alternative that did SFTP (at least not in a few hours of googling) so we decided to shell out using the <exec/> command.  The options for this were psftp.exe and WinSCP.  PSFTP appeared to be the easier and smaller footprint option so we tried that first.  The issue we encountered was how to get the program to not prompt the user to accept the key.  If Jenkins is running as SYSTEM service, it is not possible to get it to accept the key automatically.  After various attempts with wrapper shell files to redirect "Y" to stdin and registry settings, PSFTP was eventually abandoned.

WinSCP, it turns out, has a way to send in the key fingerprint via the command line.  This proved to work successfully and in fact, makes our NAnt a lot cleaner.  There are two steps involved:

  1. Creating the FTP Command file
  2. Executing the FTP Command file

Creating the FTP Command File

The FTP Command file contains as the first line the connection string including the -hostkey parameter as follows:
 <property name="connectionString.ftp" value="open sftp://${user.ftp}:${password.ftp}@${server.ftp} -hostkey=&quot;${rsakey.ftp}&quot;"/>
 <echo file="${ftp.command.file}">
 ${connectionString.ftp}
 option echo off
 option batch on
 option confirm off
 option exclude "*/.svn; *.cs; *.example; *.pdb;"
 </echo>
 <echo file="${ftp.command.file}" append="true">
 cd ${remotedir.ftp}
 ascii
 put "${website-source.ftp}\*.config"
 put "${website-source.ftp}\*.as*x"
 put "${website-source.ftp}\*.css"
 put "${website-source.ftp}\*.js"
 put "${website-source.ftp}\layouts"
 put "${website-source.ftp}\xsl"
 put "${website-source.ftp}\js"
 put "${website-source.ftp}\ErrorPages"
 put "${website-source.ftp}\SearchUtils"
 put "${website-source.ftp}\WebServices"
 put "${website-source.ftp}\css"
 put "${website-source.ftp}\App_Config"
 bin
 put "${website-source.ftp}\bin"
 put "${website-source.ftp}\img"
 </echo>
 <echo file="${ftp.command.file}" append="true">
 ascii
 cd /${remotedir.ftp}/App_Config
 put "${env-config-source.ftp}\*.config"
 </echo>
 <echo file="${ftp.command.file}" append="true">
 exit
 </echo>

Executing the ftp.command.file

The next step is to execute the FTP Command file using the <exec/> task.  Use the WinSCP.com executable instead of WinSCP.exe as that is the one intended for use in batch/non-interactive programs:

 <exec program="WinSCP.com" basedir="${sftp.exec.dir}" >
 <arg value="/script=${ftp.command.file}"/>
 </exec>

The only other challenge is to grab the Fingerprint key to be used as input for the -hostkey parameter. This can be done by connecting to the SFTP server from the WinSCP console interface and copying it.