Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recognize Windows Shell Special Folder (i.e. get its CSIDL) via its pIDL (Now determine if pIDLs are equal with C#)

I have a situation where I want to perform special processing on some Windows shell special folders (those corresponding to values in the CSIDL enum.) (My solution must be WinXP compatible.) The problem I'm having is that when I encounter IShellFolders as I work my way down the heirarchy, I cannot find a way to match up the IShellFolders to their CSIDL.

This is my current approach:

Initialize a static one-to-one data structure (csidlToFromFullPIdl) of all CSIDLs to their pIDLs returned by SHGetSpecialFolderLocation.

foreach (CSIDL csidl in Enum.GetValues(typeof(CSIDL))
{
    IntPtr fullPIdl = IntPtr.Zero;
    int hResult = ShellApi.SHGetSpecialFolderLocation(IntPtr.Zero, csidl, ref fullPIdl);
    if (hResult != 0)
        Marshal.ThrowExceptionForHR(hResult);
    csidlToFromFullPIdl.Add(csidl, fullPIdl);
}

Start the heirarchy with the Desktop IShellFolder:

int hResult = ShellApi.SHGetDesktopFolder(ref _shellFolder);
hResult = ShellApi.SHGetSpecialFolderLocation(IntPtr.Zero, CSIDL.CSIDL_DESKTOP, ref _fullPIdl);

Retrieve children like so:

hResult = _shellFolder.EnumObjects(IntPtr.Zero, SHCONTF.SHCONTF_FOLDERS, out pEnum);

// Then repeatedly call:
pEnum.Next(1, out childRelativePIdl, out numberGotten);

Construct new fully-qualified pIDLs for the children like so:

_fullPIdl = ShellApi.ILCombine(parentFullPIdl, childRelativePIdl);

(Finally, retrieve the IShellFolder for the child using:)

hResultUint = parentShellItem.ShellFolder.BindToObject(childRelativePIdl, IntPtr.Zero, ShellApi.IID_IShellFolder, out _shellFolder);

The problem I'm having is that neither the childRelativePIdl nor the _fullPIdl correspond to any pIDLs in csidlToFromFullPIdl.

TIA.

FYI on Vista machines the GUID corresponding to KNOWNFOLDERIDs may be a solution (but not for me as I must be WinXP compatible.)

I should also say that I think using the paths of the special folders (via SHGetSpecialFolderPath) is insufficient because several of the special folders in which I'm interested do not have paths. (E.g., CSIDL_DRIVES and CSIDL_NETWORK.)


I'm trying two approaches to this. The first is to use SHGetDataFromIDList to retrieve the Clsid, which I can then compare to known Clsids:

public static Guid GetClsidFromFullPIdl(IntPtr fullPIdl)
{
    // Get both parent's IShellFolder and pIDL relative to parent from full pIDL
    IntPtr pParentShellFolder;
    IntPtr relativePIdl = IntPtr.Zero;
    int hResultInt = ShellApi.SHBindToParent(fullPIdl, ShellGuids.IID_IShellFolder, out pParentShellFolder, ref relativePIdl);
    if (hResultInt != (int)HRESULT.S_OK)
        Marshal.ThrowExceptionForHR(hResultInt);
    object parentShellFolderObj = System.Runtime.InteropServices.Marshal.GetTypedObjectForIUnknown(
        pParentShellFolder, typeof(IShellFolder));
    IShellFolder parentShellFolder = (IShellFolder)parentShellFolderObj;

    SHDESCRIPTIONID descriptionId = new SHDESCRIPTIONID();
    IntPtr pDescriptionId = MarshalToPointer(descriptionId);
    // Next line returns hResult corresponding to NotImplementedException
    hResultInt = ShellApi.SHGetDataFromIDList(parentShellFolder, ref relativePIdl, SHGDFIL.SHGDFIL_DESCRIPTIONID, pDescriptionId,
        Marshal.SizeOf(typeof(SHDESCRIPTIONID)));
    if (hResultInt != (int)HRESULT.S_OK)
        Marshal.ThrowExceptionForHR(hResultInt);

    if (parentShellFolder != null)
        Marshal.ReleaseComObject(parentShellFolder);

    return descriptionId.Clsid;
}

static IntPtr MarshalToPointer(object data)
{
    IntPtr pData = Marshal.AllocHGlobal(Marshal.SizeOf(data));
    Marshal.StructureToPtr(data, pData, false);
    return pData;
}

The problem with this approach is that the call to SHGetDataFromIDList returns an hResult that corresponds to throwing a NotImplementedException. Does this mean that SHGetDataFromIDList is unavailable on my system? (WinXP SP3.)

My second approach is to compare the item identifier lists referenced by two pointers to item identifier lists and see if they are equal. I'm implementing a technique coded here in C. This is what I have so far:

like image 946
Carl G Avatar asked Aug 26 '10 05:08

Carl G


1 Answers

Per Raymond Chen: ITEMIDLISTs can be equivalent without being byte-for-byte identical. Use IShellFolder::CompareIDs to test equivalence.

like image 184
Carl G Avatar answered Nov 11 '22 06:11

Carl G