FTP Clients - Part 13: WinSCP

For this next installment in my series about FTP clients, I want to take a look at WinSCP, which is an open source FTP/SFTP client that is available from the following URL:

http://www.winscp.net/

For this blog post I used WinSCP 5.5.1, and it was available for free when I wrote this blog post. That being said, WinSCP's author (Martin Prikryl) takes donations. (And I think that it's a worthy cause; I like to support independent development work.)

WinSCP 5.5 Overview

When you open WinSCP 5.5, you will first see the Login dialog box, which will be empty until you add some sites to the list. The Login dialog allows you to create folders so you can categorize your sites, and the user interface is comparable to what you would expect in a Site Manager for other FTP clients.

Fig. 1 - The opening Login dialog in WinSCP 5.5.

When you are adding FTP sites, you have three choices for the protocol: FTP, SCP, and SFTP; you also have four choices for encryption: No encryption, TLS/SSL Implicit encryption, TLS Explicit encryption, and SSL Explicit encryption. (I'll discuss those later.)

When you open a site for which you did not save the password, (which I highly recommend), you will be prompted for your password.

Fig. 2 - The WinSCP 5.5 Password dialog.

Once your FTP site is opened, the main application window is displayed, and it resembles a two-column file explorer interface with local and remote folders, which you might expect from a GUI-based FTP client. (Note: WinSCP refers to this as it's "Commander" interface.)

Fig. 3 - Local and Remote Folders.

That being said, if you change your application preferences, you can change the user interface so that it uses a single-column file explorer interface with a folder tree, which might be useful if you would rather use the FTP client as a drag-and-drop repository. (Note: WinSCP refers to this as it's "Explorer" interface.)

Fig. 4 - Remote Folder Tree and Files.

WinSCP 5.5 has support for automation through .NET and COM, and documentation about automating WinSCP 5.5 programmatically is available on the WinSCP website at the following URL:

WinSCP .NET Assembly and COM Library

There are several detailed automation examples on the WinSCP website that are written in C#, VB.NET, PowerShell, JavaScript, VBScript, etc., and the documentation is quite good. If you need to do a lot of FTP scripting and you are looking for a good way to automate your FTP sessions, you might want to consider this FTP client.

If you don't want to write a bunch of code, you can also automate WinSCP from a command line, and the documentation about that is available on the WinSCP website at the following URL:

WinSCP Command-line Options

Another great feature about WinSCP is that it can be downloaded as portable executables, which makes it easy to copy between systems. This is a great feature for me since I like to keep a collection of handy utilities in my SkyDrive/OneDrive folders.

Using WinSCP 5.5 with FTP over SSL (FTPS)

WinSCP 5.5 has built-in support for FTP over SSL (FTPS), and it supports both Explicit and Implicit FTPS. To specify which type of encryption to use for FTPS, you need to choose the appropriate option from the Encryption drop-down menu for an FTP site.

Fig. 5 - Specifying the FTPS encryption.

Once you have established an FTPS connection through WinSCP 5.5, the user experience is the same as it is for a standard FTP connection. That being said, I could not find a way to drop out of FTPS once a connection is established, so FTPS is an all or nothing option for your sessions.

Using Using WinSCP 5.5 with True FTP Hosts

True FTP hosts are not supported natively, and even though WinSCP 5.5 allows you to send post-login commands after an FTP site has been opened, I could not find a way to send a custom command before sending user credentials, so true FTP hosts cannot be used.

Using Using WinSCP 5.5 with Virtual FTP Hosts

WinSCP 5.5's login settings allow you to specify the virtual host name as part of the user credentials by using syntax like "ftp.example.com|username" or "ftp.example.com\username", so you can use virtual FTP hosts with WinSCP 5.5.

Fig. 6 - Specifying an FTP virtual host.

Scorecard for WinSCP 5.5

This concludes my quick look at a few of the FTP features that are available with WinSCP 5.5, and here are the scorecard results:

Client
Name
Directory
Browsing
Explicit
FTPS
Implicit
FTPS
Virtual
Hosts
True
HOSTs
Site
Manager
Extensibility
WinSCP 5.5.1 Rich Y Y Y N Y N/A
Note: I could not find anyway to extend the functionality of WinSCP 5.5; but as I said
earlier, it provides rich automation features for .NET, COM, and the command-line.

That wraps things up for today's blog. Your key take-aways should be: WinSCP 5.5 is good FTP client with a lot of options, and it has a very powerful automation story. As I mentioned earlier, if you have to do a lot of FTP automation, you should really take a look at this FTP client.


Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/

Adding Additional Content Types to my Classic ASP and URL Rewrite Samples for Dynamic SEO Functionality

In December of 2012 I wrote a blog titled "Using Classic ASP and URL Rewrite for Dynamic SEO Functionality", in which I described how to use Classic ASP and URL Rewrite to dynamically-generate Robots.txt and Sitemap.xml or Sitemap.txt files. I received a bunch of requests for additional information, so I wrote a follow-up blog this past November titled "Revisiting My Classic ASP and URL Rewrite for Dynamic SEO Functionality Examples" which illustrated how to limit the output for the Robots.asp and Sitemap.asp files to specific sections of your website.

That being said, I continue to receive requests for additional ways to stretch those samples, so I thought that I would write at least a couple of blogs on the subject. With that in mind, for today I wanted to show how you can add additional content types to the samples.

Overview

Here is a common question that I have been asked by several people:

"The example only works with *.html files; how do I include my other files?"

That's a great question, and additional content types are really easy to implement, and the majority of the code from my original blog will remain unchanged. Here's the file by file breakdown for the changes that need made:

FilenameChanges
Robots.asp None
Sitemap.asp See the sample later in this blog
Web.config None

If you are already using the files from my original blog, no changes need to be made to your Robots.asp file or the URL Rewrite rules in your Web.config file because the updates in this blog will only impact the output from the Sitemap.asp file.

Updating the Sitemap.asp File

My original sample contained a line of code which read "If StrComp(strExt,"html",vbTextCompare)=0 Then" and this line was used to restrict the sitemap output to static *.html files. For this new sample I need to make two changes:

  1. I am adding a string constant which contains a list of file extensions from several content types to use for the output.
  2. I replace the line of code which performs the comparison.

Note: I define the constant near the beginning of the file so it's easier for other people to find; I would normally define that constant elsewhere in the code.

<%
    Option Explicit
    On Error Resume Next
    
    Const strContentTypes = "htm|html|asp|aspx|txt"
    
    Response.Clear
    Response.Buffer = True
    Response.AddHeader "Connection", "Keep-Alive"
    Response.CacheControl = "public"
    
    Dim strFolderArray, lngFolderArray
    Dim strUrlRoot, strPhysicalRoot, strFormat
    Dim strUrlRelative, strExt

    Dim objFSO, objFolder, objFile

    strPhysicalRoot = Server.MapPath("/")
    Set objFSO = Server.CreateObject("Scripting.Filesystemobject")
    
    strUrlRoot = "http://" & Request.ServerVariables("HTTP_HOST")
    
    ' Check for XML or TXT format.
    If UCase(Trim(Request("format")))="XML" Then
        strFormat = "XML"
        Response.ContentType = "text/xml"
    Else
        strFormat = "TXT"
        Response.ContentType = "text/plain"
    End If

    ' Add the UTF-8 Byte Order Mark.
    Response.Write Chr(CByte("&hEF"))
    Response.Write Chr(CByte("&hBB"))
    Response.Write Chr(CByte("&hBF"))
    
    If strFormat = "XML" Then
        Response.Write "<?xml version=""1.0"" encoding=""UTF-8""?>" & vbCrLf
        Response.Write "<urlset xmlns=""http://www.sitemaps.org/schemas/sitemap/0.9"">" & vbCrLf
    End if
    
    ' Always output the root of the website.
    Call WriteUrl(strUrlRoot,Now,"weekly",strFormat)

    ' --------------------------------------------------
    ' This following section contains the logic to parse
    ' the directory tree and return URLs based on the
    ' files that it locates.
    ' -------------------------------------------------- 
    strFolderArray = GetFolderTree(strPhysicalRoot)

    For lngFolderArray = 1 to UBound(strFolderArray)
        strUrlRelative = Replace(Mid(strFolderArray(lngFolderArray),Len(strPhysicalRoot)+1),"\","/")
        Set objFolder = objFSO.GetFolder(Server.MapPath("." & strUrlRelative))
        For Each objFile in objFolder.Files
            strExt = objFSO.GetExtensionName(objFile.Name)
            If InStr(1,strContentTypes,strExt,vbTextCompare) Then
                If StrComp(Left(objFile.Name,6),"google",vbTextCompare)<>0 Then
                    Call WriteUrl(strUrlRoot & strUrlRelative & "/" & objFile.Name, objFile.DateLastModified, "weekly", strFormat)
                End If
            End If
        Next
    Next

    ' --------------------------------------------------
    ' End of file system loop.
    ' -------------------------------------------------- 
    If strFormat = "XML" Then
        Response.Write "</urlset>"
    End If
    
    Response.End

    ' ======================================================================
    '
    ' Outputs a sitemap URL to the client in XML or TXT format.
    ' 
    ' tmpStrFreq = always|hourly|daily|weekly|monthly|yearly|never 
    ' tmpStrFormat = TXT|XML
    '
    ' ======================================================================

    Sub WriteUrl(tmpStrUrl,tmpLastModified,tmpStrFreq,tmpStrFormat)
        On Error Resume Next
        Dim tmpDate : tmpDate = CDate(tmpLastModified)
        ' Check if the request is for XML or TXT and return the appropriate syntax.
        If tmpStrFormat = "XML" Then
            Response.Write " <url>" & vbCrLf
            Response.Write " <loc>" & Server.HtmlEncode(tmpStrUrl) & "</loc>" & vbCrLf
            Response.Write " <lastmod>" & Year(tmpLastModified) & "-" & Right("0" & Month(tmpLastModified),2) & "-" & Right("0" & Day(tmpLastModified),2) & "</lastmod>" & vbCrLf
            Response.Write " <changefreq>" & tmpStrFreq & "</changefreq>" & vbCrLf
            Response.Write " </url>" & vbCrLf
        Else
            Response.Write tmpStrUrl & vbCrLf
        End If
    End Sub

    ' ======================================================================
    '
    ' Returns a string array of folders under a root path
    '
    ' ======================================================================

    Function GetFolderTree(strBaseFolder)
        Dim tmpFolderCount,tmpBaseCount
        Dim tmpFolders()
        Dim tmpFSO,tmpFolder,tmpSubFolder
        ' Define the initial values for the folder counters.
        tmpFolderCount = 1
        tmpBaseCount = 0
        ' Dimension an array to hold the folder names.
        ReDim tmpFolders(1)
        ' Store the root folder in the array.
        tmpFolders(tmpFolderCount) = strBaseFolder
        ' Create file system object.
        Set tmpFSO = Server.CreateObject("Scripting.Filesystemobject")
        ' Loop while we still have folders to process.
        While tmpFolderCount <> tmpBaseCount
            ' Set up a folder object to a base folder.
            Set tmpFolder = tmpFSO.GetFolder(tmpFolders(tmpBaseCount+1))
              ' Loop through the collection of subfolders for the base folder.
            For Each tmpSubFolder In tmpFolder.SubFolders
                ' Increment the folder count.
                tmpFolderCount = tmpFolderCount + 1
                ' Increase the array size
                ReDim Preserve tmpFolders(tmpFolderCount)
                ' Store the folder name in the array.
                tmpFolders(tmpFolderCount) = tmpSubFolder.Path
            Next
            ' Increment the base folder counter.
            tmpBaseCount = tmpBaseCount + 1
        Wend
        GetFolderTree = tmpFolders
    End Function
%>

That's it. Pretty easy, eh?

I have also received several requests about creating a sitemap which contains URLs with query strings, but I'll cover that scenario in a later blog.


Note: This blog was originally posted at http://blogs.msdn.com/robert_mcmurray/

The Book of Squirrels

(Note: I found this on my computer, which I had posted to our refrigerator several years ago when my wife and I were going out of town for a few days and I wanted my son, Peter, to remember to put out food for the squirrels while my wife and I were away.)

BookOfSquirrels

Bob 21:15 - The lord of the house said to Peter Joshua, "Peter, do you love me more than these?" "Yes, Dad," he said, "you know that I love you." Bob said, "Feed my squirrels."

Bob 21:16 - Again the lord of the house said, "Peter, son of Robert, do you love me?" He answered, "Yes, Dad, you know that I love you." Bob said, "Take care of my squirrels."

Bob 21:17 - The third time he said to him, "Peter, son of Robert, do you love me?" Peter was hurt because his dad asked him the third time, "Do you love me?" He said, "Dad, you know many things; you know that I love you." Bob said, "Feed my squirrels."

(Note: If you don't get the reference, I'm not explaining it to you.)

Dear Unknown Pickup Truck Driver

Dear Unknown Pickup Truck Driver,

I'd like to personally thank you for failing to see the bicyclist (me) in the clearly-marked bike lane and nearly hitting me. I flipped my bicycle to avoid running into you, so your brain-dead vehicular maneuver restored my belief that some people are too dumb to operate a car. Next time, try hanging up your @#$% cell phone and watch the road.

As luck would have it, I was riding to the hospital where my wife works as a nurse when this mishap occurred, and she used Tegaderm to bandage everything. (Although that was after the painful experience of scrubbing all of the dirt out of the wound.)

On the positive side, this was a great test of the brakes on my new bicycle. (FYI - The brakes work really well; maybe a little too well. But as they say, any landing you can walk away from...)

I should also mention that I'm very fortunate that minor bumps and gashes were the worst of my injuries; I was foolishly wearing neither my helmet nor my gloves when this happened. Both hands were a little bruised and my right arm had several nasty-looking scrapes, but nothing was broken. (And I have learned my lesson; I will always wear my helmet in the future.)

Hashtags: #itsokaythegroundbrokemyfall, #lookicanfly, #itsonlyafleshwound

Using ASX Files with Windows Media Center

Like a lot of Windows geeks and fanboys, I use Windows Media Center on a Windows 7 system as my Digital Video Recorder (DVR) and media library. My system consists of a Dell GX270 computer with a ZOTAC NVIDIA GeForce GT610 video card, and it uses an InfiniTV 6 ETH tuner to receive cable signals. This setup has served us faithfully for years, and it is the center piece of our home entertainment system. If you're not familiar with Windows Media Center, that's because it's a rather hideously under-advertised feature of Windows. Just the same, here is an official Microsoft teaser for it:

But I've done a few extra things with my Windows Media Center that are a little beyond the norm, and one of the biggest items that I spent a considerable amount of time and effort digitizing my entire collection of DVD and Blu-ray discs as MP4 files, and I store them on a Thecus NAS that's on my home network which I use for media libraries on my Windows Media Center. This allows me to have all of my movies available at all times, and I can categorize them into folders which show up under the "Videos" link on the Windows Media Center menu.

That being said, there's a cool trick that I've been using to help customize some of my movies. Some of the movies that I have encoded have some material that I'd like to cut out, (like excessive opening credits and lengthy intermissions), but I don't want to edit and re-encode each MP4 file. Fortunately, Windows Media Center supports Advanced Stream Redirector (ASX) files, which allows me to customize what parts of a video are seen without having to edit the actual video.

Here's a perfect example: I recently purchased the 50th Anniversary Collector's Edition of Lawrence of Arabia on Blu-ray. The film is one of my favorites, and this reissue on Blu-ray is phenomenal. That being said, the movie begins with a little over four minutes of a blank screen while the musical overture plays. In addition, there is an additional eight minutes of a blank screen while the music for intermission is played. This is obviously less than desirable, so I created an ASX file which skips the opening overture and intermission.

By way of explanation, ASX files are XML files which define a playlist for media types, which can be any supported audio or video media. The individual entries can define various metadata about each media file, and thankfully can be used to specify which parts of a media file will be played.

With that in mind, here's what the ASX file that I created for Lawrence of Arabia looks like:

<ASX VERSION="3.0">
  <!-- Define the title for the movie. -->
  <TITLE>Lawrence Of Arabia</TITLE>
  <!-- Specify the movie's author. -->
  <AUTHOR>Columbia Pictures</AUTHOR>
  <!-- List the copyright for the movie. -->
  <COPYRIGHT>1962 Horizon Pictures (GB)</COPYRIGHT>
  <ENTRY>
    <!-- Define the video file for this entry. -->
    <REF HREF="Lawrence Of Arabia.mp4" />
    <!-- Define the start time for this entry. -->
    <STARTTIME VALUE="00:04:17.0"/>
    <!-- Define the duration for this entry. -->
    <DURATION VALUE="02:15:07.0"/>
  </ENTRY>
  <ENTRY>
    <!-- Define the video file for this entry. -->
    <REF HREF="Lawrence Of Arabia.mp4" />
    <!-- Define the start time for this entry. -->
    <STARTTIME VALUE="02:23:38.0"/>
  </ENTRY>
</ASX>

The XML comments explain what each of the lines in the file is configuring, and it should be straight-forward. But I would like to describe a few additional details:

  • Individual media entries are obviously defined in a collection of <ENTRY> elements, and in this example I have defined two entries:
    • The first entry defines a <STARTTIME> and <DURATION> which skip over the overture and play up to the intermission.
    • The second entry defines a <STARTTIME> which starts after the intermission and plays through the end of the movie.
  • The other metadata in the file - like the <AUTHOR> and <COPYRIGHT> - is just for me. That information is optional, but I like to include it.

There are several other pieces of metadata which can be configured, and a list of those are defined in the Windows Media Metafile Elements Reference and ASX Elements Reference.