File System Monitoring
By Eric — — 6 minute readAt times it is useful to keep tabs on what is happening (or has happened) in the file system. This is a brief overview of all the ways I know of to track activity on a Windows file system.
Scanning
Scanning is the brute-force approach to knowing what is happening in the
file system. The relevant Win32 APIs are FindFirstFile
and
FindNextFile
. Limitations to this approach are that it is slow and
resource intensive, and it can't detect file deletions unless it can
compare against a previous snapshot of the file system.
Change Notifications
The Win32 APIs FindFirstChangeNotification
and
FindNextChangeNotification
let you monitor a volume subtree and be
notified when a particular class of file changes occurs. The main
limitation of this approach is that these APIs don't actually tell you
what changed (unless what you're watching is really narrow), just that
something did.
To know what changed, you need to use ReadDirectoryChangesW
instead.
This is much more efficient than scanning, but changes can be missed if
your application isn't running, or if the buffer supplied to
ReadDirectoryChangesW
overflows.
I think the documentation for the .NET Framework
FileSystemWatcher
class (which clearly uses ReadDirectoryChangesW
in its implementation)
is actually better at explaining the limitations and pitfalls than the
documentation for ReadDirectoryChangesW
itself.
Change Journal
The change journalis a feature of NTFS 5.0 (Windows 2000). It keeps a persistent record of all changes to a volume, which can then be queried by an application to see what has changed. This is more efficient than scanning, and doesn't require that your application be running in order to catch all the changes.
The change journal obviously requires NTFS so if you need to monitor FAT volumes, this isn't the solution. Also, other applications can actually turn the change journal off, so you might still have to resort to scanning if this were to happen (and it is detectible) to be sure that something wasn't missed. Finally, you need administrator privileges to query the change journal. I guess it would be too hard to enforce all the access rights to the file system and keep the journal efficient.
Distributed Link Tracking
Distributed Link Tracking is another NTFS 5.0-only approach. It was created to solve the problem of broken shortcuts and OLE links. The interesting thing about link tracking is that with it you can actually detect moves to other machines, renames of machines or network shares that would otherwise "break" a known path, or even physical moves of a volume to another machine (but not removable media).
Link tracking is a polling system -- you don't get notified when a file moves or gets renamed. Instead you have to ask where a particular file is.
You can try out the functionality by creating a shortcut to any file. Now rename the original file and double-click the shortcut. It should still resolve correctly to the renamed file. Move the file to some network share on the domain and the shortcut will still work. Another user can also move a file, and as long as the location to which it was moved can be resolved to a path, the link tracking service can find it.
You can also use fsutil, a built-in Windows utility, to manipulate object identifiers (OIDs) that are used to implement link tracking.
I wrote a little Windows Forms program to explore using link tracking programmatically. Here's the interesting part of the code (I got the needed interop signatures from pinvoke.net):
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
namespace FileTracker
{
public partial class MainForm : Form
{
IShellLinkW shellLink;
public MainForm()
{
InitializeComponent();
}
private void TrackFile(string fileName)
{
shellLink = (IShellLinkW)Activator.CreateInstance(
Type.GetTypeFromCLSID(UnsafeNativeMethods.CLSID_ShellLink));
shellLink.SetPath(fileName);
shellLink.SetDescription("File Tracker Link");
}
private string ResolveFile()
{
if (shellLink != null)
{
shellLink.Resolve(IntPtr.Zero,
UnsafeNativeMethods.SLR_FLAGS.SLR_UPDATE |
UnsafeNativeMethods.SLR_FLAGS.SLR_NO_UI |
UnsafeNativeMethods.SLR_FLAGS.SLR_NOSEARCH);
StringBuilder path = new StringBuilder(256);
UnsafeNativeMethods.WIN32_FIND_DATAW findData =
new UnsafeNativeMethods.WIN32_FIND_DATAW();
shellLink.GetPath(path,
path.Capacity,
out findData,
UnsafeNativeMethods.SLGP_FLAGS.SLGP_UNCPRIORITY);
if (File.Exists(path.ToString()))
{
return path.ToString();
}
else
{
return path.ToString() + " (deleted?)";
}
}
return string.Empty;
}
private void btnBrowse_Click(object sender, EventArgs e)
{
OpenFileDialog dialog = new OpenFileDialog();
dialog.Title = "Choose File to Track";
if (dialog.ShowDialog(this) == DialogResult.OK)
{
tbOriginal.Text = dialog.FileName;
tbCurrentLocation.Text = string.Empty;
TrackFile(dialog.FileName);
}
}
private void btnResolve_Click(object sender, EventArgs e)
{
tbCurrentLocation.Text = ResolveFile();
}
}
}
File System Filter Driver
A filter driver can see everything that happens to the file system because it is inserted between the IO manager and the underlying file system implementation. I don't have any personal experience with this approach, but there is abundant documentation from Microsoft on filter drivers.