views:

965

answers:

6

I'm trying to write some simple code that will return the directory for the recycle bin on a local drive. Seems like it would be simple -- should be a thousand answers on Google. Haven't found one yet :(

I HAVE found that FAT and NTFS drives have different base names (RECYCLED and RECYCLER). I've found that 'the' recycle bin is a virtual folder that combines the recycle bins of all drives on the machine.

What I haven't found is a way to find C: drive's recycle bin directory -- even on a Vietnamese (or any other non-English) machine. (No posts I can find indicate whether "RECYCLER" gets internationalized or not)

Can anyone point me to a definitive answer?

Thanks

UPDATE: Aware of CSIDL_BITBUCKET and the functions that use it. From everything I've read though, it points to a virtual directory which is the union of all deleted files by that user on all drives. Looking for the physical recycle bin directory (on my Vista it appears to be C:\$Recycle.Bin as far as I can tell)

A: 

In Win32, use SHGetSpecialFolderLocation. Pass CSIDL_BITBUCKET as the CDIL parameter.

Michael Petrotta
CSIDL_BITBUCKET is virtual. If a virtual folder is specified, this function will fail.
sean e
Right you are. SHGetSpecialFolderLocation is what you want for the recycler. Updated the answer.
Michael Petrotta
From my reading, SHGetSpecialFolderLocation returns the virtual user folder -- the one with all local drive's combined into one. I'm looking for the physical recycle bin directory on the disk.
DougN
There is no physical recycle bin directory on the disk. It is a virtual folder. The recycle bin simply collects specially named folders and files (renamed when you clicked "delete" so they "disappeared" from the file system) but does not have a physical location itself.
Abel
+1  A: 

See this question and this one too.

sean e
Looked at the article referenced by your first link. It simply references CSIDL_BITBUCKET which refers to the user's virtual recycle bin directory for the whole machine, not a per-drive directory. This is the problem with everything I can find on Google - it all points to this virtual directory.
DougN
Oh, and the second post forces you to go through all directories looking for one that would match the recycle bin's GUID. Isn't there a way to get the recycle bin a per-drive basis?
DougN
+1  A: 

Raymond Chen has the answer - How can I tell that a directory is really a recycle bin?

John Topley
I'm starting to think this might be the only way -- cycle through all directories in the root and check each one using Raymond's method of comparing the directory GUID to the Recycle Bin GUID. You'd think there'd be a more direct way...
DougN
Microsoft probably don't want you messing with those folders directly, because they might be subject to change in the future.
John Topley
+2  A: 

Using Raymond Chen's advice, and someone else's technique (can't remember where I found it) I present a function that will find the Recycle Bin directory on a drive. The function cycles through the directories in the root directory looking at hidden and/or system directories. When it finds one, it checks the child subdirectories looking for one that has CLSID_Recycle Bin.

Note that I've included two GetFolderCLSID functions below. Raymond Chen's is the simpler one, but it doesn't work on Windows 2000. The other implementation is longer, but appears to work everywhere.

Call like: CString recycleDir = FindRecycleBinOnDrive(L"C:\");

CString FindRecycleBinOnDrive(LPCWSTR path)
{
    CString search;
    search.Format(L"%c:\\*", path[0]);
    WIN32_FIND_DATA fd = {0};
    HANDLE fHandle = FindFirstFile(search, &fd);
    while(INVALID_HANDLE_VALUE != fHandle)
    {
     if(FILE_ATTRIBUTE_DIRECTORY == (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) //only check directories
     {
      if(0 != (fd.dwFileAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN))) //only check hidden and/or system directories
      {
       //the recycle bin directory itself won't be marked, but a SID-specific child directory will, so now look at them
       CString childSearch;
       childSearch.Format(L"%c:\\%s\\*", path[0], fd.cFileName);
       WIN32_FIND_DATA childFD = {0};
       HANDLE childHandle = FindFirstFile(childSearch, &childFD);
       while(INVALID_HANDLE_VALUE != childHandle)
       {
        if((FILE_ATTRIBUTE_DIRECTORY == (childFD.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) && //only check directories
         (childFD.cFileName[0] != L'.')) //don't check . and .. dirs
        {
         CString fullPath;
         fullPath.Format(L"%c:\\%s\\%s", path[0], fd.cFileName, childFD.cFileName);
         CLSID id = {0};
         HRESULT hr = GetFolderCLSID(fullPath, id);
         if(SUCCEEDED(hr))
         {
          if(IsEqualGUID(CLSID_RecycleBin, id))
          {
           FindClose(childHandle);
           FindClose(fHandle);
           //return the parent (recycle bin) directory
           fullPath.Format(L"%c:\\%s", path[0], fd.cFileName);
           return fullPath;
          }
         }
         else
         {
          Log(logERROR, L"GetFolderCLSID returned %08X for %s", hr, fullPath);
         }
        }

        if(FALSE == FindNextFile(childHandle, &childFD))
        {
         FindClose(childHandle);
         childHandle = INVALID_HANDLE_VALUE;
        }
       }
      }
     }
     if(FALSE == FindNextFile(fHandle, &fd))
     {
      FindClose(fHandle);
      fHandle = INVALID_HANDLE_VALUE;
     }
    }
    _ASSERT(0);
    return L"";
}


//Works on Windows 2000, and even as Local System account
HRESULT GetFolderCLSID(LPCWSTR path, CLSID& pathCLSID)
{
    LPMALLOC pMalloc = NULL;
    HRESULT hr = 0;
    if (SUCCEEDED(hr = SHGetMalloc(&pMalloc))) 
    {
     LPSHELLFOLDER pshfDesktop = NULL;
     if (SUCCEEDED(hr = SHGetDesktopFolder(&pshfDesktop))) 
     {
      LPITEMIDLIST pidl = NULL;
      DWORD dwAttributes = SFGAO_FOLDER;
      if (SUCCEEDED(hr = pshfDesktop->ParseDisplayName(NULL, NULL, (LPWSTR)path, NULL, &pidl, &dwAttributes))) 
      {
       LPPERSIST pPersist = NULL;
       if (SUCCEEDED(hr = pshfDesktop->BindToObject(pidl, NULL, IID_IPersist, (LPVOID *) &pPersist))) 
       {
        hr = pPersist->GetClassID(&pathCLSID); 
        pPersist->Release();
       } 
       pMalloc->Free(pidl);
      } 
      pshfDesktop->Release();
     } 
     pMalloc->Release();
    }
    return hr;
}


//Not supported on Windows 2000 since SHParseDisplayName wasn't implemented then
//HRESULT GetFolderCLSID(LPCWSTR pszPath, CLSID& pathCLSID)
//{
//  SHDESCRIPTIONID did = {0};
//  HRESULT hr = 0;
//  LPITEMIDLIST pidl = NULL;
//  if (SUCCEEDED(hr = SHParseDisplayName(pszPath, NULL, &pidl, 0, NULL))) //not supported by Windows 2000
//  {
//   IShellFolder *psf = NULL;
//   LPCITEMIDLIST pidlChild = NULL;
//   if (SUCCEEDED(hr = SHBindToParent(pidl, IID_IShellFolder, (void**)&psf, &pidlChild))) 
//   {
//    hr = SHGetDataFromIDList(psf, pidlChild, SHGDFIL_DESCRIPTIONID, &did, sizeof(did));
//    psf->Release();
//    pathCLSID = did.clsid;
//   }
//   CoTaskMemFree(pidl);
//  }
//  return hr;
//}
DougN
A: 

Chen code is totally wrong

Yes, and it's not the first time... (100 lines of code to get the bin directory, it's a joke)

Please point me towards a better (shorter) implementation. I haven't found one anywhere.
DougN
A: 

A little bit late, but perhaps better late than never...

After debugging shell32.dll, I have found that for each version of windows the recycle path is hardcoded and, also, depends on the filesystem of that drive. I have tested this on Windows XP, Vista and Windows7:

Let X: be the drive we want to get the path to the recycle bin and let SID be the SID of the current user, then:


    switchif(OsType) {
        case WindowsXP:
        {
            if(PartitionType("X:") == NTFS)
            {
                printf("Path is: X:\\Recycler\\SID\\");
            }
            else
            {
                printf("Path is X:\\RECYCLED\\");
            }
        }

        case WindowsVista:
        case Windows7:
        {
            if(PartitionType("X:") == NTFS)
            {
                printf("Path is: X:\\$Recycle.bin\\SID\\");
            }
            else
            {
                printf("Path is X:\\$RECYCLE.BIN\\");
            }
        }
    }

A wiki article presents the same facts: http://en.wikipedia.org/wiki/Recycle%5FBin%5F%28Windows%29

botismarius
That's interesting. Wonder if it holds in foreign language versions of Windows as well?
DougN
I believe it does.
botismarius