In this chapter, you’ll learn how Windows Store apps access storage files and folders. We’ll start by exploring how apps can access read-only resources such as images, music, and videos embedded in their package files. Then we’ll show how your app can access its own private, per-user data folders to store package-specific data. Of course, apps can access files in many other storage locations, such as the user’s documents and pictures libraries, removable media, and network-shared folders. For security reasons, accessing some of these locations requires either user interaction or that you, as a developer, enable settings in your app package’s manifest file.
This chapter focuses on navigating through files and folders, obtaining their properties and thumbnail images, and performing rich file queries. But this chapter does not address how to read and write a file’s contents; this is discussed in Chapter 6.
Figure 5-1 shows the relationship between the main WinRT interfaces and classes you need to understand to work effectively with storage files and folders. This object model scares many developers when they first see it because it is much more complex than what is available in other developer platforms. However, I personally love this object model because it is well segmented and compartmentalized; all members were carefully placed, and new features not available on other platforms are prominently exposed. Note that I am excluding some less important interfaces from Figure 5-1 to simplify the discussion.
Let’s now walk through the object model to understand it. The IStorageItem
interface is the core of the model. This interface exposes members that operate on both files and folders. For example, you can rename and delete both files and folders. They also share several properties, such as Name, Path, DateCreated
, and Attributes
.
IStorageFolder
inherits from IStorageItem
and adds members that are specific to folders; StorageFolder
is the concrete class implementing these two interfaces’ members. Similarly, IStorageFile
also inherits from IStorageItem
and adds members that are specific to files; StorageFile
is the concrete class implementing these two interfaces’ members.
The IStorageItemProperties
interface defines members that expose a storage item’s properties, such as thumbnail image, display name, and display type. Because files and folders both have properties, both the StorageFolder
and StorageFile
concrete types implement this interface. We’ll discuss how your app can work with properties in this chapter’s Storage item properties section.
Finally, as we mentioned in the introduction, WinRT exposes a rich set of operations for querying files and folders. The IStorageFolderQueryOperations
interface exposes this functionality. Because you’ll initiate a query via some root folder, only the StorageFolder
class implements this interface; StorageFile
does not.
A StorageFile
object represents an actual file on disk. However, a StorageFolder
object does not necessarily represent a folder on a disk. A StorageFolder
object can also refer to a virtual folder, such as the user’s pictures library. A library is a virtual folder, and its contents come from many subdirectories spread across fixed disks or disks attached to different machines on the network. When a StorageFolder
object refers to a virtual folder, its Path
property will be an empty string (“”). In WinRT, virtual folders are first-class citizens in the storage object model. This enables some very rich and powerful scenarios, such as querying and filtering, as shown in this chapter’s Performing file and folder queries section.
As you saw in Chapter 1 all WinRT APIs that perform I/O operations are exposed as XxxAsync
methods. Making I/O APIs asynchronous ensures that they don’t block your app’s UI thread, allowing the thread to continue processing user input so that it remains responsive to the user.
To simplify Figure 5-10, all the static methods defined by the StorageFolder
and StorageFile
concrete classes are not shown. However, you should be aware that these methods do exist and they are all factory methods that simply return IAsyncOperation
<StorageFile>
or IAsyncOperation
<StorageFolder>
objects.
As your app runs, it can access various files that are classified as follows:
Read-only package files. are static, read-only files that you include inside your app’s package. Package files are installed once per machine and shared by all users. By default, Windows installs package files under the %ProgramFiles%WindowsAppsPackageFullName directory.[33] Your package’s binary files, WinRT components, and other asset files are all staged (installed) in this directory. When all users on the system uninstall this version of your package, this directory and its contents are permanently removed.
Read-write package files. are for per-user data created by your app (or background tasks) at runtime. Windows manages the per-user package files under the %UserProfile%AppDataLocalPackagesPackageFamilyName directory. This directory has several subdirectories to support local, roaming, and temporary package files that were discussed in Chapter 4. The system itself creates some additional subdirectories to manage system services (such as the background transfer service and Internet cache discussed in Chapter 7) on your package’s behalf. Your app can create whatever files it so desires under the LocalState, RoamingState, and TempState directories. The files are for your package and remain on the user’s machine when the user upgrades to newer versions of your package. The package files are permanently destroyed if the user uninstalls your package.
User files. are considered to be owned and managed by the user, not an app or a package. They typically contain documents, pictures, music, or videos. The user decides where these files are stored, but they usually reside in one of the user’s Documents, Pictures, Music, or Videos libraries. However, they can also reside on network shares or in cloud storage (such as SkyDrive). Your app can access user files but only with user consent. Users explicitly consent via a folder or file picker or implicitly consent by choosing to install your package after being notified that your package has specified a user file capability in its manifest. More details related to consent are presented later in this chapter. Because user files are managed by the user, the files can be accessed by multiple apps and can also be accessed by multiple users. Upgrading or uninstalling a package has no impact on user files. Table 5-1 summarizes the differences between the three different file classifications.
Table 5-1. File-classification differences.
Read-only package files | Read-write package files | User files | |
---|---|---|---|
File accessibility | Read-only | Read/write | Read/write |
Location decided by | Windows | Windows | User |
Stored per-user | No | Yes | User decides |
Accessible by other apps | No | No | Yes |
Destroyed on package uninstall | Yes | Yes | No |
Destroyed on package upgrade | Yes | No | No |
App requires user consent to access | No | No | Picker/capability |
When a user installs a package, the system unzips the entire contents of the package file into a %ProgramFiles%WindowsAppsPackageFullName directory. Of course, you can include any kinds of files you desire in the package. At runtime, your app has read-only access to the contents of this subdirectory.
When debugging an app using Visual Studio, the package directory is under the project’s directory (usually in a bindebug subdirectory). This subdirectory is writable; however, your app should not write to this subdirectory because it will succeed during development and fail when properly installed (staged and registered).
There are two ways you can access a read-only package file: by storage folder or by URI. The following code shows how to obtain a StorageFolder
object representing your package’s directory and then how to get a file in this directory:
// Get StorageFolder object that represents our package's install directory: StorageFolder folder = Windows.ApplicationModel.Package.Current.InstalledLocation; // In our package's directory go to the Assets subdirectory and find the Image.png file: StorageFile file = await folder.GetFileAsync(@"AssetsImage.png");
Once you have a StorageFile
object, you can open it and read its contents. I discuss how to do this in Chapter 6.
Alternatively, you can access the same file using a special URI:
StorageFile file = await StorageFile.GetFileFromApplicationUriAsync( new Uri("ms-appx:///Assets/Image.png"));
This Microsoft-specific URI scheme (ms-appx) tells Windows you want to access a file that is included in your app’s package. Note the three slashes after the colon. The third slash indicates the omission of the package’s name. Therefore, “ms-appx:///Assets/Image.png” is equivalent to
"ms-appx://" + Windows.ApplicationModel.Package.Current.Id.Name + "/Assets/Image.png"
Using the URI technique is very useful because there are occasions when a URI is mandatory—for example, setting the source attribute of a XAML Image control. In addition, when you use a URI, you are tapping into the Windows resource system. This means that Windows will search directories looking for the file based on the user’s culture, contrast settings, and display DPI information.
Let’s briefly take DPI as an example. When you have a file Image.png, you can provide scaled versions of this asset for 100, 140, and 180% DPI, by tagging it with .scale
followed by the percentage—for example, Image.scale-140.png
. If you don’t provide these explicit versions, Windows will scale the 100% version up, which will inevitably lead to blurry or jagged images. By providing explicit files for each DPI setting, you can make sure that the image looks good on all devices. Similar schemes exist for contrast (for example, contrast-high
), language (for example, lang-nl-NL
), layout direction (for example, layoutdir-RTL
), and so on. You can concatenate those resource qualifiers on the specific resources like this: Image.scale-140_contrast-high.png
. See http://msdn.microsoft.com/en-us/library/windows/apps/xaml/Hh965324(v=win.10).aspx for more information.
When a user installs a package, the system creates a subdirectory where the app can create and manage some per-user files. The directory can be found here: %UserProfile%AppDataLocalackagesPackageFamilyName. Because the PackageFamilyName does not include a version number, this directory is used by all versions of a particular package.[34] This directory contains three subdirectories: LocalState, RoamingState, and TempState; these are described in Chapter 4. In these subdirectories, your app can create additional subdirectories, up to 32 levels deep if desired.
The contents of the TempState subdirectory can be destroyed by the system at any time. By default, the system cleans it out once per week by using a scheduled task. A user can modify the frequency of this using the Windows Task Scheduler.[35] The scheduled task is called CleanupTemporaryState, and it can be found under Microsoft > Windows > ApplicationData.
There are two ways you can access per-user package files: by storage folder or by URI. To get a StorageFolder
, you first get your package’s current ApplicationData
object and then you query its LocalFolder, RoamingFolder
, or TemporaryFolder
property. You can use the URI technique (with a URI scheme of ms-appdata) to get a StorageFile
object for a file that was previously created using the StorageFolder
technique. The following code demonstrates how to create a file in each folder and subsequently get a reference to the file using the URI technique. The code also shows the various flags exposed by CreationCollisionOption
to handle potential file-name conflicts.
StorageFolder folder; StorageFile file; // Create and then access a local package file: folder = ApplicationData.Current.LocalFolder; file = await folder.CreateFileAsync("LocalFile.txt", CreationCollisionOption.ReplaceExisting); file = await StorageFile.GetFileFromApplicationUriAsync( new Uri("ms-appdata:///local/LocalFile.txt")); // Create and then access a roaming package file: folder = ApplicationData.Current.RoamingFolder; file = await folder.CreateFileAsync("RoamingFile.txt", CreationCollisionOption.GenerateUniqueName); file = await StorageFile.GetFileFromApplicationUriAsync( new Uri("ms-appdata:///roaming/RoamingFile.txt")); // Create and then access a temporary package file: folder = ApplicationData.Current.TemporaryFolder; file = await folder.CreateFileAsync("TemporaryFile.txt", CreationCollisionOption.OpenIfExists); file = await StorageFile.GetFileFromApplicationUriAsync( new Uri("ms-appdata:///temp/TemporaryFile.txt")); // The line below deletes all read-write package files: await ApplicationData.Current.ClearAsync();
In this chapter’s Performing file and folder queries section, we’ll show how to execute queries over files and folders. However, for this to work, Windows must know which folders contain files to index. Windows Search does not index the contents of your package’s folders; see http://msdn.microsoft.com/en-us/library/windows/desktop/bb266513.aspx for details. However, if you create a folder called “Indexed” in your package’s local folder, Windows Search will index the contents of this folder.[36] Searches the user performs using File Explorer will not show results that include the contents of the folder unless the File Explorer is positioned at the root of this folder: %UserProfile%AppDataLocalPackagesPackageFamilyNameLocalStateIndexed. Also, you can check whether the contents of this folder have been indexed by Windows Search by opening the Windows desktop Control Panel and then opening the Indexing Options dialog box. If it says “Indexing complete” at the top, you know the contents of this directory have all been indexed.
Of course, the reason to do this is so that your app can perform programmatic queries against the indexed files. The following code demonstrates how to create the “Indexed” folder, add some text files to it, and then perform a query against the folder:
// Create the local package folder's "Indexed" subdirectory: StorageFolder localFolder = ApplicationData.Current.LocalFolder; StorageFolder indexedSubdir = await localFolder.CreateFolderAsync("Indexed"); // Create two text files in the "Indexed" subdirectory: await FileIO.WriteTextAsync(await indexedSubdir.CreateFileAsync("File1.txt"), "abc"); await FileIO.WriteTextAsync(await indexedSubdir.CreateFileAsync("File2.txt"), "abcd"); // Create a query that looks for .txt files containing "abcd" var qo = new QueryOptions(CommonFileQuery.DefaultQuery, new[] { ".txt" }) { ApplicationSearchFilter = "abcd", IndexerOption = IndexerOption.OnlyUseIndexer, // Indexed files only! FolderDepth = FolderDepth.Deep // Search subdirectories too }; StorageFileQueryResult searchResults = localFolder.CreateFileQueryWithOptions(qo); // Perform the query and get the results IReadOnlyList<StorageFile> result = await searchResults.GetFilesAsync(); // 'result' contains a single StorageFile object referring to File2.txt
Users typically have files they care deeply about in various folders, such as the Documents, Pictures, Music, and Videos libraries, network shares, and so on. Windows desktop apps have always been able to access the user’s files and folders arbitrarily. This allows desktop apps to traverse the user’s folders and modify any files it finds there. Or, a desktop app might upload those files to a web server somewhere on the Internet. Clearly, this is not an ideal situation and it has caused users to be scared to use Windows desktop applications. To address users’ valid concerns, a Windows Store app can access only its own package files without user consent.
If your app wishes to manipulate a user file, your app can present the user with a folder or file picker. (See Figure 5-2.) The picker allows the user to navigate over her own folders and files securely. The user can then select a single folder, single file, or set of files, thereby granting your app access to the selected item or items only. In addition, the user can cancel the picker, thereby granting your app no access to anything. The key point here is that the user is in control of her files, not your app.
In reality, the user is granting your package access to the folder or files. So your app can prompt the user for consent to allow a background task (that is part of the same package) access to the folder or files.
Let’s examine the UI for the file-open picker shown in Figure 5-2. This file-open picker dialog has a single-select mode and a multiple-select mode. Figure 5-2 shows the picker in multiple-select mode. As the user selects files from the UI, the files are placed in what’s called the basket, which appears at the bottom of the screen. As soon as any file is in the basket, the file-open picker enables the Open button at the lower-right corner. The user can remove files from the basket by tapping the items in the basket or deselecting items in the folder’s list. Some apps implement the FileOpenPicker, FileSavePicker, and CachedFileUpdater declarations in their manifest to provide the user a custom file open/save UI experience. For example, Microsoft’s SkyDrive Windows Store app does this, allowing users to download files from cloud storage. The resulting file is then returned back to the hosting Windows Store app. A hosted view (as discussed in Chapter 3) is not allowed to show a picker; attempting to do so throws an exception. The reason why Windows forces this limitation is because pickers are hosted themselves and it would be confusing to end users to have hosted views nest other hosted views.
The picker classes are defined in the Windows.Storage.Pickers
namespace. Here is code prompting the user to select a folder via the FolderPicker
:
var fop = new FolderPicker { FileTypeFilter = {"*"} }; var folder = await fop.PickSingleFolderAsync(); if (folder == null) return; // User canceled the picker // folder refers to the user-selected StorageFolder object
There are three types of pickers: FolderPicker, FileOpenPicker
, and FileSavePicker
. Table 5-2 lists them with their properties.
Table 5-2. Pickers and their properties.
For the folder and file-open picker, only the FileTypeFilter
property is mandatory. This property is a collection of strings telling the picker which file types to show. Your app can use the wildcard (“*”) string to show all files to the user. All the other properties are self-explanatory except for one, SettingsIdentifier
. Your app can set the SettingsIdentifier
property to any string value. When your app shows a picker for the first time, the picker will navigate to the folder specified by the SuggestedStartLocation
. However, the user can use the picker to navigate to a different folder. When the user subsequently picks a location or file and closes the picker, the system saves this last-selected location. If, in the future, your app brings up a picker with the same SettingsIdentifier
value, the picker remembers the user’s last location and navigates to it directly, thereby overriding the SuggestedStartLocation
. This is the reason why the name of the property is SuggestedStartLocation
; it is a suggested location and not a demand.[37]
A FolderPicker
returns a StorageFolder
object representing a folder that the user is allowing your app to use. With this StorageFolder
object, your app can access any files in this folder and any child items such as subfolders and files in any subfolders. This grants your app a lot of power; use it wisely. The folder picker UI does not give any indication to the user that your app gets access to the folder and its complete contents.
When the user selects a file via a picker, the system returns a StorageFile
object, allowing your app access to the file. But the user could switch away from your app, causing the system to suspend it and possibly even terminate it. (See Chapter 3 for reasons why.) When the user switches back to your app, the system launches it again and your app is supposed to act as if it were running the whole time. However, when Windows terminated your app, the StorageFile
object got destroyed and, now, your app can no longer access the user-selected file. Your app could present the user with another picker and have her grant consent to your app again, but this would be a horrible user experience. What we need is a mechanism that allows your package to remember the StorageFile
and StorageFolder
objects that the user granted your app’s package access to.
Fortunately, this mechanism does exist, and it’s called the FutureAccessList
. Each package has a single FutureAccessList
property exposed by the static type StorageApplicationPermissions
, and it’s a simple dictionary of key/value pairs. The keys are String
s, and we call them tokens. The values contain an IStorageItem
and a String
, which we call metadata. The metadata string is optional; if you don’t specify it, the empty string (“”) is used. When adding an IStorageItem
object to the FutureAccessList
, you can specify a specific key string, but you don’t have to. If you omit a key (token), a GUID will be generated and used as the key.
The following line adds an entry to the FutureAccessList
. The key is “FileWeWereUsing”, and the value is some StorageFile
object as well as some metadata String
:[38]
StorageApplicationPermissions.FutureAccessList.AddOrReplace( "FileWeWereUsing", storageFile, "SomeMetadata");
If your app terminates and launches again, you regain access to the file by doing this:
StorageFile file = await StorageApplicationPermissions.FutureAccessList.GetFileAsync("FileWeWereUsing");
Getting the metadata string back (if you need it) is a bit trickier:
String metadata = StorageApplicationPermissions.FutureAccessList.Entries .First(e => e.Token == "FileWeWereUsing").Metadata;
The FutureAccessList
can hold up to 1,000 storage items, and your code controls addition and removal. The StorageApplicationPermissions
static class also offers a MostRecentlyUsedList
property. The MostRecentlyUsedList
works exactly like the FutureAccessList
, except it holds up to 25 storage items and Windows manages them automatically for you. That is, when you add a new storage item and the list has reached its limit of 25, the oldest item is automatically removed. Moreover, the system sorts the items in the MostRecentlyUsedList
; thus, if you enumerate the storage items, the items are returned in most-recently-used order. Finally, because both lists implement the IStorageItemAccessList
interface, their APIs are identical. The interface looks like this:
public interface IStorageItemAccessList { UInt32 MaximumItemsAllowed { get; } // Returns 1000 or 25 void Clear(); // Erases all entries in the collection String Add(IStorageItem file, string metadata); // Returns token (GUID) void AddOrReplace(String token, IStorageItem file, String metadata); void Remove(String token); Boolean ContainsItem(String token); IAsyncOperation<IStorageItem> GetItemAsync(String token); IAsyncOperation<StorageFolder> GetFolderAsync(String token); IAsyncOperation<StorageFile> GetFileAsync(String token); AccessListEntryView Entries { get; } // Returns collection of token/metadata pairs // Some members not shown here }
One of the really cool features of these lists is that they automatically track changes to the storage item. Specifically, if the user uses another application (for example, File Explorer or cmd.exe) to rename or move the item to another subdirectory on the same disk volume, your app will still be able to access the item with its new name or location after extracting it from a list.[39]
When your app accesses files or folders with the pickers or one of the IStorageItemAccessList
classes, WinRT forwards the storage APIs your app calls to another process called a Broker (RuntimeBroker.exe). The broker process is a very important part of the Windows security model because it ensures that your app is allowed to access only the files and folders that the user consents to. Because the WinRT file APIs now have to make an additional cross-process call through the broker instead of going directly to the file system, you need to be aware that performance might suffer in order to provide users with the additional security of restricting apps’ access to the file system.
When a user double-clicks a text file in File Explorer or opens a text file from within an email application, typically Notepad.exe starts up and opens the document. This happens because Windows has associated Notepad.exe with the file extension .txt. Similarly, you can associate file extensions with your own Windows Store app. Your app can define its own file extension, or it can use an already-existing extension such as .txt. When a user opens a file, Windows checks to see how many apps have registered for the file’s file extension. If multiple apps have registered for the file extension, Windows presents the user with a list of these apps. The user can then decide which app to use and indicate if this app should be the default in the future. (See the dialog box shown in Figure 5-3.)
The user can also view and edit all the file-type associations in the system by selecting Settings charm > Change PC Settings > Search & Apps > Defaults > Choose Default Apps By File Type. This displays the pane shown in Figure 5-4.
For a Windows Store app, launching a file is similar to using a file-open picker because the user is trying to open a file and the user is in control of which app should be used to open the file. When an app is installed, Windows needs to know which file types your app supports so that it can activate your app when the user opens a supported file type. You declare file-type associations for each file type you want your app to support in your package’s manifest. (See Figure 5-5.)
When declaring support for file-type associations, there are many file types that are forbidden, including .accountpicture-ms, .appx, .application, .appref-ms, .bat, .cer, .chm, .cmd, .com, .cpl, .crt, .dll, .drv, .exe, .fon, .gadget, .hlp, .hta, .inf, .ins, .jse, .lnk, .msi, .msp, .ocx, .pif, .ps1, .reg, .scf, .scr, .shb, .shs, .sys, .ttf, .url, .vbe, .vbs, .ws, .wsc, .wsf, and .wsh. The most current list can be found at http://msdn.microsoft.com/en-us/library/windows/apps/hh779669.aspx.
Table 5-3 describes the various properties.
Once you’ve built your package and deployed it on a machine, Windows will know about its file-type associations. You can verify this using File Explorer to see your manifest values appear as in Figure 5-6.
Table 5-3. File-type association properties and their descriptions.
Required | Description | |
---|---|---|
Name | ✓ | Unique name (lowercase) for all associations (Content type/File type) sharing DisplayName, Logo, Info Tip, and Edit Flags |
Display name | ✕ | String shown in File Explorer’s Type column |
Logo | ✕ | Icon shown in File Explorer (example: “AssetsIcon.png”) You should define four icons named like this: Icon.targetsize-[16 | 32 | 48 | 256].png File Explorer picks the target size based on List, Medium, Large, and Extra Large views |
Info tip | ✕ | Tooltip text when hovering in File Explorer |
Edit flags | ✕ | Open is safe: indicates the file can do no harm to the system (.txt file; not an .exe file). Always unsafe: indicates that the file type should never be trusted (.exe file). You should select neither or one of these options; do not select both. For more info, look up the Win32 FILETYPEATTRIBUTEFLAGS enum type. |
Content type | ✕ | Mime type (example: “Image/jpeg”) |
File type | ✓ | File extension (example: “.jpg”) |
Figure 5-6. File Explorer showing file-type information for a .jeff file obtained from the app’s manifest.
When the user launches your Windows Store app via a file-type association, Windows calls your app’s OnFileActivated
override instead of your app’s OnLaunched
method. In this OnFileActivated
method, Windows passes a FileActivatedEventArgs
object:
public sealed class FileActivatedEventArgs : IActivatedEventArgs, IApplicationViewActivatedEventArgs { // Members specific to FileActivatedEventArgs objects: public String Verb { get; } public IReadOnlyList<IStorageItem> Files { get; } public StorageFileQueryResult NeighboringFilesQuery { get; } // IActivatedEventArgs members: public ActivationKind Kind { get; } public ApplicationExecutionState PreviousExecutionState { get; } public SplashScreen SplashScreen { get; } // IApplicationViewActivatedEventArgs member: public Int32 CurrentlyShownApplicationViewId { get; } }
The Verb
property enables your app to handle different operations on the file, such as Open and Edit. The Files
property contains the set of files the user selected when she launched your app. The following code shows the verb and the first file the system passes to your app:
protected override async void OnFileActivated(FileActivatedEventArgs args) { IStorageItem firstFile = args.Files[0]; await new MessageDialog( String.Format("Activated to '{0}' the '{1}' file. Path='{2}'", args.Verb, firstFile.Name, firstFile.Path)).ShowAsync(); }
Sometimes, when launching a file, the launched app would like to process the neighboring files too. For example, the Windows Mail app allows the user to tap on a picture attachment, which launches a photo viewer app. If the mail message has multiple photos in it, the user could go back to the message and tap on each photo attachment individually to look at them all. But this is rather inconvenient. It would be better if the user could tap on one of the photo attachments, launch a photo viewer app, and then navigate through all the attachment’s photos. To enable this kind of scenario, the Mail app would first download all of a message’s attachments into a folder and then launch one of the files, thereby activating the photo viewer app. The photo viewer app would query FileActivatedEventArgs
’s NeighboringFilesQuery
property. If this property is not null
, it refers to a StorageFileQueryResult
object that is scoped to the folder containing the file that launched it. The photo viewer app can now call StorageFileQueryResult
’s GetFilesAsync
method to access the other files in the same folder.
A StorageFileQueryResult
object is scoped to whatever launched it. For example, if the user launches a file from her desktop, the StorageFileQueryResult
object is scoped to the user’s desktop. If the user was in File Explorer and did some complex search query and then launched a file, the StorageFileQueryResult
object is scoped to File Explorer’s search results. Furthermore, the StorageFileQueryResult
object is scoped to the same kinds of files, which must be pictures, music, or videos. That is, if the user launches a .jpg file, the StorageFileQueryResult
object will return only other image files (like .gif, .png, .tif, and so on). Your app does not need to declare file-type associations for all these file types. One last thing, if the user launches an app, passing it multiple storage items, then FileActivatedEventArgs
’s NeighboringFilesQuery
property will always return null
.
So far, we’ve been talking about how your app gets activated because of a file-type association. Now, we’ll discuss how an app can activate another app’s file-type association. Here is code allowing the user to select a file and then open the file by launching its corresponding app:
StorageFile file = new FileOpenPicker { FileTypeFilter = { ".txt" } }.PickSingleFileAsync(); Boolean launched = await Launcher.LaunchFileAsync(file);
For Windows desktop apps, the ability to launch a file has created many security-related issues for Windows. For example, mail attachments that run .exe files can install viruses or do other harm to the user’s PC. To greatly improve the security of a user’s PC, Windows Store apps are greatly restricted as to what files they can launch. For example, Launcher
’s methods prevent launching a desktop app (which runs less restricted than a Windows Store app) with files that could execute code, such as .asp, .aspx, .bat, .cmd, .com, .dll, .exe, .inf, .jar, .js, .mdb, ,msi, .pl, .vb, .vbs, .wsf, .vsi, and so forth.
In addition, you’ll notice that LaunchFileAsync
returns a Boolean
(true
if the launch is successful, and false
if the launch failed). If the launch fails, the system exposes no way to find out the reason why. No additional information is given because Microsoft doesn’t want malicious apps to learn more about the failure in order to attempt to work around it. Also, calling LaunchFileAsync
throws an exception if it’s not called from a UI thread or if it’s called from a UI thread whose window is not active. This prevents apps from popping up and activating themselves at arbitrary times, disturbing the user’s workflow, and from capturing user input (like passwords). And finally, there is an overload of the LaunchFileAsync
method that accepts a LauncherOptions
object. Your app can create one of these and set options that force the user to select the app to be launched for a certain file type or always display a warning to the user that the file being launched is potentially unsafe.
It’s also possible to launch an app to access a file via a URI. This is useful for apps that can access files directly from an Internet location. This feature is called direct invoke, and it requires that you associate a content type with your file-type association. For more about content types, see the IANA website here: http://www.iana.org/assignments/media-types. Because our file-type association declared a content type of “application/jeff”, another app can launch our app via a URI as follows:
Uri uri = new Uri("http://Wintellect.com/SomeFile"); // URI to file on Internet LauncherOptions options = new LauncherOptions { ContentType = "application/jeff" }; Boolean ok = await Launcher.LaunchUriAsync(uri, options);
Launching an app this way also causes its OnFileActivated
method to be called; the URI can be found in the StorageFile
’s FolderRelativeId
property.
Developers frequently ask how their Windows Store app can launch another app. Because of security concerns, there is no direct way for one app to launch another. However, Windows Store apps can be activated if they declare file-type associations (as just discussed) or if they declare URI protocols. Both of these techniques are indirect; that is, an app launches a file or a URI protocol, but the user controls which app actually starts running in response to this. The user views and edits all the URI protocol associations in the system by selecting Settings charm > Change PC Settings > Search & Apps > Defaults > Default Apps By Protocol. There is no way for a Windows Store app to directly launch another app. Again, this is by design for security reasons.
Like file-type associations, URI protocols are declared via the app’s manifest and then the app should override Windows.UI.Xaml.Application
’s OnActivated
method to handle the activation.
For example, the Bing maps app has declared support for the “bingmaps” URI protocol, and this allows another app to launch the Bing maps app with the following code:
// See http://msdn.microsoft.com/en-us/library/windows/apps/jj635237.aspx for Maps URI scheme var uri = new Uri("bingmaps:?where=1600%20Pennsylvania%20Ave,%20Washington,%20DC"); await Launcher.LaunchUriAsync(uri);
The IStorageItem
interface offers properties applicable to both files and folders. These properties are Name, Path, DateCreated
, and Attributes
(Normal, ReadOnly, Directory, Archive, Temporary
, and LocallyIncomplete
[40]). In addition, both the StorageFile
and StorageFolder
classes implement the IStorageItemProperties
interface, which defines the DisplayName, DisplayType
, and FolderRelativeId
properties. And the IStorageFile
interface offers some file-specific properties: FileType
and ContentType
. Table 5-4 shows all these properties and an example of what they look like.
Table 5-4. Various properties available on a StorageFile
object.
The IStorageItem
interface also defines a GetBasicPropertiesAsync
method that ultimately returns a BasicProperties
object exposing some other properties common to both files and olders:
public sealed class BasicProperties : IStorageItemExtraProperties { // Gets the timestamp of the last time the file was modified. public DateTimeOffset DateModified { get; } // Gets the most relevant date for the item. // For a photo, date taken. For a song, date released. public DateTimeOffset ItemDate { get; } // Gets the size of the file. public UInt64 Size { get; } }
You can also think of a thumbnail image as being a property, and your app can obtain this property by querying IStorageItemProperties’ GetThumbnailAsync
method. However, this method should no longer be used; instead, both StorageFile
and StorageFolder
implement the new IStorageItemProperties2
interface, which defines a GetScaledImageAsThumbnailAsync
method. This method can return thumbnail images of any size or cropped to meet your app’s needs. The method scans for the thumbnail you desire from the PC’s local cache first. If the thumbnail is not found, the method checks the file itself for an embedded thumbnail. Finally, if the file is available only in the user’s SkyDrive, the method makes a request to the SkyDrive service to have the service produce and download a thumbnail image. The great thing here is that the file itself (which could be huge in the case of a 20-MB image) does not get downloaded in order to get and show the user a thumbnail for it; this reduces bandwidth usage, reduces local disk consumption, and improves performance. Of course, if the SkyDrive service can’t be reached, GetScaledImageAsThumbnailAsync
returns null
.
However, we have just discussed the very tip of the property-system iceberg. The Windows property system is enormous and incredibly rich. Windows actually captures many properties related to specific data files and stores them in a database on your hard disk. This database is called the content indexer, and I discuss some other features it offers in the Searching over a stream’s content section in Chapter 6. This allows your app to query (and modify) a phenomenal set of properties. The IStorageItemProperties
interface offers a Properties
property that returns a StorageItemContentProperties
object. The class looks like this:
public sealed class StorageItemContentProperties : IStorageItemExtraProperties { public IAsyncOperation<IDictionary<String, Object>> RetrievePropertiesAsync( IEnumerable<String> propertiesToRetrieve); public IAsyncAction SavePropertiesAsync(); public IAsyncAction SavePropertiesAsync( IEnumerable<KeyValuePair<String, Object>> propertiesToSave); // Convenience methods that internally call RetrievePropertiesAsync to // get commonly used properties for commonly used file types public IAsyncOperation<DocumentProperties> GetDocumentPropertiesAsync(); public IAsyncOperation<ImageProperties> GetImagePropertiesAsync(); public IAsyncOperation<MusicProperties> GetMusicPropertiesAsync(); public IAsyncOperation<VideoProperties> GetVideoPropertiesAsync(); }
When calling RetrievePropertiesAsync
, you must pass it a collection of strings identifying the properties you wish to obtain. Go to http://msdn.microsoft.com/en-us/library/dd561977(VS.85).aspx to see the enormous list of all possible strings. To get strings for some common properties, see the static Windows.Storage.SystemProperties
class. The following code shows an example calling this method:
IDictionary<String, Object> props = await storageFile.Properties.RetrievePropertiesAsync( new String[] { "System.FileAttributes", "System.DateModified", "System.Size", SystemProperties.ItemNameDisplay });
You can retrieve literally hundreds of predefined properties, ranging from the straightforward FileOwner
to the more esoteric System.ComputerName
or even System.FreeSpace
(free space on the system disk).
To simplify your code when obtaining commonly used properties for common file types, the StorageItemContentProperties
class offers the four GetXxxPropertiesAsync
methods to easily obtain properties commonly used when apps work with documents, images, music, or videos. Table 5-5 shows the properties returned for a specific file type.
Table 5-5. Commonly used properties for document, image, music, and video files.
File type | Properties |
---|---|
Document | Author, Comments, Keywords, Title |
Image | CameraManufacturer, CameraModel, DateTaken, Height, Keywords, Latitude, Longitude, Orientation, PeopleNames, Rating, Title, Width |
Music | Album, AlbumArtist, Artist, Bitrate, Composers, Conductors, Duration, Genre, Producers, Publisher, Rating, Subtitle, Title, TrackNumber, Writers, Year |
Video | Bitrate, Directors, Duration, Height, Keywords, Latitude, Longitude, Orientation, Producers, Publisher, Rating, Subtitle, Title, Width, Writers, Year |
So far, we’ve discussed accessing user data through file pickers and file-type associations. In both scenarios, the end user explicitly gives your app access to a file or folder. But what about a photo viewer app that allows the user to browse all his pictures? Or a music player that shows the user’s songs organized by artists or albums? Or an app that indexes all the user’s documents, allowing him to search through them? Your app could certainly use pickers for these scenarios. However, that would force the user to choose the folders containing his pictures or music files before he could use the app. Moreover, the user already has a dedicated virtual location for media files and documents in libraries that we could use. (See the side note if you’re unfamiliar with libraries.)
Windows Store apps are forbidden from accessing a user’s files unless the user allows the app to do it. For an app to traverse the contents of a library, the app’s package manifest must first specify in the Capabilities section which libraries the package wants access to. Figure 5-7 shows the capabilities related to accessing a user’s files. Selecting a library capability in the manifest grants your package bulk access to a large set of the user’s files. Your package’s app must be diligent here and not abuse this privilege. In fact, packages that use library capabilities are scrutinized much more stringently than packages that use pickers when submitted for Windows Store certification. If you can implement your app using pickers instead of library capabilities, you should. You should specify library capabilities only when your package absolutely requires programmatic access to the user’s files.
Whereas the Pictures library contains pictures, the Music library contains music, and the Videos library contains videos, the Documents library contains all kinds of files on behalf of the user. My personal Documents library contains Microsoft Excel files, PowerPoint files, Word files, PDF files, Microsoft Money files, C# source code files, and the list goes on and on. Having access to all these files opens up security issues where private user files could too easily be accessed and even uploaded to servers somewhere on the Internet. For this reason, packages are strongly discouraged from enabling the Documents Library capability in the manifest. In fact, as you can see in Figure 5-7, Visual Studio doesn’t even show “Documents Library” in the list of Capabilities. If you really want to use this capability, you must manually add it to the manifest’s XML file.
In addition, packages that specify the Documents Library capability will not pass Windows Store certification if submitted using an individual account. Only company accounts (which are verified) can submit packages that specify the Documents Library capability. Furthermore, a package that specifies the Documents Library capability must also specify one or more file-type associations. This gives the app access to only the specified file types within the library. From a security perspective, having apps declare a file-type association is the equivalent of having a virtual documents library like “My Excel Files,” which the user can feel more comfortable granting access to.
Then, when the user goes to the Store app to install the package, the Store app shows the user what capabilities the package requires. By installing the package, the user is implicitly granting the package access to the specified capabilities. Figure 5-8 shows how the Store app shows a package’s required capabilities to a user before the user installs the package.
After a user installs a package, she can always see what capabilities the package needs by running the package’s app, opening the Settings charm, and then selecting Permissions, as you can see in Figure 5-9.
If a package specifies the Music Library capability, its app can easily access all the folders in this library with a single line of code:
IReadOnlyList<StorageFolder> folders = await KnownFolders.MusicLibrary.GetFoldersAsync();
If your package does not have the required capability, the system throws an “access denied” exception.
In the line just shown, you’ll notice that we use the KnownFolders
class. This class exposes several StorageFolder
objects:
public static class KnownFolders { public static StorageFolder CameraRoll { get; } // For Windows Phone only // The main library folders: public static StorageFolder PicturesLibrary { get; } // User's Pictures library public static StorageFolder SavedPictures { get; } // = PicturesLibrary (for Phone) public static StorageFolder MusicLibrary { get; } // User's Music library public static StorageFolder Playlists { get; } // Music library's play list folder public static StorageFolder VideosLibrary { get; } // User's Video library public static StorageFolder DocumentsLibrary { get; } // User's Documents library (avoid) // Allows Picture, Music, Video library access on user's Home Group public static StorageFolder HomeGroup { get; } // Allows Picture, Music, Video library access on removable devices: public static StorageFolder RemovableDevices { get; } // The folder of media server (Digital Living Network Alliance [DLNA]) devices. public static StorageFolder MediaServerDevices { get; } }
Windows has a feature called HomeGroup that grants a user easy access to multiple machines in a home environment. Figure 5-10 shows under my name that I have two machines in my HomeGroup: BOSBOX8 and VIRTBOS.
You’ve seen that libraries can include physical directories on other systems to get a consolidated view of media files or documents. But what if a user wants to browse, for example, his wife’s pictures either on the home system or even on her computer? That is what a HomeGroup is for. The KnownFolders
class exposes this too as a virtual folder. When a user first joins a machine to a HomeGroup, the system asks if the machine’s library should be shared. Your app can access these shared HomeGroup libraries by using KnownFolders
’s HomeGroup
property.
A package specifying the HomeGroup capability must also specify one or more of the Pictures Library, Music Library, or Videos Library capabilities. The package will also need the Home Or Work Networking capability, which is not on by default. (See Chapter 7.) For security reasons, it is not possible to get access to a HomeGroup machine’s Documents library.
The following code shows how to enumerate the contents of the virtual HomeGroup folder:
StorageFolder folder = KnownFolders.HomeGroup; foreach (var user in await folder.GetFoldersAsync()) { // Users foreach (var machine in await user.GetFoldersAsync()) { // Machines foreach (var library in await machine.GetFoldersAsync()) { // Libraries // Process a library folder... } } }
The KnownFolders
class also has a MediaServerDevices
property. Like the virtual HomeGroup storage folder, use of this property also requires that one or more of the media library capabilities be specified as well as the Home Or Work Networking capability.
The KnownFolders.RemovableDevices
property is used for removable storage such as USB drives. Accessing the contents on removable media has similar security concerns to that of accessing the contents of the Documents library. That is, the app must also specify at least one file-type association as well as the package’s Removable Storage capability. Just as when you use the Documents library, the system exposes only files on the removable storage device that meet the specified file-type association or associations. The following code shows how to enumerate the contents of the virtual removable devices storage folder:
StorageFolder devices = KnownFolders.RemovableDevices; foreach (var device in await devices.GetFoldersAsync()) { // Drives foreach (var folder in await device.GetItemsAsync()) { // Files or folders // Process a storage item... } }
Windows Store apps can access files on the network through UNC paths too, for example:
StorageFile file = StorageFile.GetFileFromPathAsync(@"\SomeMachineSomeShareSomeFile.txt");
Windows treats shares on the network the same way as it treats KnownFolders.RemovableDevices
; you will need to specify file-type associations to indicate the file types your app works with. Instead of specifying the Removable Storage capability in the manifest, the package must specify the Private Networks and Enterprise Authentication capabilities. The Enterprise Authentication capability allows the app to use the user’s credentials to authenticate on the remote system. Be aware that Enterprise Authentication is a special-use capability requiring that the package be submitted by a company account instead of an individual account.
One final folder to discuss is the user’s Downloads folder. On a PC, each user gets his or her own Downloads folder. When an app first puts something in the user’s Downloads folder, Windows creates a subfolder within the Downloads folder for the app. This subfolder name matches the app’s package family name, followed by an exclamation mark and the application ID (usually “App” as specified in the manifest’s Application element). This keeps one app’s downloaded files separate from another app’s downloaded files. Figure 5-11 shows this subfolder name as viewed from cmd.exe.
In the app’s subfolder, Windows also creates a hidden (and system) Desktop.ini file whose contents are shown in Figure 5-11. The existence of this file causes Windows File Explorer to show the subfolder with a different name (as indicated by the LocalizedResourceName value). So, when the user looks at his Downloads folder with File Explorer, he sees what’s shown in Figure 5-12.
Figure 5-12. The Downloads folder shows a subfolder matching the app’s name due to the Desktop.ini file placed in the subfolder.
Note that Downloads subfolders are not destroyed when packages are uninstalled because the files in these subfolders are considered to be user files, not package or app files. An app creates files and subfolders in its Downloads subfolder by calling methods defined by the DownloadsFolder
class:
public static class DownloadsFolder { public static IAsyncOperation<StorageFile> CreateFileAsync(String desiredName); public static IAsyncOperation<StorageFile> CreateFileAsync(String desiredName, CreationCollisionOption option); public static IAsyncOperation<StorageFolder> CreateFolderAsync(String desiredName); public static IAsyncOperation<StorageFolder> CreateFolderAsync(String desiredName, CreationCollisionOption option); }
The following code creates a text file for the user in the app’s Downloads subfolder:
IStorageFile file = await DownloadsFolder.CreateFileAsync("file.txt"); await FileIO.AppendLinesAsync(file, new [] { "Hello there"});
WinRT offers no API to query the contents of an app’s Downloads folder; therefore, your app must keep track of the folders and files it creates using the FutureAccessList
property discussed earlier in this chapter.
Table 5-6 summarizes the capability and file-type association requirements of the various virtual storage folders.
Table 5-6. Capability and file-type association requirements for virtual storage folders.
Virtual folder | Requires capability | Requires file type assoc. | Notes |
---|---|---|---|
Music/Pictures/Videos Library | ✓ | ✕ | 1+ required for HomeGroup. |
Playlists | ✓ | ✕ | Requires Music Library capability. |
HomeGroup | (special) | ✕ | Requires Music, Pictures, or Videos Library capability. |
DocumentsLibrary | ✓ | ✓ | HomeGroup can’t access this. |
RemovableStorage | ✓ | ✓ | Children are drives. |
MediaServerDevices | (special) | ✕ | Children are media servers. |
DownloadsFolder | ✕ | ✕ | Creates subfolders and files only. |
This chapter has discussed how to work with folders, files, properties, thumbnail images, and libraries. In this section, I show how to quickly search for items within a folder using a query that can filter on multiple properties, such as date, size, rating, or even user-defined strings. You can also specify how you want the query results sorted. Additionally, you can receive notifications when storage items are added or removed.
The following code demonstrates how to perform a query over the user’s Pictures library. The query returns a set of virtual folders, with each folder representing a year.[42] Then, within each folder, pictures taken that year are returned. Let me make something perfectly clear: this works regardless of how the user organizes his pictures in his Pictures library. That is, all the pictures could be at the root of the user’s Pictures library, or they could be in subdirectories organized by person or location. None of this matters; the following code returns the pictures grouped by year:
// Create QueryOptions to filter/sort results; this example groups the results by year QueryOptions qo = new QueryOptions(CommonFolderQuery.GroupByYear) { FolderDepth = FolderDepth.Deep }; // From the user's Pictures library, create a query that returns virtual folders StorageFolderQueryResult folders = KnownFolders.PicturesLibrary .CreateFolderQueryWithOptions(qo); // Process each year's files foreach (StorageFolder folder in await folders.GetFoldersAsync()) { Debug.WriteLine(folder.Name); // Folder name is year, e.g. "2014" foreach (StorageFile file in await folder.GetFilesAsync()) { Debug.WriteLine(" " + file.Name); // Pictures taken in 2014 } }
Wow, this is all there is to it. Let’s talk a little more about the QueryOptions
class. You create and initialize an instance of this class to fully describe the kind of query you wish to perform. Table 5-7 summarizes the various options.
Table 5-7. QueryOptions
constructor parameter and other property options.
Kind | QueryOptions member | Description |
---|---|---|
Constructor flags | CommonFolderQuery | DefaultQuery or GroupByType/Author/Tag/Year/Month Artist/Album(Artist)/Composer/Genre/PublishedYear/Rating |
CommonFileQuery | DefaultQuery or OrderByName/Title/SearchRank/Date/MusicInfo | |
Configurable properties | FileTypeFilter | File extension list (empty for all). |
FolderDepth | Shallow (folder only) or Deep (folder and subfolders) | |
Language | Language ID string (example: “en-US”) | |
IndexerOption | UseIndexerWhenAvailable, OnlyUseIndexer, DoNotUseIndexer | |
ApplicationSearchFilter UserSearchFilter | Advanced Query Syntax (AQS) strings combined together. See http://msdn.microsoft.com/en-us/library/windows/desktop/bb266512.aspx. | |
SortOrder | Set of PropertyName/AscendingOrder pairs | |
Read-only properties | DateStackOption | Indicates how results are grouped (None, Month, or Year). |
GroupPropertyName | Indicates the property being used to group the |
When you construct a QueryOptions
object, you pass to its constructor a CommonFolderQuery
enumeration value or a CommonFileQuery
enumeration value indicating whether you want results grouped by folder or an ordered flat set of results. The remaining properties are pretty self-explanatory; you can look them up in the SDK documentation if you need more information about them. When filtering on storage item properties, you have access to the complete set of properties as described in the Storage item properties section in this chapter. And you can create rich filter strings using the Windows Advanced Query Syntax (AQS) along with the ApplicationSearchFilter
and UserSearchFilter
properties.
For example, the following code creates a query that returns music files in the rock genre that are older than November 5, 2004 and whose album title contains the word “Sky”:
QueryOptions qo = new QueryOptions(CommonFolderQuery.GroupByPublishedYear) { FolderDepth = FolderDepth.Deep, ApplicationSearchFilter = "date:>11/05/04 AND genre:rock AND System.Music.AlbumTitle:~~Sky" }; StorageFolderQueryResult folders = KnownFolders.MusicLibrary.CreateFolderQueryWithOptions(qo);
By the way, your app doesn’t have to process all of a query’s results. The StorageFolderQueryResult
class offers an overload of the GetFoldersAsync
method that takes a starting index and a max number of items. Similarly, the StorageFileQueryResult
class offers an overload of the GetFilesAsync
method that also takes a starting index and a max number of items.
Here is another example that creates a query resulting in a flat set of pictures ordered by date taken:
QueryOptions qo = new QueryOptions(CommonFileQuery.OrderByDate, new[] { "*" }); StorageFileQueryResult files = KnownFolders.PicturesLibrary.CreateFileQueryWithOptions(qo); files.OptionsChanged += OnOptionsChanged; files.ContentsChanged += OnContentsChanged;
This creates a flat, ordered list of all files by using a wild card (“*”). In addition, the OptionsChanged
event handler will invoke our OnOptionsChanged
method whenever one of our QueryOptions
object’s properties gets changed. Also, the ContentsChanged
event handler will invoke our OnContentsChanged
method if any storage items change that affect the results of our query. An app can dynamically update its user interface in response to this event.
It is common for apps to process the resulting folders and files, getting thumbnail images and properties for each storage item. However, iterating over all the items individually to get this data would be quite time consuming, and it is also highly unlikely that all the information could fit on the user’s screen anyway. To acquire thumbnail images more efficiently, call QueryOptions' SetThumbnailPrefetch
method. This causes the system to start loading thumbnail images immediately; this approach uses more resources but makes thumbnail retrieval on query results much faster. The QueryOptions
class also offers a SetPropertyPrefetch
method to get storage item properties more efficiently.
The following code demonstrates using these methods to improve the performance of fetching properties and thumbnail images:
// Create QueryOptions to filter/sort results QueryOptions qo = new QueryOptions(CommonFileQuery.OrderByDate, new[] { "*" }); // Improve performance of fetching properties and/or thumbnails String[] propertiesToRetrieve = new String[] { "System.Size" }; qo.SetPropertyPrefetch(PropertyPrefetchOptions.ImageProperties, propertiesToRetrieve); qo.SetThumbnailPrefetch(ThumbnailMode.PicturesView, 190, ThumbnailOptions.None); // From virtual folder call Create[File|Folder|Item]QueryWithOptions StorageFileQueryResult files = KnownFolders.PicturesLibrary.CreateFileQueryWithOptions(qo);
[33] The %ProgramFiles%WindowsApps directory is a hidden, system directory, so you will not be able to see it in File Explorer unless you choose to show hidden items. Once you’ve found the directory and subsequently try to open it, you’ll get an “access denied” error. However, you can open any of this directory’s subdirectories. Windows actually determines the package directory via the PackageRoot value, which can be found in the registry in the HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionAppx key.
[34] For details about how to handle schema changes in your package’s data on upgrade, refer back to Chapter 4.
[35] To update it, you must export it to an XML file, edit the XML file, and then import it back in.
[36] The subdirectory must be named “Indexed”; this will not work if you use any other name.
[37] Windows persists SettingsIdentifier info in the registry here: HKEY_CLASSES_ROOTLocal SettingsSoftwareMicrosoftWindowsCurrentVersionAppModelSystemAppDataPackageFamilyNamePersistedPickerData. It is really a package-specific value; not an app-specific value.
[38] Windows persists access list info in the registry here: HKEY_CLASSES_ROOTLocal SettingsSoftwareMicrosoftWindowsCurrentVersionAppModelSystemAppDataPackageFamilyNamePersistedStorageItemTable.
[39] For the curious, Windows assigns a unique file Id to all files on an NTFS volume and this is how it can track these changes. For more information about this, check out the Win32 OpenFileById function and its FILE_ID_DESCRIPTOR parameter on MSDN.
[40] Other classic attributes like Hidden, System, Device, SparseFile, ReparsePoint, Compressed, Offline, NotContentIndexed, Encrypted, IntegrityStream, and NoScrubData are not exposed.
[41] Libraries aren’t actual physical folders, and when you programmatically browse the file system, you will not find them. They are stored as XML files in %UserProfile%AppDataRoamingMicrosoftWindowsLibraries.
[42] You can easily tell that the folders do not physically exist by looking at each StorageFolder’s Path property. For virtual folders, the property will show the empty string (“”) because there is no actual path to a disk location that can be shown.
18.117.71.211