Modernizing our simple program that retrieves information about the items in the Recycle Bin

Raymond Chen

Raymond

Last time,

we wrote a simple program to print various properties of the items
in the Recycle Bin
,
and we did so in the classical style,
using item ID lists and IShell­Folders.
One thing you may have noticed is that a lot of functions take
the combination of an IShell­Folder and a
PCUITEMID_CHILD.
In the shell namespace, operations on items usually happen
by means of the pair (folder, child),
and one of the common mistakes made by beginners is
failing to keep track of the pairing and passing child pidls
to the wrong parent folder.

Even if you’re not a beginner and are good at keeping track of
which child pidls correspond to which parent folders,
it’s still extra work you have to do,
and it means that a lot of functions take two parameters in order to
describe one thing.

Enter IShell­Item.

The IShell­Item encapsulates the pair (folder, child).
This solves two problems:

  1. You only have to pass one thing around (the IShell­Item)
    instead of two (the IShell­Folder and the
    PCUITEMID_CHILD).

  2. By keeping track of the two items as a single unit,
    it reduces the risk that you’ll accidentally use a child pidl
    with the wrong parent folder.

Another complexity of the classic shell interface is that there
are a bunch of ways of obtaining COM objects from a shell folder:

  • IShell­Folder::Bind­To­Object
  • IShell­Folder::Bind­To­Storage
  • IShell­Folder::Create­View­Object
  • IShell­Folder::Get­UI­Object­Of
  • IUnknown::Query­Interface
    (thanks to the desktop special case we saw last time).

The IShell­Item::Bind­To­Handler
interface hides these
special-cases by dealing with them under the covers so you don’t have to.
You just call IShell­Item::Bind­To­Handler
and it figures
out where to get the object and what weird special cases apply.
(It also takes care of the weird S_FALSE return value
from IShell­Folder::Enum­Objects.)

And then there’s the annoyance of
IShell­Folder::Get­Display­Name­Of
using

the kooky STRRET structure
.
The IShell­Item::Get­Display­Name
function encapsulates that
away for you by doing the work to convert that STRRET
into a boring string pointer.

First up in modernizing our sample program
is to change Bind­To­Csidl to return a shell item
instead of a shell folder.

HRESULT BindToCsidlItem(int csidl, IShellItem ** ppsi)
{
 *ppsi = NULL;
 HRESULT hr;
 PIDLIST_ABSOLUTE pidl;
 hr = SHGetSpecialFolderLocation(NULL, csidl, &pidl);
 if (SUCCEEDED(hr)) {
  hr = SHCreateShellItem(NULL, NULL, pidl, ppsi);
  CoTaskMemFree(pidl);
 }
 return hr;
}

But wait, since we’re modernizing, we may as well upgrade to
SHGet­Known­Folder­ID­List:

HRESULT BindToKnownFolderItem(REFKNOWNFOLDER rfid, IShellItem ** ppsi)
{
 *ppsi = NULL;
 HRESULT hr;
 PIDLIST_ABSOLUTE pidl;
 hr = SHGetKnownFolderIDList(rfid, 0, NULL, &pidl);
 if (SUCCEEDED(hr)) {
  hr = SHCreateShellItem(NULL, NULL, pidl, ppsi);
  CoTaskMemFree(pidl);
 }
 return hr;
}

Hey wait, there’s a function for this already in Windows 7!
It’s called SHGet­Known­Folder­Item.
Yay, now we can delete the function entirely.

Next, we convert Print­Display­Name to use
IShell­Item and the item-based display name flags
SIGDN.

void PrintDisplayName(IShellItem *psi, SIGDN sigdn, PCTSTR pszLabel)
{
 LPWSTR pszName;
 HRESULT hr = psi->GetDisplayName(sigdn, &pszName);
 if (SUCCEEDED(hr)) {
  _tprintf(TEXT("%s = %ws\n"), pszLabel, pszName);
  CoTaskMemFree(pszName);
 }
}

