3

I have a file, it is large, reading it takes a long time and I'd like to have some monitoring to see when it's being written to.

I can solve this quite easily by chucking some logging into the application that it reading it, however I'd like something more agnostic. There are quite a few application that have a similar use case. Monitoring writes should be easy enough as I can watch the last modified date, but reading isn't so easy.

If there any way to passively monitor read and writes to a file?

To clarify: If it can do done in 100% C#, then great, if not then shelling out to some other 'thing', or even resorting to some other language is fine. I don't really mind what the watching bit is written in.

Trying Rahul's Solution

I have set up ithe following test code. It dumps events to the console:

public static void Main()
{
    var taskFactory = new TaskFactory();
    var setup = new Action(() =>
    {
        var setupWatcher =
            new Action<NotifyFilters, string, FileSystemWatcher>((filters, s, watcher) =>
            {
                watcher.EnableRaisingEvents = true;
                watcher.NotifyFilter = filters;
                watcher.Changed += (sender, args) => System.Console.WriteLine(s, args.FullPath, args.ChangeType);
            });


        var lastAccessWatcher = new FileSystemWatcher(BASE_PATH);
        setupWatcher(NotifyFilters.LastAccess,
            "File: {0}\tFilter: LastAccess\tType: {1}", lastAccessWatcher);

        var lastWriteWatcher = new FileSystemWatcher(BASE_PATH);
        setupWatcher(NotifyFilters.LastWrite, "File: {0}\tFilter: LastWrite\tType: {1}",
            lastWriteWatcher);

        var fileNameWatcher = new FileSystemWatcher(BASE_PATH);
        setupWatcher(NotifyFilters.FileName,
            "File: {0}\tFilter: FileName\tType: {1}", fileNameWatcher);

        var directoryNameWatcher = new FileSystemWatcher(BASE_PATH);
        setupWatcher(NotifyFilters.LastWrite, "File: {0}\tFilter: DirectoryName\tType: {1}",
            directoryNameWatcher);
    });

    taskFactory.StartNew(setup);

    while (true)
    {
        Thread.Sleep(10);
    }
}

However, when I open a text file in notepad, no event is thrown by the lastAccessWatcher, whereas, when I save, two events are thrown by the lastWriteWatcher and the directoryNameWatcher, as per below.

File: F:\FileMonitor\New Text Document.txt      Filter: LastWrite       Type: Changed
File: F:\FileMonitor\New Text Document.txt      Filter: LastWrite       Type: Changed
File: F:\FileMonitor\New Text Document.txt      Filter: DirectoryName   Type: Changed
File: F:\FileMonitor\New Text Document.txt      Filter: DirectoryName   Type: Changed

So...

  1. What does trigger 'last access'
  2. Can I actually have any trigger fired when a file is read?
