How can I get information about the items in the Recycle Bin?

Raymond Chen

Raymond

For some reason, a lot of people are interested in programmatic
access to the contents of the Recycle Bin.
They never explain why they care,
so it’s possible that they are looking at their problem the wrong way.

For example, one reason for asking,
“How do I purge an item from the Recycle Bin given a path?”
is that
some operation in their program results in the files going
into the Recycle Bin and they want them to be deleted entirely.
The correct solution is to clear the FOF_ALLOW­UNDO flag
when deleting the items in the first place.
Moving to the Recycle Bin and then purging is the wrong solution because
your search-and-destroy mission may purge more items than just the
ones your program put there.

The Recycle Bin is somewhat strange in that it can have multiple items
with the same name.
Create a text file called TEST.TXT on your desktop,
then delete it into the Recycle Bin.
Create another text file called TEST.TXT on your desktop,
then delete it into the Recycle Bin.
Now open your Recycle Bin.
Hey look, you have two TEST.TXT files with the same path!

Now look at that original problem:
Suppose the program, as part of some operation, moves
the file TEST.TXT from the desktop to the Recycle Bin,
and then the second half of the program goes into the Recycle Bin,
finds TEST.TXT and purges it.
Well, there are actually three copies of TEST.TXT in the
Recycle Bin, and only one of them is the one you wanted to purge.

Okay, I got kind of sidetracked there.
Back to the issue of getting information about the items in the
Recycle Bin.

The Recycle Bin is a shell folder,
and the way to enumerate the contents of a shell folder is to
bind to it and enumerate its contents.
The low-level interface to the shell namespace is via
IShell­Folder.
There is an easier-to-use medium-level interface based on
IShell­Item,
and there’s a high-level interface based on Folder
designed for scripting.

I’ll start with the low-level interface.
As usual, the program starts with a bunch of header files.

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <propkey.h>

The Bind­To­Csidl function binds to a folder specified
by a CSIDL.
The modern way to do this is via KNOWN­FOLDER,
but just to keep you old fogeys happy, I’m doing things the
classic way since you refuse to upgrade from Windows XP.
(We’ll look at the modern way later.)

HRESULT BindToCsidl(int csidl, REFIID riid, void **ppv)
{
 HRESULT hr;
 PIDLIST_ABSOLUTE pidl;
 hr = SHGetSpecialFolderLocation(NULL, csidl, &pidl);
 if (SUCCEEDED(hr)) {
  IShellFolder *psfDesktop;
  hr = SHGetDesktopFolder(&psfDesktop);
  if (SUCCEEDED(hr)) {
   if (pidl->mkid.cb) {
    hr = psfDesktop->BindToObject(pidl, NULL, riid, ppv);
   } else {
    hr = psfDesktop->QueryInterface(riid, ppv);
   }
   psfDesktop->Release();
  }
  CoTaskMemFree(pidl);
 }
 return hr;
}

The subtlety here is in the test for pidl->mkid.cb.
The IShell­Folder::Bind­To­Object
method is for binding
to child objects (or grandchildren or deeper descendants).
If the object you want is the desktop itself, then you can’t use
IShell­Folder::Bind­To­Object
since the desktop is not
a child of itself.
In fact, if the object you want is the desktop itself,
then you already have the desktop,
so we just Query­Interface for it.
It’s an annoying special case which usually lurks in your code
until somebody tries something like “Save file to desktop”
or changes the location of a special folder to the desktop,
and then
boom you trip over the fact that the desktop is not a child of itself.
(See further discussion below.)

Another helper function prints the display name of a shell namespace item.
There isn’t much interesting here either.

void PrintDisplayName(IShellFolder *psf,
    PCUITEMID_CHILD pidl, SHGDNF uFlags, PCTSTR pszLabel)
{
 STRRET sr;
 HRESULT hr = psf->GetDisplayNameOf(pidl, uFlags, &sr);
 if (SUCCEEDED(hr)) {
  PTSTR pszName;
  hr = StrRetToStr(&sr, pidl, &pszName);
  if (SUCCEEDED(hr)) {
   _tprintf(TEXT("%s = %s\n"), pszLabel, pszName);
   CoTaskMemFree(pszName);
  }
 }
}

Our last helper function retrieves a property from the shell namespace
and prints it.
(Obviously, if we wanted to do something other than print it,
we could coerce the type to something other than VT_BSTR.)

