WindowsAppSDK icon indicating copy to clipboard operation
WindowsAppSDK copied to clipboard

StorageDevice.FromId(id) gives 'Access Denied Exception' in a OOP background task in WinUI3 - Desktop

Open JosHuybrighs opened this issue 3 years ago • 7 comments

Describe the bug

I am porting a UWP/DesktopBridge app to WinUI - Desktop. The app registers a DeviceWatcher task to detect inserts and ejects of USB storage devices. In the UWP app the task is an in-process task; in the WinUI app it is (must be) a OOP task. The task runs well but there is a Access denied exception when the task is informed about a USB device being inserted and then calls StorageDevice.FromId(id). The call is there in order to get a StorageFolder and from there to extract the Volume Label and Drive Letter of the MSC device. The app is SyncFolder, which is a folder sync/copy tool for users, and the Volume Label and Drive Letter information is needed in order to find out whether the app must inform the user that a possible candidate storage device is being inserted for a given sync/backup task. This would be necessary when the user is using a set of rotating backup disks. Volume Label (and to a lesser extend Drive Letter) would then be the common name for all disks in the set.

The exception happens in the call to Windows.Devices.Portable.StorageDevice.FromId(). The code is actually quite simple:

async Task<List<StorageDeviceExt.StorageDeviceInfo>> GetAvailableMSCStorageDevicesAsync()
        {
            var devices = new List<StorageDeviceExt.StorageDeviceInfo>();
            var portableStorageDevices = await Windows.Devices.Enumeration.DeviceInformation.FindAllAsync(Windows.Devices.Enumeration.DeviceClass.PortableStorageDevice);
            ConditionalFileLogger.Log($"PortableStorageDevicesMonitor - GetAvailableStorageDevicesAsync: found {portableStorageDevices.Count} portable devices");
            foreach (var d in portableStorageDevices)
            {
                int step = 0;
                try
                {
                    step = 1;
                    var props = d.Properties;
                    ConditionalFileLogger.Log($"PortableStorageDevicesMonitor - GetAvailableStorageDevicesAsync: get storage folder of {d.Name}, {d.Id}");
                    step = 2;
                    var storageFolder = Windows.Devices.Portable.StorageDevice.FromId(d.Id);
                    if (storageFolder != null &&
                        !String.IsNullOrEmpty(storageFolder.Path))
                    {
                        step = 3;
                        ConditionalFileLogger.Log($"PortableStorageDevicesMonitor - RefreshPortableStorageDevicesAsync: add {{d.Name}} ({storageFolder.Path}) to list");
                        string driveLetter;
                        string volumeLabel = StorageDeviceExt.GetVolumeLabelAndDriveLetter(storageFolder.DisplayName, out driveLetter);
                        devices.Add(new StorageDeviceExt.StorageDeviceInfo()
                        {
                            DeviceId = d.Id,
                            DriveType = System.IO.DriveType.Removable,
                            VolumeLabel = volumeLabel,
                            DriveLetter = driveLetter
                        });
                    }
                }
                catch (Exception e)
                {
                    ConditionalFileLogger.Log($"StorageDeviceHelper - RefreshPortableStorageDevicesAsync exception 1, step {step} for {d.Name}: {e.Message}");
                    Analytics.TrackEvent("PortableStorageDevicesMonitor-Exception", new Dictionary<string, string>
                    {
                        { "Exception", $"GetAvailableStorageDevicesAsync: {e.Message}" }
                    });
                }
            }
            ConditionalFileLogger.Log($"PortableStorageDevicesMonitor - GetAvailableStorageDevicesAsync: found {devices.Count} portable MSC storage devices");
            return devices;
        }

ConditionalFileLogger is my own implementation of a log service that outputs log events to a file. Analytics.TrackEvent(..) is a call to ms appcenter to register the exception in appcenter.

The exception is: Access denied. (0x80070005 (E_ACCESSDENIED)) and is generated for every USB device for which the FromId() call is made.

