none
How do you open and lock a file with UWP?

    Question

  • I have a UWP app (targeting Build 17134) that lets a user select a file with FileOpenPicker. I then want to open the file and have exclusive access to it. That is, all other processes are prevented from reading, writing, deleting or renaming the file. With Win32 CreateFile works with share mode = 0. So, how do I do this with UWP?

    1) It doesn't look like StorageFile or StorageFolder have anyway to set share mode. Is this correct?

    2) From the Microsoft documentation here, it sounds like System.IO.File will work but when I execute the following code

            var s = File.Open("c:\\temp\\a.txt", FileMode.Create);

    I get an "Access to the path 'c:\temp\a.txt' is denied." exception. This happens whether or not the file exists. I have broadFileSystemAccess set in the package manifest. If I select "a.txt" with the file picker, I can read and write to it with StorageFile and its streams.

    3) Is there a way to get a Win32 HANDLE from a StorageFile? If so, maybe I could use LockFile or some other API to give me exclusive access.

    Monday, October 29, 2018 2:11 AM

Answers

  • More or less correct. System.IO will work by default in locations that the app has direct permissions to (i.e its install directory and application data directories) To touch locations that the app doesn't have direct permissions to you'll need to go through the file broker which can provide additional permissions as granted by the user (directly via a file picker, implicitly via capabilities, etc.). The normal way to use the broker is via the StorageItem (StorageFolder and StorageFile) classes.

    Newer versions of Windows 10 are expanding this and provide access to brokered HANDLEs in several ways. Starting with version 1803 (the April 2018 update), your app can call the CreateFileFromApp to create or open a file wherever the app has brokered access. Since about the Creators Update your app could query a HANDLE from a StorageFile with IStorageItemHandleAccess . Once you have this HANDLE you can use it with any System.IO classes methods that can instantiate from a HANDLE (e.g. FileStream), but not from classes which take a path and then open their own handle.

    You should be able to use this HANDLE with LockFile, but I don't know offhand how that will work with the file broker process if you get the HANDLE from IStorageItemHandleAccess rather than CreateFileFromApp. The broker process will have the file open to service the StorageFile object.

    Tuesday, October 30, 2018 10:31 PM
    Owner
  • The reason CreateFileFromAppW was failing was the form of the input filename. If I used the UNC prefix (\\?\) or URI prefix (file://) the call would be successful. But there was strangeness though. 1) sometimes, the UNC prefix would work and other times it would not. In the latter cases, the URI prefix would work. So, I'd try one and if it failed try the other. 2) even though the function as successful, it would not open the file with complete exclusivity. Inside the app, calling CreateFileFromAppW a second time would fail with 0x80070020 (The process cannot access the file because it is being used by another process). But outside the app, the file did not have an exclusive lock on it. For example, even though my app "locked" a file, Explorer could rename it or delete it. So, I'm not sure what was going on but something wasn't working right.

    So, I gave IStorageItemHandleAccess a shot. Worked great! And it seems to do exclusive locking correctly. I did the following:

    [ComImport] [Guid("5CA296B2-2C25-4D22-B785-B885C8201E6A")] [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] public interface IStorageItemHandleAccess { uint Create(uint access, uint sharing, uint option, IntPtr opLockHandler, ref IntPtr handle); }; ... StorageFile storageFile from picker var comInterface = Marshal.GetComInterfaceForObject(storageFile, typeof(IStorageItemHandleAccess)); var storageHandleAccess = (IStorageItemHandleAccess)Marshal.GetObjectForIUnknown(comInterface); const uint HAO_READ = 0x120089; const uint HAO_WRITE = 0x120116; IntPtr handle = IntPtr.Zero; storageHandleAccess.Create(HAO_READ | HAO_WRITE, 0, 0, IntPtr.Zero, ref handle); var safeHandle = new SafeFileHandle(handle, true); using (var inputStream = new FileStream(safeHandle, FileAccess.ReadWrite)) ...

    • Marked as answer by Chuck Bohling Thursday, November 8, 2018 2:02 AM
    • Unmarked as answer by Chuck Bohling Thursday, November 8, 2018 5:13 PM
    • Marked as answer by Chuck Bohling Thursday, November 8, 2018 5:14 PM
    Wednesday, November 7, 2018 11:18 PM

All replies

  • You're sample works great but it's in AppData. I need to get to a random file like c:\msdn\sample.txt. When I use the following code:

        StorageFolder folder = await StorageFolder.GetFolderFromPathAsync("c:\\");
        var msdn = await folder.CreateFolderAsync("msdn");
        StorageFile samplefile = await msdn.CreateFileAsync("sample.txt");
        FileStream stream = new FileStream(samplefile.Path, FileMode.Open, FileAccess.Read, FileShare.None);

    the folder c:\msdn and the file sample.txt are created but I get an UnauthorizedAccessException creating the FileStream. Experimenting with the arguments to the constructor doesn't change anything.

    So it appears System.IO.File will only work in the app's sandbox. Is that correct?

    Tuesday, October 30, 2018 10:00 PM
  • More or less correct. System.IO will work by default in locations that the app has direct permissions to (i.e its install directory and application data directories) To touch locations that the app doesn't have direct permissions to you'll need to go through the file broker which can provide additional permissions as granted by the user (directly via a file picker, implicitly via capabilities, etc.). The normal way to use the broker is via the StorageItem (StorageFolder and StorageFile) classes.

    Newer versions of Windows 10 are expanding this and provide access to brokered HANDLEs in several ways. Starting with version 1803 (the April 2018 update), your app can call the CreateFileFromApp to create or open a file wherever the app has brokered access. Since about the Creators Update your app could query a HANDLE from a StorageFile with IStorageItemHandleAccess . Once you have this HANDLE you can use it with any System.IO classes methods that can instantiate from a HANDLE (e.g. FileStream), but not from classes which take a path and then open their own handle.

    You should be able to use this HANDLE with LockFile, but I don't know offhand how that will work with the file broker process if you get the HANDLE from IStorageItemHandleAccess rather than CreateFileFromApp. The broker process will have the file open to service the StorageFile object.

    Tuesday, October 30, 2018 10:31 PM
    Owner
  • To reiterate, I can open "c:\temp\a.txt" with the file picker and use the returned StorageFile to read and write the file using its FileRandomAccessStream. All that works fine. I do have broadFileSystemAccess. I'm on build 17134.376.

    I tried the following in my UWP app:

    [DllImport("Windows.Storage.OneCore.dll")]
    public static extern IntPtr CreateFileFromAppW(
        string lpFileName,
        uint dwDesiredAccess,
        uint dwShareMode,
        IntPtr lpSecurityAttributes,
        uint dwCreationDisposition,
        uint dwFlagsAndAttributes,
        IntPtr hTemplateFile);
    
    [DllImport("Windows.Storage.OneCore.dll")]
    public static extern IntPtr CreateFile2FromAppW(
        string lpFileName,
        uint dwDesiredAccess,
        uint dwShareMode,
        uint dwCreationDisposition,
        IntPtr pCreateExParams);
    ...
        const uint GENERIC_READ = 0x80000000;
        const uint FILE_SHARE_READ = 0x00000001;
        const uint OPEN_ALWAYS = 4;
        const uint FILE_ATTRIBUTE_NORMAL = 0x80;
    
        var h1 = CreateFileFromAppW("c:\\temp\\a.txt", GENERIC_READ, FILE_SHARE_READ,
                                    IntPtr.Zero, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);
        var e1 = GetLastError();
        var h2 = CreateFile2FromAppW("c:\\temp\\a.txt", GENERIC_READ, FILE_SHARE_READ,
                                     OPEN_ALWAYS, IntPtr.Zero);
        var e2 = GetLastError();
    ...
    

    The returned file handles h1 and h2 were both -1 and unfortunately, e1 and e2 were both 0. Any idea what could be wrong?

    Thursday, November 1, 2018 12:17 AM
  • Your reiteration matches my expectations: the broadFileSystemAccess capability works with the filebroker and explicitly documents that it allows access via the Storage API that you're using. That part's straightforward.

    I don't know why your calls to CreateFileFromAppW aren't working - the same calls work for me. I get valid handles from CreateFileFromAppW and CreateFile2FromAppW when running your code snippet with the broadFileSystemAccess restricted capability, both without using the picker and after selecting the file with a FileSavePicker or FileOpenPicker. 

    As for the blank last error, I'm guessing that you're pinvoking GetLastError here. This doesn't work since GetLastError returns the error from the last unmanaged call made, which is likely to be some internal call by the CLR rather than your CreateFileFromAppW call. To get last error information from a pinvoke call Marshal.GetLastWin32Error().

    You can also use a tool such as procmon to trace the low level calls and their error codes.

    If you'd like somebody to work with you one-on-one to see what's going on please open a support case at http://aka.ms/storesupport with the "Windows 10 UWP app development / System Services Development / Files and folders API" topic and we'll have somebody work with you directly.

    --Rob

    Thursday, November 1, 2018 8:04 PM
    Owner
  • The reason CreateFileFromAppW was failing was the form of the input filename. If I used the UNC prefix (\\?\) or URI prefix (file://) the call would be successful. But there was strangeness though. 1) sometimes, the UNC prefix would work and other times it would not. In the latter cases, the URI prefix would work. So, I'd try one and if it failed try the other. 2) even though the function as successful, it would not open the file with complete exclusivity. Inside the app, calling CreateFileFromAppW a second time would fail with 0x80070020 (The process cannot access the file because it is being used by another process). But outside the app, the file did not have an exclusive lock on it. For example, even though my app "locked" a file, Explorer could rename it or delete it. So, I'm not sure what was going on but something wasn't working right.

    So, I gave IStorageItemHandleAccess a shot. Worked great! And it seems to do exclusive locking correctly. I did the following:

    [ComImport] [Guid("5CA296B2-2C25-4D22-B785-B885C8201E6A")] [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] public interface IStorageItemHandleAccess { uint Create(uint access, uint sharing, uint option, IntPtr opLockHandler, ref IntPtr handle); }; ... StorageFile storageFile from picker var comInterface = Marshal.GetComInterfaceForObject(storageFile, typeof(IStorageItemHandleAccess)); var storageHandleAccess = (IStorageItemHandleAccess)Marshal.GetObjectForIUnknown(comInterface); const uint HAO_READ = 0x120089; const uint HAO_WRITE = 0x120116; IntPtr handle = IntPtr.Zero; storageHandleAccess.Create(HAO_READ | HAO_WRITE, 0, 0, IntPtr.Zero, ref handle); var safeHandle = new SafeFileHandle(handle, true); using (var inputStream = new FileStream(safeHandle, FileAccess.ReadWrite)) ...

    • Marked as answer by Chuck Bohling Thursday, November 8, 2018 2:02 AM
    • Unmarked as answer by Chuck Bohling Thursday, November 8, 2018 5:13 PM
    • Marked as answer by Chuck Bohling Thursday, November 8, 2018 5:14 PM
    Wednesday, November 7, 2018 11:18 PM