And then we convert Print­Detail to use
IShell­Item.
Oh wait, now we’ve hit a snag:
The IShell­Item interface doesn’t have a helper method
that wraps IShell­Folder2::Get­Details­Ex.
Fortunately, there is a way to ask IShell­Item to
regurgitate the IShell­Folder
and PITEMID_CHILD
that it is wrapping:
You use the
IParent­And­Item::Get­Parent­And­Item
method.

void PrintDetail(IShellItem *psi,
    const SHCOLUMNID *pscid, PCTSTR pszLabel)
{
 IParentAndItem *ppni;
 HRESULT hr = psi->QueryInterface(IID_PPV_ARGS(&ppni));
 if (SUCCEEDED(hr)) {
  IShellFolder *psf;
  PITEMID_CHILD pidl;
  hr = ppni->GetParentAndItem(NULL, &psf, &pidl);
  if (SUCCEEDED(hr)) {
   VARIANT vt;
   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);
   }
   psf->Release();
   CoTaskMemFree(pidl);
  }
 }
}

Wow, it looks like we lost ground there.
Ah, but Windows Vista extends IShell­Item with the
IShell­Item2 interface, and that has a bunch of new
methods for retrieving properties.

void PrintDetail(IShellItem2 *psi,
    const SHCOLUMNID *pscid, PCTSTR pszLabel)
{
  PROPVARIANT vt;
  HRESULT hr = psi->GetProperty(*pscid, &vt);
  if (SUCCEEDED(hr)) {
   hr = VariantChangeType(&vt, &vt, 0, VT_BSTR);
   if (SUCCEEDED(hr)) {
    _tprintf(TEXT("%s: %ws\n"), pszLabel, V_BSTR(&vt));
   }
   PropVariantClear(&vt);
  }
 }
}

But wait, there’s more.
There’s a special accessor just for retrieving properties as strings!

void PrintDetail(IShellItem2 *psi2,
    const SHCOLUMNID *pscid, PCTSTR pszLabel)
{
 LPWSTR pszValue;
 HRESULT hr = psi2->GetString(*pscid, &pszValue);
 if (SUCCEEDED(hr)) {
  _tprintf(TEXT("%s: %ws\n"), pszLabel, pszValue);
  CoTaskMemFree(pszValue);
 }
}

Okay, that’s more like it.
Now let’s update the main program.

int __cdecl _tmain(int argc, PTSTR *argv)
{
 HRESULT hr = CoInitialize(NULL);
 if (SUCCEEDED(hr)) {
  IShellItem *psiRecycleBin;
  hr = SHGetKnownFolderItem(FOLDERID_RecycleBinFolder, KF_FLAG_DEFAULT,
                            NULL, IID_PPV_ARGS(&psiRecycleBin));
  if (SUCCEEDED(hr)) {
   IEnumShellItems *pesi;
   hr = psiRecycleBin->BindToHandler(NULL, BHID_EnumItems,
                                     IID_PPV_ARGS(&pesi));
   if (hr == S_OK) {
    IShellItem *psi;
    while (pesi->Next(1, &psi, NULL) == S_OK) {
     IShellItem2 *psi2;
     if (SUCCEEDED(psi->QueryInterface(IID_PPV_ARGS(&psi2)))) {
      _tprintf(TEXT("------------------\n"));
      PrintDisplayName(psi2, SIGDN_PARENTRELATIVE,
                             TEXT("ParentRelative"));
      PrintDisplayName(psi2, SIGDN_NORMALDISPLAY, TEXT("Normal"));
      PrintDisplayName(psi2, SIGDN_FILESYSPATH, TEXT("FileSys"));
      PrintDetail(psi2, &SCID_OriginalLocation, TEXT("Original Location"));
      PrintDetail(psi2, &SCID_DateDeleted, TEXT("Date deleted"));
      PrintDetail(psi2, &PKEY_Size, TEXT("Size"));
      psi2->Release();
     }
     psi->Release();
    }
   }
   psiRecycleBin->Release();
  }
  CoUninitialize();
 }
 return 0;
}

Okay, so now we know how to enumerate the contents of the Recycle Bin
and obtain properties of the items in it.
How do we purge or restore items?
We’ll look at that next time.

Raymond Chen
Raymond Chen

Follow Raymond