The same call to the above GetAvailableMSCStorageDevicesAsync() method runs fine when invoked from within the WinUI app itself (i.e. not from the OOP WinRT process). So, it seems that the app itself is granted all the rights to get a StorageFolder for a portable device but not the OOP DeviceWatcher task.

Is this intended behavior? That would be strange/wrong because the same code in the UWP in-process DeviceWatcher runs without issues.

Steps to reproduce the bug

Create a OOP DeviceWatcher task in a WinUI 3 Desktop app (packaged, using wap project). Register the task. Call Windows.Devices.Portable.StorageDevice.FromId()

Expected behavior

No response

Screenshots

No response

NuGet package version

1.0.0

Packaging type

Packaged (MSIX)

Windows version

Windows 11 version 21H2 (22000)

IDE

Visual Studio 2022

Additional context

No response

JosHuybrighs avatar Apr 07 '22 20:04 JosHuybrighs

Ok, so you're basically doing what https://github.com/microsoft/Windows-universal-samples/tree/main/Samples/DeviceEnumerationAndPairing/cs does, but in a WinUI3 app instead of a UWP?

jonwis avatar Apr 08 '22 00:04 jonwis

@jonwis Yes, except that in my implementation I am also creating a list of all mounted USB devices with their ID, Volume Label and Drive Letter.

In the mean time I also found out that the issue is not related to the task being a DeviceWatcher. I called the above method in a Timer background task and, as I expected, the Access Denied exception occurs also there. It looks like the problem is related to the fact that the call is done in a OOP background task which in WinUI3-Desktop must be build using a Windows Runtime Component. I am aware that there are restrictions with a normal WinRT component but I don't understand why such restrictions must still exist when the component is integrated in a WinUI3-Desktop app/package.

JosHuybrighs avatar Apr 08 '22 07:04 JosHuybrighs

Ok, thanks. @aeloros we should probably talk more about that "bounce a bg task invocation back to the main app process" - maybe what's happening here is the BG tasks are getting launched as AppContainers, which don't have access?

jonwis avatar Apr 08 '22 16:04 jonwis

Yeah, in the InProc case the task is run in the same process as the UI. It therefore gets the same sort of access as a foreground application. In the OOP case, the task runs inside BackgroundTaskHost.exe which is never foreground. I'm not sure if the 'foreground' nature is what is blocking here, but clearly the UI app has a different set of policies applied.

My guess is if you changed the UWP scenario to be OOP as well, it would fail. I actually think this is expected and probably by design, but since we have new scenarios to support it could be improved for sure. :)

aeloros avatar Apr 08 '22 20:04 aeloros

@aeloros I appreciate the quick answer. I assume that in this case the following must be decided:

  1. Allow also in-process background tasks in a WinUI3-Desktop app. That is currently not possible and will require enhancements from Microsoft.
  2. Allow OOP background tasks to have the same set of policies as the 'main' app. Also needs effort from Microsoft.
  3. Extend the 'portable' devices API with more properties one can retrieve for MSC devices: Volume Label, Drive Letter, BitLocker status, etc., and allow these properties to be retrieved everywhere. Also needs enhancements from Microsoft.
  4. Launch the 'main' app (or another full trust process) from within the OOP task, but without a visible window, and hand-over anything to the main app what cannot be done within the OOP task. That can be done today.

I am afraid that I will therefore have to use option 4: that is not a price winner in software design if the only thing to do is to send a toast notification.

JosHuybrighs avatar Apr 09 '22 07:04 JosHuybrighs

Maybe look into using protocol launch for your design of #4. There is a lot of room for improvement on our end here. :|

aeloros avatar Apr 11 '22 20:04 aeloros

UniversalBGTask API has been introduced here:

  • https://github.com/microsoft/WindowsAppSDK/discussions/5347#discussioncomment-12874999.

AppContainer support is still incomplete to this date.

mominshaikhdevs avatar Jun 18 '25 13:06 mominshaikhdevs