I'm seeing similar symptoms in the following environment:
- JavaScript application
- Minimum version 10.0.10240.0 (initial version released July 2015)
- Target version 10.0.19041.0 (version 2004, May 2020 Update)
- Multi-user application (manifest
uap:SupportedUsers
= multiple
)
The scenario is creating a picker using the Windows.Storage.Pickers.FileOpenPicker
constructor (intended for single user applications) and then calling FileOpenPicker.PickSingleFileAsync
or FileOpenPicker.PickMultipleFilesAsync
. The picker appears to behave normally including while selecting a file, but there is a noticeable delay in user interface responsiveness after choosing a file, and the returned file is null
. (Based on an experiment of deploying the "File picker provider sample" from the Universal Windows Platform (UWP) app samples modified to be a multi-user application, this might be the file picker basket throwing "Access is denied." (error code 0x80070005
) when File Explorer calls Windows.Storage.Pickers.Provider.FileOpenPickerUI.AddFile
.)
I switched to creating a file picker for multi-user applications (using FileOpenPicker.CreateForUser
), but calling FileOpenPicker.PickSingleFileAsync
or FileOpenPicker.PickMultipleFilesAsync
would result in the error "Invalid window handle." (error code 0x80070578
).
The workaround I settled on was catching that specific error code, then calling Windows.Storage.DownloadsFolder.CreateFolderForUserAsync
with a made-up preset temporary folder name (and setting Windows.Storage.CreationCollisionOption.FailIfExists
), and if that succeeded, then I immediately cleaned up that folder by calling StorageFolder.DeleteAsync
. Regardless of whether the temporary folder was created or not, this prepares the file picker for the remaining running lifetime of the application, and you can retry calling the FileOpenPicker
methods normally.
Note that this workaround does leave behind a folder in the Downloads folder named after your application that your application cannot delete automatically; if its presence bothers you or your users, you can have the user open a folder picker to the parent folder (the Downloads folder) and then you can delete that child folder, preferably after verifying that the child folder is still empty.
The DownloadsFolder
workaround is implemented as the following; in Microsoft Visual Studio 2017, create a new JavaScript project using the "Blank App (Universal Windows)" template, set the minimum and target versions as described earlier, add <uap:SupportedUsers>multiple</uap:SupportedUsers>
inside /Package/Properties
to package.appxmanifest
, and replace the contents of main.js
with the following code:
Windows.UI.WebUI.WebUIApplication.onactivated = Application_Activate_FilePicker;
function Application_Activate_FilePicker(eventDetails)
{
var sender = eventDetails.target;
var activationEventArguments = eventDetails.detail[0];
var user = activationEventArguments.user;
var openPicker = BuildFileOpenPickerForUser(user);
openPicker.pickSingleFileAsync().then(BindFunctionToContextOnce(Application_Activate_FilePicker_FilePicked, user), BindFunctionToContextOnce(Application_Activate_FilePicker_FilePickerFailed, user));
}
function Application_Activate_FilePicker_FilePicked(file)
{
var user = this;
if (file !== null)
{
window.setTimeout(BindFunctionToContextOnce(Application_Activate_FilePicker_DisplayFile, file), 1000);
}
else
{
window.setTimeout(Application_Activate_FilePicker_DisplayAbsence, 1000);
}
}
function Application_Activate_FilePicker_DisplayFile()
{
var file = this;
new Windows.UI.Popups.MessageDialog("A file was selected: " + file.name, "File Selected").showAsync();
}
function Application_Activate_FilePicker_DisplayAbsence()
{
new Windows.UI.Popups.MessageDialog("No file was selected.", "No File Selected").showAsync();
}
function Application_Activate_FilePicker_FilePickerFailed(problem)
{
var user = this;
if (problem.number === -2147023496 /* ~~0x80070578 */)
{
if (user !== null)
{
var downloadsFolderClass = Windows.Storage.DownloadsFolder;
if (downloadsFolderClass.createFolderForUserAsync !== void(0))
{
downloadsFolderClass.createFolderForUserAsync(user, "TemporaryFolder", Windows.Storage.CreationCollisionOption.failIfExists).then(BindFunctionToContextOnce(Application_Activate_FilePicker_TemporaryFolderCreated, user), BindFunctionToContextOnce(Application_Activate_FilePicker_TemporaryFolderFailed, user));
}
}
}
}
function Application_Activate_FilePicker_TemporaryFolderCreated(storageFolder)
{
var user = this;
storageFolder.deleteAsync(Windows.Storage.StorageDeleteOption.permanentDelete).then(BindFunctionToContextOnce(Application_Activate_FilePicker_TemporaryFolderDeleted, user), BindFunctionToContextOnce(Application_Activate_FilePicker_TemporaryFolderFailed, user));
}
function Application_Activate_FilePicker_TemporaryFolderDeleted()
{
var user = this;
Application_Activate_FilePicker_TemporaryFolderFailed.call(user, null);
}
function Application_Activate_FilePicker_TemporaryFolderFailed(problem)
{
var user = this;
var openPicker = BuildFileOpenPickerForUser(user);
openPicker.pickSingleFileAsync().then(BindFunctionToContextOnce(Application_Activate_FilePicker_FilePicked, user), BindFunctionToContextOnce(Application_Activate_FilePicker_FilePickerFailed, user));
}
function BindFunctionToContextOnce(targetFunction, targetObject)
{
return function BindFunctionToContextOnce_Callback()
{
var localFunction = targetFunction;
var localTarget = targetObject;
targetFunction = null;
targetObject = null;
return localFunction.apply(localTarget, arguments);
};
}
function BuildFileOpenPickerForUser(user)
{
var fileOpenPickerClass = Windows.Storage.Pickers.FileOpenPicker;
var openPicker;
if ((user !== null) && (fileOpenPickerClass.createForUser !== void(0)))
{
openPicker = fileOpenPickerClass.createForUser(user);
}
else
{
openPicker = new fileOpenPickerClass();
}
openPicker.settingsIdentifier = "Specimen";
openPicker.fileTypeFilter.replaceAll(["*"]);
openPicker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.downloads;
return openPicker;
}
Update (9 July 2022): Based on additional research from @Clément Micard (C#, minimum build 19041 and target build 19041), we now recommend calling Windows.Storage.StorageFolder.GetFolderFromPathForUserAsync
with any path, but we suggest acting in good faith and specifying a valid path (like your application's local temporary folder Windows.Storage.ApplicationData.Current.TemporaryFolder.Path
). Ignore the result and then try your file picker operation. I can confirm this strategy is also functional for UWP JavaScript, and has the nice benefit of no disk writes and leftover folders. For UWP sandboxed applications, you do not need the broadFileSystemAccess
capability to make use of this strategy; just be sure to catch the access denied exception (error code 0x80070005
).