BanksySan
  • 27,362
  • 33
  • 117
  • 216
  • Have you considered using [FileSystemWatcher](https://msdn.microsoft.com/en-us/library/system.io.filesystemwatcher(v=vs.110).aspx) ? [This](http://stackoverflow.com/questions/721714/notification-when-a-file-changes) SO thread provides description how to do that. – sszarek Apr 24 '15 at 11:54
  • @sszarek Aye, that was my first 'go to' thing, but it's events are all around file system changes rather than activities. – BanksySan Apr 24 '15 at 12:26
  • That's true. There are some ideas across internet/SO to run loop which tries to open FileStream. But this is dirty workaround. E.g. [here](http://stackoverflow.com/questions/2515766/how-to-test-if-a-file-is-currently-being-written-to) – sszarek Apr 24 '15 at 12:51
  • @sszarek I used that hack last week so that I could read from a newly created file, the created even fires when the file starts being written to rather than when it becomes available to read from. Nasty, icky hack. – BanksySan Apr 24 '15 at 13:27

4 Answers4

2

For watching writes to the file, the FileSystemWatcher class is the right way to go. However if you want to know if a file is being written, what I would do is get a list of all the open files by process, and monitor when the file is opened and then closed.

There is a CodeProject article that shows how to get the open files by process here.

Ron Beyer
  • 11,003
  • 1
  • 19
  • 37
  • 1
    The `FileSystemWatcher` only fires events on changes, so that would only work for catching writes or deletions. – BanksySan Apr 24 '15 at 12:22
  • Yes, which is why I linked to a CodeProject article that has a project that shows how to list the files in use (open) by a process. A file has to be opened to read, you can keep track of that list and when that file appears and then disappears, you know that process has finished using it (reading or writing). – Ron Beyer Apr 24 '15 at 12:44
  • Just read the article, looks like it could well be a solution. Worth my up vote! – BanksySan Apr 24 '15 at 12:48
1

You can use the FileSystemWatcher class

Listens to the file system change notifications and raises events when a directory, or file in a directory, changes.

You can use the different events

and also read this FileSystemWatcher Tips

An example:

public void MyFileWatcher(string path)
{
    FileSystemWatcher watcher = new FileSystemWatcher();
    watcher.Path = path;
    watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite 
       | NotifyFilters.FileName | NotifyFilters.DirectoryName;
    watcher.Filter = "myFile.txt";
    watcher.Changed += new FileSystemEventHandler(OnChanged);    
    watcher.EnableRaisingEvents = true;
}

private static void OnChanged(object source, FileSystemEventArgs e)
{
   Console.WriteLine(e.FullPath + " " + e.ChangeType);
}

Similarly you can add other event handlers definition.

Also if you want to check the read then you can use the FileInfo.LastAccessTime and FileInfo.Refresh() in a polling loop to get the information of when your file is being read.

Rahul Tripathi
  • 168,305
  • 31
  • 280
  • 331
  • 2
    Only works for writes though, if the OP wants to know when the file is read, thats another story. – Ron Beyer Apr 24 '15 at 11:56
  • @RonBeyer:- Updated my answer for the read functionality as well ie, to use the `FileInfo.LastAccessTime` and `FileInfo.Refresh()` in a polling loop. – Rahul Tripathi Apr 24 '15 at 12:14
  • 1
    Please read this: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724290%28v=vs.85%29.aspx Specifically where it says "For example, the resolution of create time on FAT is 10 milliseconds, while write time has a resolution of 2 seconds and access time has a resolution of 1 day, so it is really the access date." – Ron Beyer Apr 24 '15 at 12:16
  • @RahulTripathi that LastAccess filter could be the magic I need. I'll give it a go and report back. – BanksySan Apr 24 '15 at 12:27
  • @BanksySan:- Sure. If you want I can give an example for that as well. Do let me know if you want me to add that? – Rahul Tripathi Apr 24 '15 at 12:33
  • Thanks @RahulTripathi, I'll have a stab and get back to you if I'm not getting any luck. – BanksySan Apr 24 '15 at 12:49
  • @RahulTripathi From what I'm gathering, I'd have to have multiple `FileSystemWatcher` instances to watch for different events. – BanksySan Apr 25 '15 at 01:39
  • @RahulTripathi If you are still willing to give me a sample, then that would be very much appreciated. – BanksySan Apr 25 '15 at 03:24
1

One solution would be to use a Microsoft tool called Process Monitor. It's able to list all CreateFile/ReadFile/WriteFile calls any process does. There are several command line options available:

enter image description here

ken2k
  • 48,145
  • 10
  • 116
  • 176
  • @RonBeyer Of course, but it's not clear from the question being able to do that from code (without launching a third party application) is a requirement. – ken2k Apr 24 '15 at 12:00
  • In code would be ideal, but is there's another tool that can do it then I have no problems with writing a wrapper for that. – BanksySan Apr 24 '15 at 12:23
  • @ken2k it looks like this is a graphical only interface? No way to get it to pump out data to some other application. – BanksySan Apr 25 '15 at 01:38
  • @BanksySan No, there's also a command line interface so you can start it from another process and get the results back. – ken2k Apr 27 '15 at 08:34
0

In code your task is handled with a filesystem filter driver. Process Monitor application (already mentioned in another answer) includes such driver in pre-built form.

You can write a kernel-mode driver yourself, yet this would probably be an overkill. Then there exists our CallbackFilter - a class library with a pre-created kernel-mode driver included. You write code in C# and catch all filesystem operations before or after they are performed. If your need is short-term, then the evaluation license will work.

Eugene Mayevski 'Callback
  • 45,135
  • 8
  • 71
  • 121