0

If I have a CSIDL (or its newer alternative KNOWNFOLDERID) for a special folder (for the sake of this example, let's assume My Documents folder) and a DOS folder path, is there any way to tell that the path refers to a subfolder within the special folder?

EDIT 1: I implemented the following method after @RemyLebeau's suggestion, but it always sets my nIsParent to 0, or not a parent. What am I missing there?

int nCSIDL = CSIDL_PERSONAL;
LPCTSTR pDosPath = L"C:\\Users\\UserName\\Documents\\Subfolder1\\File.txt";

int nIsParent = -1; //-1=error, 0=no, 1=yes

LPITEMIDLIST pidlDocuments = NULL;
if(SUCCEEDED(SHGetFolderLocation(NULL, nCSIDL, NULL, 0, &pidlDocuments)))
{
    LPITEMIDLIST pidl = ILCreateFromPath(pDosPath);
    if(pidl)
    {
        nIsParent = ILIsParent(pidlDocuments, pidl, FALSE) ? 1 : 0;

        ILFree(pidl);
    }

    ILFree(pidlDocuments);
}

EDIT 2: As for his 2nd suggestion to use SHGetPathFromIDList and then PathRelativePathTo on both DOS paths, it won't work for the following: My Documents on my computer is redirected to "\\SRVR-A\Home\UserName\Documents", which is also the "R:\Documents" folder with drive R: mapped to that Home share. PathRelativePathTo fails on those paths.

EDIT 3: If I had a folder Test folder in My Documents I could do this using my mapped drive R::

subst S: "R:\Documents\Test folder"

Which will technically make folder "S:\Test folder" a parent of My Documents as well, which is "\\SRVR-A\Home\UserName\Documents\Test folder".

That is why I was looking for a Shell-only, or a single API solution.

Community
  • 1
  • 1
c00000fd
  • 20,994
  • 29
  • 177
  • 400
  • If you use the full power of junctions / network shares / directory links, this will be fun. Undecidable in the general case even. – Deduplicator Jun 04 '14 at 02:34
  • @Deduplicator: That's what I mean. And that's why I'm looking for some Shell API to do this for me (if there's one.) – c00000fd Jun 04 '14 at 02:36
  • I think your best bet is getting a canonical path for both, and then resolving all junctions / reparse-points / soft-links / hard-links / netmounts and so manually. You only get a definitive answer if you get them all resolved for both. – Deduplicator Jun 04 '14 at 02:44
  • @Deduplicator: or you get an *inifinite* answer. Beware of cyclic junctions and links. But I side with your general point. Although the object manager will resolve the name, but that happens in KM. And AFAIK those APIs are internal and/or undocumented, so s/he's out of luck. – 0xC0000022L Jun 04 '14 at 02:49
  • @0xC0000022L I'm sure there's an infinite recursion guard in the kernel, too. In case of triggering that, you'll get an error. – Deduplicator Jun 04 '14 at 02:51
  • @Deduplicator: There is not. However, there's a hard limit on the overall path length. It's possible the object manager has a safeguard, too, but when you have a file system path, after it finds the correct device the control is handed off to the FS driver and the ones coming with Windows have no (extra) safeguards concerning that. And why should they, there's a hard limit. See http://stackoverflow.com/q/15262110/476371 – 0xC0000022L Jun 04 '14 at 02:55
  • @0xC0000022L: So, A link pointing to itself will recurse infinitely? – Deduplicator Jun 04 '14 at 02:57
  • @Deduplicator: it will hit the path length maximum and fail there (in kernel). But worse yet, most programs are not prepared and will simply crash ;) ... there are no extra safeguards against this. The hard path length limit is the upper boundary. But in such cases handling it is certainly more clever than crashing. Thus my comment. – 0xC0000022L Jun 04 '14 at 02:59
  • @0xC0000022L How will the path get longer when a link pointing to itself is replaced by itself? There's no change at all! – Deduplicator Jun 04 '14 at 03:07
  • @0xC0000022L: We're talking about figuring out whether a folder is a *subfolder* of some other folder. Not if another folder can be found beneath it. So in essence you just have to walk *upwards*, which is a very finite list of parents. – Joey Jun 04 '14 at 04:59
  • @Јοеу: in order to *walk* up you need to know the actual path (which could be made up of reparse points of various flavor). Hence you need to resolve your path before you walk up. – 0xC0000022L Jun 04 '14 at 13:13
  • In the most general case this is obviously impossible, e.g., if they are both UNC paths using different server names which just happen to resolve to the same underlying file system. For local disk, I guess you could look up the file and volume IDs (using GetFileInformationByHandle) and then use the MFT to follow the directory tree upwards until you either reach the top or find a match. There's some [sample code here](http://stackoverflow.com/a/7459109/886887) that includes logic to find the parent directory for a given file index. You need to be admin. – Harry Johnston Jun 05 '14 at 22:57
  • @HarryJohnston: Thanks. Being an admin is an issue. My process is not. – c00000fd Jun 06 '14 at 03:01

