Windows 8–WinRT StorageFolder, Hidden Files, .NET, C++, AppContainers, Brokering

I was toying around with some code to sum up file and folder sizes on the disk from a Windows Store app and I hit what was, for me, a little bit of a snag.

My first attempt (extracted into a smaller snippet) looked something like this;

     FolderPicker picker = new FolderPicker();
      picker.FileTypeFilter.Add("*");
      var folder = await picker.PickSingleFolderAsync();

      var queryOptions = new QueryOptions();
      queryOptions.FolderDepth = FolderDepth.Shallow;
      queryOptions.FileTypeFilter.Add("*");
      queryOptions.IndexerOption = IndexerOption.DoNotUseIndexer;
      queryOptions.SetPropertyPrefetch(PropertyPrefetchOptions.BasicProperties, null);

      var query = folder.CreateFileQueryWithOptions(queryOptions);

      // I don't really want a thumbnail but it seems I can't avoid it?
      FileInformationFactory factory = new FileInformationFactory(query,
        ThumbnailMode.SingleItem);

      var files = await factory.GetFilesAsync();

      foreach (var file in files)
      {
        // This crashes because BasicProperties comes back null. Why?
        Debug.WriteLine("File size is {0}", file.BasicProperties.Size);
      }

Now, I’ll be honest and say that I don’t understand this. I felt like on line 9 up there I was asking for the BasicProperties of a StorageFile to be prepopulated for me by a query but it seems that isn’t happening because line 22 typically crashes on a null reference exception. So, that was a surprise and I also wasn’t sure about what unnecessary work line 14/15 might be doing when asking for a particular type of thumbnail so I figured I’d leave FileInformationFactory behind and try a different tack by avoiding the FileInformationFactory.

      FolderPicker picker = new FolderPicker();
      picker.FileTypeFilter.Add("*");
      var folder = await picker.PickSingleFolderAsync();

      var files = await folder.GetFilesAsync();
      var tasks = new List<Task<BasicProperties>>();

      foreach (var file in files)
      {
        tasks.Add(file.GetBasicPropertiesAsync().AsTask());
      }
      var results = await Task.WhenAll(tasks);

      foreach (var basicProps in results)
      {
        Debug.WriteLine("Size of file is {0}", basicProps.Size);
      }

and that initially seems to work ( I’ll come back to that in a moment ) but I don’t really like the code because I now have 2 enumerables in play – one for my list of files and another for my list of their BasicProperties and I’d really rather end up with one list here so I’d have to rework this a little;

      FolderPicker picker = new FolderPicker();
      picker.FileTypeFilter.Add("*");
      var folder = await picker.PickSingleFolderAsync();

      var files = await folder.GetFilesAsync();

      var tasks = files.Select(
        async file =>
        {
          var props = await file.GetBasicPropertiesAsync();
          return (Tuple.Create(file, props));
        }
      );
      var results = await Task.WhenAll(tasks);

      foreach (var tuple in results)
      {
        Debug.WriteLine("File {0} has size {1}", tuple.Item1.Name, tuple.Item2.Size);
      }

and that seems to kind of work although you can argue about whether you’d really want to wait for all those tasks to complete on line 14 rather than taking some other approach.

I was relatively happy with this until I pointed that code at a particular folder;

image

and my debug trace showed me;

image

and so the sharp-eyed will have noticed that test2.txt is a hidden file and I’ve got Explorer to show me hidden files but the StorageFolder class won’t.

By design, StorageFolder doesn’t bring back hidden files and so I’m never going to be able to get a view as to how big that file is using this sort of code.

Windows API to the rescue?

At this point, I wondered whether there were Windows APIs available to my Windows Store code that might help me. There’s a list of the APIs that a Windows Store app is allowed to call on the MSDN website and, specifically, FindFirstFile and FindNextFile are allowed so I figured that might work for me.

The next question is – how to call those APIs? If I take a look at FindFirstFile or to give it its full name FindFirstFileExW then I don’t find it too pretty from the point of view of its interop signature into .NET code although I can search the web to find the interop bits I need but I figure instead it’s going to be easier to call this API in the way that nature intended which is from C code.

Now, writing C code isn’t actually going to help me that much because to consume whatever I’ve written from .NET I really need to have a WinRT component and writing those ( for me at least ) involves writing C++/CX code so I end up writing that rather than C.

When writing this sort of WinRT component code, I seem to find myself always having to refer back to the docs on what types are allowed to be passed across the interface boundaries and I also seem to struggle to remember what some of the concrete types are. For example, I remembered that I was allowed to pass an IVector^ across a boundary but I couldn’t remember what the concrete implementation of that was on the C++ side and the docs here are good for that.

Anyway, I managed to cobble together a header file;

using namespace Platform;
using namespace Windows::Foundation::Collections;
using namespace Windows::Storage;

namespace FileHelper
{
	public ref class FileInfo sealed
	{
	public:	
		property ULONG64 Size
		{
			ULONG64 get();
		}
		property String^ Name
		{
			String^ get();
		}
	internal:
		FileInfo(String^ name, ULONG64 ulSize);
	private:
		ULONG64 _ulSize;
		String^ _name;
	};
	public ref class FolderInfo sealed
	{
	public:
		static IVector<FileInfo^>^ GetEntries(StorageFolder^ folder);
	private:
		FolderInfo();
	};
}

and some implementation;

using namespace FileHelper;
using namespace Platform;
using namespace Platform::Collections;

FileInfo::FileInfo(String^ name, ULONG64 ulSize) : 
	_name(name), _ulSize(ulSize)
{
}

