10

How do i get the shell IPreviewHandler for a particular file extension?

Background

Windows allows developers to create a preview handler for their custom file types:

Preview handlers are called when an item is selected to show a lightweight, rich, read-only preview of the file's contents in the view's reading pane. This is done without launching the file's associated application.

A preview handler is a hosted application. Hosts include the Windows Explorer in Windows Vista or Microsoft Outlook 2007.

I want to leverage the existing IPreviewHandler infrasturcture to get a thumbnail for a file.

In A Stream

The problem is that my files are not housed in the shell namespace (i.e. they are not sitting on the hard drive). They are sitting in memory, accessable through an IStream. This means i cannot use the legacy IExtractImage interface; as it does not support loading a file from a Stream.

Fortunately, this is why the modern IPreviewHandler supports (recommends, and prefers) loading data from a Stream, and recommends against loading previews from a file:

This method is preferred to Initialize due to its ability to use streams that are not accessible through a Win32 path, such as the contents of a compressed file with a .zip file name extension.

So how do i get it?

There is no documentation on the correct way to get ahold of the IPreviewHandler associated with a particular extension. But if i take the directions of how to register an IPreviewHandler, and read the contract from the other side:

HKEY_CLASSES_ROOT
  .xyz
     (Default) = xyzfile

HKEY_CLASSES_ROOT
   xyzfile
      shellex
         {8895b1c6-b41f-4c1c-a562-0d564250836f} //IPreviewHandler subkey
             (Default) = [clsid of the IPreviewHandler]

I should be able to follow the same route, given that i know the extension. Lets follow that with a real world example, a .jpg file:

enter image description here

enter image description here

Notice that the file has a preview. Notice i included the second screenshot only to reinforce the idea that the preview doesn't come from a file sitting on the hard drive.

Lets get spellunking!

First is the fact that it's a .jpg file:

HKEY_CLASSES_ROOT
   .jpg
      (Default) = ACDC_JPG

HKEY_CLASSES_ROOT
   ACDC_JPG
      ShellEx
         {BB2E617C-0920-11d1-9A0B-00C04FC2D6C1}
         ContextMenuHandlers

Wait, there is no {8895b1c6-b41f-4c1c-a562-0d564250836f} subkey for a previewhandler. That must mean that we cannot get a thumbnail for .jpg files.

reducto an absurdum

The Real Question

The careful reader will realize that the actual question i'm asking is:

How do i get the preview of an image contained only in a stream?

And while that is a useful question, and the real issue i'm having, having an answer on how to use IPreviewHandler is also a useful question.

So feel free to answer either; or both!

Bonus Reading