2 Answers2

1

Everything in the Shell is represented by the ITEMIDLIST structure, even file system paths. Retrieve the ITEMIDLIST of the special folder using SHGetFolderLocation() or SHGetKnownFolderIDList(), then retrieve the ITEMIDLIST of the DOS path using SHParseDisplayName() or ILCreateFromPath(), then use ILIsParent() to check if the special folder's ITEMIDLIST is a parent of the DOS path's ITEMIDLIST.

Alternatively, retrieve the special folder's path using SHGetFolderPath() or SHGetKnownFolderPath(), then use PathRelativePathTo to check if the DOS path can be represented as a relative subfolder of the special folder's path without using any ".." components.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks. I was really hopeful of your Shell-only solution, but unfortunately `ILIsParent()` always returns FALSE. (See my original post.) As for `PathRelativePathTo`, it doesn't work well with the mapped drives. (Also details in my original post.) – c00000fd Jun 04 '14 at 06:17
  • Did you try `SHParseDisplayName()` instead of `ILCreateFromPath()`? On some paths, they may return differ `ITEMIDLIST` values. If that does not work, maybe iterate through the `ITEMIDLIST` contents manually, using `IShellFolder::CompareIDs()` at each child level. I would expect `ILIsParent()` to do something like that internally, but who knows, it could just do a raw memory comparison instead. – Remy Lebeau Jun 04 '14 at 07:40
  • You will probably have to manually resolve the DOS path to its true target path before then comparing it to the special folder. – Remy Lebeau Jun 04 '14 at 07:42
  • I'm curious, why didn't `ILIsParent()` work as expected -- any idea? – c00000fd Jun 04 '14 at 08:10
  • I can't answer that, as I have never had to do this type of comparison before. You will have to look inside the `ITEMIDLIST` data to see what's different. – Remy Lebeau Jun 04 '14 at 21:46
-2

Create a function that gets a full path, name of the special folder, and just call strstr on the full path with the name of the special folder and if it does not return NULL then it is a subfolder.

As for an API for it, I'm not aware of something like that but it could be possible.

user3647412
  • 129
  • 1
  • 5
  • `strstr()` is an exact-match kind of function. You would have to first normalize the two strings. Even then, it would make more sense to use `strcmpi()` instead. Something like [`PathRelativePathTo()`](http://msdn.microsoft.com/en-us/library/windows/desktop/bb773740.aspx) would be better for that kind of comparison instead. – Remy Lebeau Jun 04 '14 at 05:07
  • Not speaking of the `strstr()` approach :) It doesn't seem like a good idea. It doesn't address folder redirections and mapped drives. – c00000fd Jun 04 '14 at 06:18
  • Besides, it's 8 bit and Windows paths are UTF-16. You need to apply the correct casing rules (which are actually stored on the disk) – MSalters Jun 04 '14 at 06:33
  • I was speaking of a general idea, obviously you'll need have the same charset and casings, strstr/wcswcs is just a minimal example. – user3647412 Jun 04 '14 at 08:17