Search code examples
powershellwinapiipropertystorage

Is it possible to get shell properties for an item not in the shell namespace?


Short Version

How does the shell get the properties of a file?

Long Version

The Windows Shell exposes a rich system of properties about items (e.g. files and folders) in the shell namespace.

For example:

  • System.Title: A Quick Guide for SQL Server Native Client OLE DB to ODBC Conversion
  • System.Author: George Yan (KW)
  • System.Document.LastAuthor: rohanl
  • System.Comment: To learn more about this speaker, find other TEDTalks, and subscribe to this Podcast series, visit www.TED.com Feedback: tedtalks@ted.com
  • System.ItemParticipants: George Yan (KW)
  • System.Company: Contoso
  • System.Language: English (United States)
  • System.Document.DateCreated: 6/‎10/‎2014 ‏‎5∶16∶30 ᴘᴍ
  • System.Image.HorizontalSize: 1845 pixels
  • System.Image.VerticalSize: 4695 pixels
  • System.Image.HorizontalResolution: 71 dpi
  • System.Image.VerticalResolution: 71 dpi

In order for the shell to read these properties, it obviously has to use a lot of sources:

  • Windows Media Foundation IMFMetadata works great for images and movies
  • Windows Imaging Component (WIC) probably has a lot of APIs for reading metadata
  • I'm not sure if IFilter can retrieve Title, Author, Subject, Comments etc from Office documents

Either way, it has to read the file contents stream and do something with the contents of the file in order to get all these fancy shell properties. In other words:

IStream  \
   +      |--> [magic] --> IPropertyStore
 .ext    /

Can use it with my own stream?

I have items that are not in the shell namespace; they are in a data store. I do expose them to the shell through IDataObject as CF_FILEDESCRIPTOR with an IStream when its time to perform copy-paste or drag-drop. But outside of that they are just streamable blobs in a data store.

I'd like to be able to leverage all the existing work done by the very talented and hard-working1 shell team to read metadata from a "file", which in the end only exists as an IStream.

Is there perhaps a binding context option that lets me get a property store based on an IDataObject rather than a IShellItem2?

So rather than:

IPropertyStore ps = shellItem2.GetPropertyStore();

is there a:

IPropertyStore ps = GetShellPropertiesFromFileStream(stream);

?

How does the shell get all the properties of a file?

Bonus Chatter - IPropertyStoreFactory

This interface is typically obtained through IShellFolder::BindToObject or IShellItem::BindToHandler. It is useful for data source implementers who want to avoid the additional overhead of creating a property store through IShellItem2::GetPropertyStore. However, IShellItem2::GetPropertyStore is the recommended method to obtain a property store unless you are implementing a data source through a Shell folder extension.

Tried

IPropertyStore ps = CoCreateInstance(CLSID_PropertyStore);
IInitializeWithStream iws = ps.QueryInterface(IID_IInitializeWithStream);

But CLSID_PropertyStore does not support IInitializeWithStream.

Bonus Reading

  • MSDN: Initializing Property Handlers

    Property handlers are a crucial part of the property system. They are invoked in-process by the indexer to read and index property values, and are also invoked by Windows Explorer in-process to read and write property values directly in the files.

  • MSDN: Registering and Distributing Property Handlers (spellunking the registry for fun and reading contracts from the other side)

Solution

  • (Have some experience in Property Store handlers) How I see a solution:

    1. Get PropertyStore handler CLSID for your file extension. You should use 2 regkeys key:

      • HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\PropertySystem\PropertyHandlers\.yourext
      • HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\PropertySystem\SystemPropertyHandlers
    2. Create two objects with CoCreateInstance

    3. If you have 2 object you can combine them into single object with PSCreateMultiplexPropertyStore

    4. Query for IInitializeWithStream (also you can try to query IPersistStream).

    If the PropertyStore object supports IInitializeWithStream/IPersistStream: you are lucky - just init your object and query the properties you need. If does not - you still have (dirty) variant to create temporary file and then use IPersistFile.