void PrintDetail(IShellFolder2 *psf, PCUITEMID_CHILD pidl,
    const SHCOLUMNID *pscid, PCTSTR pszLabel)
{
 VARIANT vt;
 HRESULT hr = psf->GetDetailsEx(pidl, pscid, &vt);
 if (SUCCEEDED(hr)) {
  hr = VariantChangeType(&vt, &vt, 0, VT_BSTR);
  if (SUCCEEDED(hr)) {
   _tprintf(TEXT("%s: %ws\n"), pszLabel, V_BSTR(&vt));
  }
  VariantClear(&vt);
 }
}

Okay, now we can get down to business.
The properties we will display from each item in the Recycle Bin
are the item name and path,
the original location (before the item was deleted),
the date the item was deleted,
and the size of the item.

Getting the name and path are done with
various combinations of flags to
IShell­Folder::Get­Display­Name­Of,
whereas getting the other properties involve talking to the
shell property system.
(My colleague

Ben Karas

covers the

shell property system
on his blog.)
The

SHCOLUMN­ID documentation

says that the displaced property set applies to items which have been
moved to the Recycle Bin,
so we can define those column IDs based on the values provided in
shlguid.h:

const SHCOLUMNID SCID_OriginalLocation =
   { PSGUID_DISPLACED, PID_DISPLACED_FROM };
const SHCOLUMNID SCID_DateDeleted =
   { PSGUID_DISPLACED, PID_DISPLACED_DATE };

The other property we want is

System.Size
,
which the documentation says is defined as
PKEY_Size by the
propkey.h header file.

Okay, let’s roll!

int __cdecl _tmain(int argc, PTSTR *argv)
{
 HRESULT hr = CoInitialize(NULL);
 if (SUCCEEDED(hr)) {
  IShellFolder2 *psfRecycleBin;
  hr = BindToCsidl(CSIDL_BITBUCKET, IID_PPV_ARGS(&psfRecycleBin));
  if (SUCCEEDED(hr)) {
   IEnumIDList *peidl;
   hr = psfRecycleBin->EnumObjects(NULL,
     SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &peidl);
   if (hr == S_OK) {
    PITEMID_CHILD pidlItem;
    while (peidl->Next(1, &pidlItem, NULL) == S_OK) {
     _tprintf(TEXT("------------------\n"));
     PrintDisplayName(psfRecycleBin, pidlItem,
                      SHGDN_INFOLDER, TEXT("InFolder"));
     PrintDisplayName(psfRecycleBin, pidlItem,
                      SHGDN_NORMAL, TEXT("Normal"));
     PrintDisplayName(psfRecycleBin, pidlItem,
                      SHGDN_FORPARSING, TEXT("ForParsing"));
     PrintDetail(psfRecycleBin, pidlItem,
                 &SCID_OriginalLocation, TEXT("Original Location"));
     PrintDetail(psfRecycleBin, pidlItem,
                 &SCID_DateDeleted, TEXT("Date deleted"));
     PrintDetail(psfRecycleBin, pidlItem,
                 &PKEY_Size, TEXT("Size"));
     CoTaskMemFree(pidlItem);
    }
   }
   psfRecycleBin->Release();
  }
  CoUninitialize();
 }
 return 0;
}

The only tricky part is the test for whether the call to
IShell­Folder::Enum­Objects succeeded,
highlighted above.
According to

the rules for
IShell­Folder::Enum­Objects
,
the method is allowed to
return S_FALSE to indicate that there are no
children, in which case it sets peidl to NULL.

If you are willing to call functions new to Windows Vista,
you can simplify the Bind­To­Csidl function
by using the helper function SHBind­To­Object.
This does the work of getting the desktop folder and handling the
desktop special case.

HRESULT BindToCsidl(int csidl, REFIID riid, void **ppv)
{
 HRESULT hr;
 PIDLIST_ABSOLUTE pidl;
 hr = SHGetSpecialFolderLocation(NULL, csidl, &pidl);
 if (SUCCEEDED(hr)) {
  hr = SHBindToObject(NULL, pidl, NULL, riid, ppv);
  CoTaskMemFree(pidl);
 }
 return hr;
}

But at this point, I’m starting to steal from the topic I scheduled
for next time, namely modernizing this program to take advantage of
some new helper functions and interfaces.
We’ll continue next time.

0 comments

Comments are closed.