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


Today we'll do a scripting version of an old C++ program: Printing information about the items in the Recycle Bin. (How you wish to act on the information is up to you.)

This is a pattern we've seen a lot. Bind to a folder, enumerate its contents, extract properties.

var shell = new ActiveXObject("Shell.Application");
var recycleBin = shell.NameSpace(10); // CSIDL_BITBUCKET
var items = recycleBin.Items();
for (var i = 0; i < items.Count; i++) {
  var item = items.Item(i);
  WScript.StdOut.WriteLine(item.Name);
  WScript.StdOut.WriteLine(item.ExtendedProperty(
                                "System.Recycle.DeletedFrom"));
  WScript.StdOut.WriteLine(item.ExtendedProperty(
                                "System.Recycle.DateDeleted"));
  WScript.StdOut.WriteLine(item.Size);
}

Wow, that was way easier than doing it in C++!

Just for fun, I'll do it in C#, first as a straight port:

// add a reference to shell32.dll
class Program {
  public static void Main()
  {
    var shell = new Shell32.Shell();
    var recycleBin = shell.NameSpace(10); // CSIDL_BITBUCKET
    var items = recycleBin.Items();
    for (var i = 0; i < items.Count; i++) {
      var item = (Shell32.FolderItem2)items.Item(i);
      System.Console.WriteLine(item.Name);
      System.Console.WriteLine(item.ExtendedProperty(
                                    "System.Recycle.DeletedFrom"));
      System.Console.WriteLine(item.ExtendedProperty(
                                    "System.Recycle.DateDeleted"));
      System.Console.WriteLine(item.Size);
    }
  }
}

We have to cast to Shell32.Folder­Item2 because the default interface for the Item() method is Shell32.Folder­Item, but Extended­Property is a method on Shell32.Folder­Item2. We didn't have to do this explicit cast in JavaScript because JavaScript is a dynamically-typed language.

So let's use the dynamic keyword to mimic that in C#. Note, however, that if you use dynamic, then you miss out on a lot of IntelliSense features.

class Program {
  public static void Main()
  {
    var shell = new Shell32.Shell();
    var recycleBin = shell.NameSpace(10); // CSIDL_BITBUCKET
    var items = recycleBin.Items();
    foreach (dynamic item in items) {
      System.Console.WriteLine(item.Name);
      System.Console.WriteLine(item.ExtendedProperty(
                                    "System.Recycle.DeletedFrom"));
      System.Console.WriteLine(item.ExtendedProperty(
                                    "System.Recycle.DateDeleted"));
      System.Console.WriteLine(item.Size);
    }
  }
}

Now you can do things like list all the files deleted today

class Program {
  public static void Main()
  {
    var today = DateTime.Today;
    var shell = new Shell32.Shell();
    var recycleBin = shell.NameSpace(10); // CSIDL_BITBUCKET
    var items = recycleBin.Items();
    foreach (dynamic item in items) {
      if (item.ExtendedProperty("System.Recycle.DateDeleted").Date
                                                       == today) {
        System.Console.WriteLine(item.name);
      }
    }
  }
}
Comments (7)
  1. Logan says:

    Note that you can actually say `foreach(Shell32.FolderItem2 item in items) { … }` without a separate cast. This is a hold over from pre-generic C# where it would have been inconvenient to be constantly doing this when iterating over System.Collection.ArrayList or similar.

  2. Nico says:

    That's a nice example use of /dynamic/.  I found myself doing a similar thing when working with the myriad of MSHTML interfaces (IHTMLDocument and friends).

    And now this post has got me looking forward to CLR week :)

  3. Nick says:

         var item = (Shell32.FolderItem2)items.Item(i);

    Should be

         var item = ((Shell32.FolderItem2)items).Item(i);

  4. Nick says:

    BTW Raymond, I don't know if this is the right place to post this (this entry reminded me of the problem) but I don't really know who else to go to on this one. Perhaps it could be food for blog entry? I'm having three issues with Shell PIDLs (only happening with select Control Panel items, one example is "Notification Area Icons").

    First, take the PIDL of "Notification Area Icons" and call SHGetNameFromIDList with that PIDL and SIGDN_NORMALDISPLAY. Instead of getting "Notification Area Icons" back (like I do for others), I get the stringified version of the PIDL (e.g. ::{control_panel_clsid}{notif_area_clsid}).

    Second, take that same PIDL and call ShellexecuteEx with the SHELLEXECUTEINFO.lpIDList set to that PIDL and SHELLEXECUTEINFO.fMask set to SEE_MASK_IDLIST. It doesn't launch! Windows chucks up an error "there is no program associated" or something similar.

    Third, take that same PIDL and call SHGetFileInfo with that PIDL and with uFlags containing SHGFI_PIDL. The result is the Windows generic blank paper icon.

    I'm confused. I have no clue what is going on here. Is this a possible bug with some of the Control Panel virtual folders?

    I mean, the exact same code works for every other Shell item (libraries, folders, even OneDrive), but *some* (not all!) Control Panel virtual folders all exhibit the *exact same* as above. I could get a list of the ones that have this odd behavior if needed, but "Notification Area Icons" is the one I remember off the top of my head.

    It is very possible I'm doing something wrong but then why would it work for most other things? And for the record, the PIDL comes from IPersistFolder2::GetCurFolder.

    Thanks!

  5. DWalker says:

    @Nick: "I don't really know who else to go to on this one".  Open a support ticket.  :-)

  6. Adam V says:

    @Nick – your change would cast the *list* of items to a FolderItem2. If necessary, you could do:

       var item = (Shell32.FolderItem2)(items.Item(i));

  7. Boris says:

    So a RecycleBin class is still at -100 BCL points?

Comments are closed.

Skip to main content