3

2018 EDIT: As indicated below and by other sources, targeting the Creator's Update or higher allows for read-only file access of a file opened for write by another process. Hooray!

I seem to have hit a brick wall when trying to develop a Windows Store app for the desktop. I'm trying to open a large (100+ MB) log file that another application has open and do real time processing on the latest events as they're written to the file.

With regular, non-sandboxed C#, this is pretty straightforward:

System.IO.FileStream stream = File.Open("LOGFILE PATH HERE", System.IO.FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

Unfortunately, in a UWP, I get "UnauthorizedAccessException" whenever I try to open a file that's in use by another app. I've tried every API in every combination I could find, but have had zero luck, so I've come here for some suggestions.

Some of what I've tried:

Windows.Storage.Pickers.FileOpenPicker picker = new Windows.Storage.Pickers.FileOpenPicker();
picker.ViewMode = Windows.Storage.Pickers.PickerViewMode.List;
//Prompt the user to open the log file:
Windows.Storage.StorageFile logFile = await picker.PickSingleFileAsync();
picker.FileTypeFilter.Add(".txt");

//This won't work in any case, because it doesn't use the handle that the user picked,
// so the UWP sandboxing blocks it:
new FileStream(logFile.Path, FileMode.OpenOrCreate, FileAccess.Read);

//EDIT: These don't work if the file is open either, I must have made a mistake earlier
await FileIO.ReadBufferAsync(logFile);
await FileIO.ReadLinesAsync(logFile);

//These work if the file is not open by another app, but fail if another app has the file open
await logFile.OpenAsync( Windows.Storage.FileAccessMode.Read);
await logFile.OpenStreamForReadAsync();

Quick Repro:

Open a PowerShell window, and run this command to hold open "test.txt" in your home directory:

$f = [System.IO.File]::Open("test.txt", [System.IO.FileMode]::OpenOrCreate, [System.IO.FileAccess]::Write, [System.IO.FileShare]::ReadWrite);
  • See this post: http://stackoverflow.com/questions/4400517/how-can-i-read-a-file-even-when-getting-an-in-use-by-another-process-exception – Sparrow Feb 17 '17 at 22:28
  • That's only for non-UWP apps, it won't work in this case.. that's the first thing I tried. Also I have that code as an example of what didn't work in my comment :*( – Debug Arnaut Feb 17 '17 at 22:38
  • Man, this is frustrating.. according to the System.Diagnostics.Stopwatch, ReadBufferAsync takes 100ms to read a modest 100MB log file even on my SSD-equipped super fast dev machine, so I guess it really is buffering the whole file. Meanwhile, on files that aren't already open, StorageFile's OpenAsync(...) method takes under 2ms, so it's exactly what i need. Fiddlesticks!!! – Debug Arnaut Feb 18 '17 at 00:24
  • I dont know the reason but I think if the other app have write the file that you can read it and it can be read when other app read it in win32. – lindexi Feb 18 '17 at 00:44

2 Answers2

3

This is expected behavior for the Universal APIs as of the Anniversary Update. (aka RS1). The Windows.Storage.* APIs and streams use what is called a "Polite Reader" model. In this model readers can be interrupted by a writer, which generates the OPLOCK break errors. In RS1 this also means that readers are blocked if ANY open handle for write exists already.

In the Creators Update (aka RS2) some things are changing on this. As the Universal Platform evolved from the original WinRT with a single foreground app, the need to allow apps to use more traditional models arose. Thus, in RS2 we are making a few changes to help in this scenario.

  1. An Unmodified polite reader will no longer fail on open if a writer already exists. However, readers will still get oplock breaks if the writer actually writes to the file.
  2. Sharing Violations are surfaced directly to the caller instead of being translated into AccessDenied. (For compatibility, this new behavior is gated on the calling app declaring RS2 as the tested platform in the apps’ manifest)
  3. There are new StorageOpenOptions available so that apps can change their code to use the new options to get behavior that doesn’t involve the oplocks, effectively opting out of the OpLock behavior.
1

I've made a simple test and it should work. The test goes like this: - open a file.txt with Notepad, the file contains only one line of text, - run the app with code below, - pick a file which is still opened in Notepad, - you should see in debug output the first line and empty second.

The code:

public async Task GetFile()
{
    Windows.Storage.Pickers.FileOpenPicker picker = new Windows.Storage.Pickers.FileOpenPicker();
    picker.ViewMode = Windows.Storage.Pickers.PickerViewMode.List;
    picker.FileTypeFilter.Add(".txt");
    //Prompt the user to open the log file:
    Windows.Storage.StorageFile logFile = await picker.PickSingleFileAsync();

    try
    {
        using (var stream = await logFile.OpenStreamForReadAsync())
        using (var reader = new StreamReader(stream))
        {
            var line = await reader.ReadLineAsync();
            Debug.WriteLine($"The first line: {line} - waiting");
            await Task.Delay(10000);
            line = await reader.ReadLineAsync();
            Debug.WriteLine($"The next line: {line} - waiting");
        }
    }
    catch (Exception exc)
    {
        Debug.WriteLine($"Exception {exc.Message}");
    }
}

In the second test I have modified the file in Notepad and saved it, while the code above hits await Task.Delay(), then when trying to read the second line, you will likely get: 'Exception The handle with which this oplock was associated has been closed. The oplock is now broken.'.

I see that you are not disposing the streams, maybe the problem is here? Have you tried to use using for Idisposable?

Romasz
  • 29,662
  • 13
  • 79
  • 154
  • Thanks Romasz. I tried OpenStreamForReadAsync, though, and it didn't work. I just tried out the example you provided, and it does work for a doc I have open in Notepad, but if I hold it open using another app (or PowerShell) it throws UnauthorizedAccessException. I'm kind of a C# newbie though, how should I execute that task? I made a button event handler and just called "await GetFile()". I'll edit my original post to add the powershell command I used to hold a file open, that seems to duplicate the behavior of the large logging app. – Debug Arnaut Feb 18 '17 at 18:40
  • @DebugArnaut Have you tried with other app or powershell only? Powershell standalone or within visual studio? – Romasz Feb 19 '17 at 09:04