ULONG64 FileInfo::Size::get()
{
	return(_ulSize);
}

String^ FileInfo::Name::get()
{
	return(_name);
}

FolderInfo::FolderInfo()
{
}

IVector<FileInfo^>^ FolderInfo::GetEntries(StorageFolder^ folder)
{
	String^ path(folder->Path + "\\*");
	WIN32_FIND_DATA findData;
	bool bContinue(true);	
	Vector<FileInfo^>^ vector = ref new Vector<FileInfo^>();

	ZeroMemory(&findData, sizeof(findData));

	HANDLE hSearchHandle = ::FindFirstFileExW(
		path->Data(),
		FINDEX_INFO_LEVELS::FindExInfoBasic,
		&findData,
		FINDEX_SEARCH_OPS::FindExSearchNameMatch,
		nullptr,
		0);

	if (hSearchHandle == INVALID_HANDLE_VALUE)
	{
		DWORD dwError = ::GetLastError();
		throw ref new COMException(dwError);
	}
	while (bContinue)
	{
		if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
		{
			ULONG64 size = findData.nFileSizeHigh * ((ULONG64)MAXDWORD + 1);
			size += findData.nFileSizeLow;

			FileInfo^ fileInfo = ref new FileInfo(
				ref new String(findData.cFileName),
				size);

			vector->Append(fileInfo);
		}
		bContinue = ::FindNextFile(hSearchHandle, &findData);
	}
	return(vector);
}

and that all seemed like a fine idea until I referenced it from my .NET code and started to think about what I was doing rather than just rushing around doing it Smile

      FolderPicker picker = new FolderPicker();
      picker.FileTypeFilter.Add("*");
      var folder = await picker.PickSingleFolderAsync();

      var files = FolderInfo.GetEntries(folder);

      foreach (var file in files)
      {
        Debug.WriteLine("File {0} has size {1}", file.Name, file.Size);
      }

This code all runs fine but at the point where we hit line 41 up above there it fails on that first call to FindFirstFileEx and it returns back with E_ACCESS_DENIED.

I shouldn’t really have been surprised.

Banging Up Against the App Container

In my naivety I hadn’t been thinking about how these APIs work, I’d just been trying to see if I could code myself out of a hole Smile

There’s a WinRT API that allows you to get a folder from a user via the FolderPicker. This stuff isn’t done inside your process. These kinds of APIs are brokered just as the slide that I’ve used in the past explains;

image

And your app is running inside of an App Container which uses (as far as I know) integrity levels to control files/folders that your app has access to and your app runs at a level of Low so won’t be able to get to anything that’s above the level of ‘Low’.

There’s a fairly decent write-up on App Containers split across this article and the follow up.

Once you have that folder, you can use it to access the files and folders within it because the system knows that you got the folder via a legitimate means (i.e. by asking the user for it) so it’s reasonable to let you work with it.

If you need access to that folder in the future (e.g. following a suspend/terminate) then you’d need to use the future access list to manage that.

But if you try and get access to one of those files via a different means as below;

      FolderPicker picker = new FolderPicker();
      picker.FileTypeFilter.Add("*");
      var folder = await picker.PickSingleFolderAsync();

      var files = await folder.GetFilesAsync();
      var firstFile = files[0];

      // Test access. This will work.
      (await firstFile.OpenReadAsync()).Dispose();

      // Test access. This won't work for an arbitrary file in an
      // arbitrary folder.
      var path = firstFile.Path;
      var sameFile = await StorageFile.GetFileFromPathAsync(path);
      (await sameFile.OpenReadAsync()).Dispose();

Then while line 9 is ok because it’s using an object that the broker handed out to it, line 14 is going to throw an access denied exception because I’m trying to talk straight to a file that I don’t otherwise have access to. As far as I know though, this would be going through the broker and the broker is saying “no”.

Even worse, if I was to try something like;

    async void Execute(object sender, RoutedEventArgs e)
    {
      FolderPicker picker = new FolderPicker();
      picker.FileTypeFilter.Add("*");
      var folder = await picker.PickSingleFolderAsync();

      var files = await folder.GetFilesAsync();
      var firstFile = files[0];

      // Test access. This will work.
      (await firstFile.OpenReadAsync()).Dispose();

      // Accessible? This fails with 'file not found'.
      IntPtr result = CreateFile2(firstFile.Path, 0x80000000, 0x1, 0x3, IntPtr.Zero);
      int lastError = Marshal.GetLastWin32Error();
    }
    [DllImport("kernel32", SetLastError=true)]
    static extern IntPtr CreateFile2(string fileName,
      UInt32 dwAccess,
      UInt32 dwShare,
      UInt32 dwCreation,
      IntPtr pCreateParams);

Then the code at line 14 isn’t talking to the broker at all, it’s just going straight to the file system and the process (although running as me) doesn’t have the permissions to open the file. I was a bit surprised that I seem to get back an E_FILE_NOT_FOUND rather than an E_ACCESS_DENIED but maybe that’s just how the API behaves in the light of process integrity levels not being right and I could see security reasons for perhaps wanting it to work that way.

No Hidden Files for Me

While I had a bit of fun writing a tiny bit of C++/CX it looks like there’s no way I’m going to get a complete folder listing including things like hidden files/folders even if the user has explicitly given me access to the folder in question. I can’t do it via the brokered way (by design) and I’ve reminded myself that the unbrokered way is a non-starter (by design).

It was a good refresher though – feel free to let me know if you see other possibilities Smile