5

I know how to get all open explorer windows, using Microsoft Internet Controls COM library. From this, I am able to find the LocationURL of those windows. However, this is only set for paths on the file system. Seemingly when virtual objects are displayed, like network printers or the recycle bin, LocationURL is empty. LocationName still seems to be set to the name which is visible on the start bar.

When LocationURL is set, this is sufficient for my purposes to know where the explorer window is pointing to, but how can I find out what it is pointing to for these special folders?

Reading up a bit on pointers to an item identifier list (PIDL). Knowing which PIDL is being shown in the explorer window could identify that. Is there any way to retrieve this?

Community
  • 1
  • 1
Steven Jeuris
  • 18,274
  • 9
  • 70
  • 161
  • 1
    The PIDL is an opaque identifier. It's just an id and it's meaningful only to the folder that delivers it. What final information are you after exactly? Any real sample code? – Simon Mourier Mar 09 '14 at 16:50
  • @SimonMourier I need to uniquely identify the location in the explorer window which is currently open and reinitialize it. [Here is what I have so far](https://bitbucket.org/Whathecode/laevo/src/e3def5ea299ddbc574c01b545673e6310a075705/ABC.Applications/ABC.Applications.Explorer/ExplorerPersistence.cs?at=default), but as stated, this only works when `LocationURL` is set. – Steven Jeuris Mar 09 '14 at 16:55
  • What do you mean by "uniquely identify the location"? Some folders are 100% virtual, not related to any file or url. – Simon Mourier Mar 09 '14 at 16:57
  • @SimonMourier Just unique enough, to be able to reinitialize explorer with that view. – Steven Jeuris Mar 09 '14 at 16:58
  • Possibly through initializing explorer again using [one of its command line switches](http://www.geoffchappell.com/studies/windows/shell/explorer/cmdline.htm), or [through Navigate of IWebBrowser2](http://msdn.microsoft.com/en-us/library/aa752127(v=vs.85).aspx). – Steven Jeuris Mar 09 '14 at 17:02

1 Answers1

10

Here is a sample C# Console app code that gets the PIDL of current Windows explorer windows:

class Program
{
    static void Main(string[] args)
    {
        var shellWindows = new ShellWindows();
        foreach (IWebBrowser2 win in shellWindows)
        {
            IServiceProvider sp = win as IServiceProvider;
            object sb;
            sp.QueryService(SID_STopLevelBrowser, typeof(IShellBrowser).GUID, out sb);
            IShellBrowser shellBrowser = (IShellBrowser)sb;
            object sv;
            shellBrowser.QueryActiveShellView(out sv);
            Console.WriteLine(win.LocationURL + " " + win.LocationName);
            IFolderView fv = sv as IFolderView;
            if (fv != null)
            {
                // only folder implementation support this (IE windows do not for example)
                object pf;
                fv.GetFolder(typeof(IPersistFolder2).GUID, out pf);
                IPersistFolder2 persistFolder = (IPersistFolder2)pf;

                // get folder class, for example
                // CLSID_ShellFSFolder for standard explorer folders
                Guid clsid;
                persistFolder.GetClassID(out clsid);
                Console.WriteLine(" clsid:" + clsid);

                // get current folder pidl
                IntPtr pidl;
                persistFolder.GetCurFolder(out pidl);

                // TODO: do something with pidl

                Marshal.FreeCoTaskMem(pidl); // free pidl's allocated memory
            }
        }
    }

    internal static readonly Guid SID_STopLevelBrowser = new Guid(0x4C96BE40, 0x915C, 0x11CF, 0x99, 0xD3, 0x00, 0xAA, 0x00, 0x4A, 0xE8, 0x37);

    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("6D5140C1-7436-11CE-8034-00AA006009FA")]
    internal interface IServiceProvider
    {
        void QueryService([MarshalAs(UnmanagedType.LPStruct)] Guid guidService, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object ppvObject);
    }

    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("1AC3D9F0-175C-11d1-95BE-00609797EA4F")]
    internal interface IPersistFolder2
    {
        void GetClassID(out Guid pClassID);
        void Initialize(IntPtr pidl);
        void GetCurFolder(out IntPtr pidl);
    }

    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("000214E2-0000-0000-C000-000000000046")]
    internal interface IShellBrowser
    {
        void _VtblGap0_12(); // skip 12 members
        void QueryActiveShellView([MarshalAs(UnmanagedType.IUnknown)] out object ppshv);
        // the rest is not defined
    }

    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("cde725b0-ccc9-4519-917e-325d72fab4ce")]
    internal interface IFolderView
    {
        void _VtblGap0_2(); // skip 2 members
        void GetFolder([MarshalAs(UnmanagedType.LPStruct)] Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object ppv);
        // the rest is not defined
    }
}

Note: Shell interfaces are only partially defined as we don't need the full details. Feel free to complete it if you need other information.

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • I was trying to cast to `IServiceProvider` earlier when I was trying to go down this avenue myself, but apparently wasn't awake enough to notice was trying to cast to `System.IServiceProvider`. I gave up when `null` was returned constantly. This makes sense! :) Any reasons why these interfaces, including `IShellBrowser`, `IFolderView`, etc ... aren't included when referencing the COM Shell library? :/ – Steven Jeuris Mar 09 '14 at 21:30
  • 2
    Yep. Big confusion between the COM and .NET ones. Same name, same purpose, 100% different:-) The Shell COM Library was made for Automation compatible clients (VB/VBA, Scripting languages, etc.). These clients cannot have access to pure IUnknown (not IDispatch) interfaces anyway. .NET is more powerful and could use it, but I don't think there is any TLB available for these interfaces that we could import. Note many interfaces are defined in the Windows API Code Pack: http://archive.msdn.microsoft.com/WindowsAPICodePack – Simon Mourier Mar 10 '14 at 08:06
  • 1
    If any passer-by's wonder what to do with the pointer afterwards, e.g. [converting it to a byte array](http://stackoverflow.com/q/1774508/590790), it can be passed to [Navigate2()](http://msdn.microsoft.com/en-us/library/aa752094(v=vs.85).aspx) of IWebBrowser2 afterwards, which works perfectly. – Steven Jeuris Mar 10 '14 at 22:58
  • 1
    There´s actually a pretty good .NET library that provides interface definitions for all that shell-related stuff; it´s called sharpshell and is available on github: https://github.com/dwmkerr/sharpshell – Matze Aug 01 '16 at 09:14