Building an Automatic Pattern-Based File Router

Sign in to queue


  This article implements a File Router, which monitors a path on the hard drive and moves files to appropriate locations based on matching certain patterns.
Arian Kulp's Blog

Difficulty: Easy
Time Required: 1-3 hours
Cost: Free
Software: Visual Studio Express Editions


This week's column was inspired by the many times I've come across catch-all folders on people's hard drives. Maybe you've got them too. Perhaps it's a documents folder where every file goes uncategorized. I also see downloads folders that contain Zip files, PDF documents, photos, installers and anything else downloaded. Internet Explorer and Firefox download to a single folder unless overridden. Many users either don't know how to manage folders or just don't spend the time to do so. From these observations I decided it would be nice to do something to reduce this burden, and thus, File Router was born.

File Router acts by monitoring a path on the hard drive, then moving files to appropriate locations based on matching certain patterns. The application is fairly basic, with plenty of opportunity for extension. That's a plus for you, not a cop-out for me!

The accompanying code is provided in both C# and VB. As with all Coding 4 Fun articles, you will need any of the Visual Studio Express Editions. The code samples in this article are shown in Visual Basic 2005; however, the downloadable source code is available in both VB 2005 and C# 2005. Beta 2 of the Express editions can be downloaded from

The code is structured as a Windows Form application with a NotifyIcon to provide a presence in the System Tray. Monitoring the folder occurs using a FileSystemWatcher object. This handy addition to the Framework raises events when certain conditions are met in the file system. These can include file creation, rename, or even a change of contents.

Generic Episode Image

The intent with File Router is to watch for new or renamed files in a given path. When these files are detected, the filename is compared to a list of user-entered expressions. Each expression contains one or more simple, comma-separated patterns such as ".jpg" or "budget." Notice that it isn't as simple as file extensions, though using a period will enable it to work that way.

In addition to the form object (used for settings), the project also includes non-visual controls such as the NotifyIcon and FileSystemWatcher, a FolderBrowserDialog, a ToolTip, and a ContextMenuStrip. I felt that some of the elements in the interface were non-intuitive with simple labels, so popup tooltips appear by many controls. The ContextMenuStrip appears when the user right-clicks on the system tray icon. It used to be that tray icons required a lot of plumbing to make them work. Adding the icon and menu is incredibly easy now.

Generic Episode Image

The main form displays the settings. The actual settings are contained in the FileScannerSettings object. The fields in this object are defined as:

Shared SETTINGS_FILENAME As String = "FileRouter.settings"
Dim _activeScan As Boolean = False
Dim _sourcePath As String
Dim _recursiveScan As Boolean = False
Dim _mappings As Dictionary(Of String, FileMapping) =
    New Dictionary(Of String, FileMapping)()

ActiveScan specifies whether or not the source path is being monitored. File Router can just run on demand if desired. The dictionary of mappings is a generic type consisting of a string key and a FileMapping object value. Each FileMapping then, in turn, contains the expression string and a corresponding destination path. In addition to encapsulating the settings in-memory, this class also contains methods to manage its persistence. To save Settings you call the SaveSettings method, passing the object to save:

Public Shared Sub SaveSettings(ByVal fs As FileScannerSettings)
    Dim str As Stream = File.Open(SETTINGS_FILENAME, FileMode.Create)
    Dim bf As BinaryFormatter = New BinaryFormatter
    bf.Serialize(str, fs)
End Sub

Notice that all of the code required to save the object only takes up four lines. Of course, it would be good to add error handling as well.

Reading the settings is similarly straight-forward:

Public Shared Function GetSettings() As FileScannerSettings
    Dim str As Stream = Nothing
        str = File.Open(SETTINGS_FILENAME, FileMode.Open)
        Dim bf As BinaryFormatter = New BinaryFormatter
        Dim fs As FileScannerSettings = _
            CType(bf.Deserialize(str), FileScannerSettings)
        Return fs
        ' Most likely saved file does not exist
        Return New FileScannerSettings
        If Not str Is Nothing Then
        End If
    End Try
End Function

Notice that this time there is some minimal error-handling. This is necessary because the file may not exist. In that case, the caller will get a fresh object.

Finally, the FileScanner object performs the actual work of scanning the folder (whether invoked on-demand or in response to a file change event), and matching files. The ScanFiles method simply retrieves the list of files in the source path and passes them to PerformMatch:

Public Sub ScanFiles()
    Dim so As SearchOption
    If _settings.RecursiveScan Then
        so = SearchOption.AllDirectories
        so = SearchOption.TopDirectoryOnly
    End If
    Dim files() As String =_
        Directory.GetFiles(_settings.SourcePath, "*.*", so)
    For Each file As String In files
End Sub

Notice that the folder can be enumerated only at the top-level, or recursively. This option is also reflected in the FileSystemWatcher object when the application is in active monitoring mode. The PeformMatch method then checks the filename for a matching expression (case-sensitive through lower case conversions) and moves the file accordingly (comments removed):

Public Sub PerformMatch(ByVal fileName As String)
    Dim fileNameAlone As String = Path.GetFileName(fileName)
    Dim fileNameLower As String = fileNameAlone.ToLower()
    For Each mapping As FileMapping In _settings.FileMappings.Values
        Dim expressions() As String = mapping.Expression.Split(",")
        For Each expression As String In expressions
            If fileNameLower.Contains(expression) Then
                    File.Move(fileName, _
                        Path.Combine( _
                        mapping.DestinationPath, fileNameAlone))
                Catch ex As Exception
                    RaiseEvent ErrorEncountered(fileNameAlone, ex)
                    Continue For
                End Try
            End If
End Sub

Each mapping is enumerated, then split by comma. Each sub-expression is then evaluated using the Contains method of the filename. If a match is found, the file is moved with the File.Move method. Notice the custom event for handling errors. This is a good decoupling, so the FileScanner class has no need to interact with the UI, and the scanner can continue with other files even after a file has an error. PerformMatch can also be called directly with a single filename as based on a file system event. .

Extension Points

  • Error checking is fairly minimal throughout the application. For instance, file paths are not validated at all, so it is best to use the browse dialogs.
  • Expressions could be more robust. Regular expressions or at least simple wildcard patterns would improve the situation as would lists of patterns per destination path, rather than a comma-based list.
  • The visual list of expressions is very basic, not allowing edits. You must delete and re-add to make a change.


I enjoyed putting a number of pieces together to make this work. It has plenty of space for enhancement, but is definitely useable as-is. Download Visual C# 2005 Express Edition or Visual Basic 2005 Express Edition, then take this application for a spin. Get started at


File, Matching, Zips

The Discussion

  • User profile image
    Stefaan Meeuws

    This being the first app that lead me to become a programmer, I'd like to doggy-ear it.

  • User profile image
    Stefaan Meeuws

    This app was the first thing that lead me to using the microsoft visual studio express editions, and now i'm on my way to become a software developer all over again! (I was out of the developer game for over six years.)

    I just wanna say thanks to Arian Kulp. Without his contribution there would be one computer programmer less in this world ..

Add Your 2 Cents