Community
  • 1
  • 1
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
  • Would it be acceptable to save the stream to a temporary file? In this case you simply can use a ready-to-use preview control for .Net [like this](http://www.jam-software.de/shellbrowser_net/file-preview.shtml). – Joachim Marder May 14 '14 at 08:44
  • Do you REALLY need IPreviewHandler? It looks IThumbnailProvider extension is what you need. Even in case of IExtractImage you can try to initialize extension with IInitializeWithStream or IPersistStream (at least Windows explorer tries). – Denis Anisimov May 17 '14 at 11:53
  • 3
    A comment on [Hosting Preview Handlers in Windows Forms Applications](http://www.brad-smith.info/blog/archives/79) points out that [AssocQueryString](http://msdn.microsoft.com/en-us/library/bb773471%28v=VS.85%29.aspx) can be used for that. [ASSOCSTR_SHELLEXTENSION](http://msdn.microsoft.com/en-us/library/bb762475%28v=vs.85%29.aspx) can be used to request a shell extension which implements a specific interface, for example `IPreviewHandler`. (Posting this as a comment because I cannot verify it, and some details still need to be filled in before this is a useful answer.) –  May 17 '14 at 12:07
  • In the case of Outlook for example (similar to what your second screenshot exhibit), the attachment is indeed saved as a temporary file on the disk, so a preview can be extracted by many other means. – Simon Mourier May 17 '14 at 18:53
  • Note that `.jpg` has a `PerceivedType` of `image`. See [AssocGetPerceivedType MSDN](https://msdn.microsoft.com/en-us/library/windows/desktop/bb773463%28v=vs.85%29.aspx). Please let me know if this answers your question. – Thomas Weller Apr 28 '15 at 09:32
  • @DenisAnisimov IExtractImage does not support IInitializeWithStream or IPersistStream. – Ian Boyd Apr 02 '19 at 20:07
  • It is very depends on handler developer. There is no rule that every handler must support stream initialization. Developer can use the following interfaces to init: IPersistStream, IParentAndItem, IPersistFile, IInitializeWithStream, IInitializeWithItem, IInitializeWithFile. – Denis Anisimov Apr 02 '19 at 21:32
  • That's all well and good; but IExtractImage is supported by 0% of providers (when rounded to the nearest whole percent). – Ian Boyd Apr 02 '19 at 22:53
  • "IExtractImage is supported by 0% of providers" - what the providers do you mean? – Denis Anisimov Apr 03 '19 at 00:28
  • Iterate every file extension, ask for the `IExtractImage` provider, create the provider and ask if it supports `IInitializeWithStream` or `IPersistStream`. You'll find 0% of IExtractImage providers support them. – Ian Boyd Apr 03 '19 at 15:21
  • Cannot confirm. My Win10 dumps: http://www.shellace.com/ExtractImage.txt and http://www.shellace.com/Both.txt – Denis Anisimov Apr 03 '19 at 15:50

1 Answers1

7

@hvd had the right answer.

File types have a ShellEx key, with {guid} subkeys. Each {guid} key represents a particular InterfaceID.

There are a number of standard shell interfaces that can be associated with a file type:

  • {BB2E617C-0920-11d1-9A0B-00C04FC2D6C1} IExtractImage
  • {953BB1EE-93B4-11d1-98A3-00C04FB687DA} IExtractImage2
  • {e357fccd-a995-4576-b01f-234630154e96} IThumbnailProvider
  • {8895b1c6-b41f-4c1c-a562-0d564250836f} IPreviewHandler

Unsupported spelunking of undocumented registry keys

If i want to find, for example, the clsid of the IPreviewHandler associated with a .jpg file, i would look in:

HKEY_CLASSES_ROOT/.jpg/ShellEx/{8895b1c6-b41f-4c1c-a562-0d564250836f}
   (default) = [clsid]

But that's not the only place i could look. I can also look in:

HKEY_CLASSES_ROOT/.jpg
   (default) = jpgfile
HKEY_CLASSES_ROOT/jpgfile/ShellEx/{8895b1c6-b41f-4c1c-a562-0d564250836f}
   (default) = [clsid]

But that's not the only place i could look. I can also look in:

HKEY_CLASSES_ROOT/SystemFileAssociations/.jpg/ShellEx/{8895b1c6-b41f-4c1c-a562-0d564250836f}
   (default) = [clsid] 

But that's not the only place i could look. I can also look in:

HKEY_CLASSES_ROOT/SystemFileAssociations/jpegfile/ShellEx/{8895b1c6-b41f-4c1c-a562-0d564250836f}
   (default) = [clsid]

But that's not the only place i could look. If i think the file is an image, i can also look in:

HKEY_CLASSES_ROOT/SystemFileAssociations/image/ShellEx/{8895b1c6-b41f-4c1c-a562-0d564250836f}
   (default) = [clsid]

How did i find these locations? Did i only follow documented and supported locations? No, i spied on Explorer using Process Monitor as it went hunting for an IThumbnailProvider.

Don't use undocumented spellunking

So now i want to use a standard shell interface for a file-type myself. This means that i have to crawl the locations. But why crawl these locations in an undocumented, unsupported way. Why incur the wrath from the guy from high atop the thing? Use AssocQueryString:

Guid GetShellClsidForFileType(String fileExtension, Guid interfaceID)
{
    //E.g.:
    //   String fileExtension = ".jpg"
    //   Guid   interfaceID   = "{8895b1c6-b41f-4c1c-a562-0d564250836f}"; //IExtractImage

    //The interface we're after - in string form
    String szInterfaceID := GuidToString(interfaceID);

    //Buffer to receive the clsid string
    DWORD bufferSize := 1024; //more than enough to hold a 38-character clsid
    String buffer;
    SetLength(buffer, bufferSize);

    HRESULT hr := AssocQueryString(
          ASSOCF_INIT_DEFAULTTOSTAR, 
          ASSOCSTR_SHELLEXTENSION, //for finding shell extensions
          fileExtension, //e.g. ".txt"
          szInterfaceID, //e.g. "{8895b1c6-b41f-4c1c-a562-0d564250836f}"
          buffer,        //will receive the clsid string
          @bufferSize);
   if (hr <> S_OK) 
      return Guid.Empty;

   Guid clsid;
   HRESULT hr = CLSIDFromString(buffer, out clsid);
   if (hr <> NOERROR) 
      return Guid.Empty;

   return clsid;
}

And so to get the clsid of IPreviewHandler for .xps files:

Guid clsid = GetShellClsidForFileType(".xps", IPreviewHandler);

How to get IPreviewHandler for a file extension?

With all the above, we can now answer the question:

IPreviewHandler GetPreviewHandlerForFileType(String extension)
{
    //Extension: the file type to return IPreviewHandler for (e.g. ".xps")
    Guid previewHandlerClassID = GetShellClsidForFileType(extension, IPreviewHandler);

    //Create the COM object
    IUnknown unk = CreateComObject(previewHandlerClassID);

    //Return the actual IPreviewHanler interface (not IUnknown)
    return (IPreviewhandler)unk;
}
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
  • This would be extremely had you included the pinvoke methods, and constant definitions that go along with the method. – Bruce Burge May 18 '18 at 13:42
  • @BruceBurge This isn't C# CLR code; it's native Win32. – Ian Boyd May 18 '18 at 14:58
  • Ha, sorry about that := probably should have given it away, with that said, porting this over to C# (if you are inclined ) would be very helpful! – Bruce Burge May 22 '18 at 22:50
  • 2
    Note that AssocQueryString does not work to find preview handlers implemented using the "metro" APIs (or whatever they're called this week). An example is Microsoft's Mixed Reality viewer (included with Windows) which provides a preview handler for FBX 3D objects (once you assign them to it via Open With > Choose Default). They seem to be registered in some newfangled way (and it seems not all the required info/mappings are in the registry), and File Explorer appears to use its own code instead of the APIs the shell provides for everyone else to use. Quite frustrating. – Leo Davidson Jul 17 '18 at 21:42
  • 1
    There are today some files that don't have a preview handler at all (.txt and all image types like .png, .jpg or the modern .heic, .webp, etc.). What the Shell does for this is fallback with IThumbnailProvider support, so if query for "{8895b1c6-b41f-4c1c-a562-0d564250836f}" failed, it will also query for "{e357fccd-a995-4576-b01f-234630154e96}" (which is IID_IThumbnailProvider ). – Simon Mourier Jun 13 '21 at 10:37