Introduction

This is my first submission to CodeProject so please excuse for any amateur formatting or other editorial lapses. I wanted to do something with my new installation of Visual Studio 2005 and .NET 2.0 runtime, and I came up with this idea I call PruneRecentDocs. This application will allow you to selectively delete the entries as presented in the My Recent Documents view.

Background

The Windows OS basically allows for either single file deletion or wholesale removal of the previous file history. I wanted something that would allow for selective pruning of this fairly long list. If you have not deleted the history of recent file accesses on your computer then you will have hundreds of entries stored in the HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\RecentDocs registry entry. Somewhere, there is an optional registry setting that can control the depth of these entries, but unless set, I suspect this list will grow without bounds. But, I am not a registry expert so I welcome your corrections and feedback on this issue.

If you run RegEdit.exe and look at HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\RecentDocs , you will see something like this:

And if you go to Start -> My Recent Documents, you will see something like this:

From the My Recent Documents view, you can only right click the mouse and delete one file at time. The default number of files displayed in the popup list is 15. However, if you delete one or more of these displayed files and if you have more files stored in the RecentDocs registry, then upon the next popup display, you will be presented with the next 15 MRU files. Attempting to delete all these files via this mechanism is very tedious.

Using the code

The class diagram view as shown in Visual Studio 2005:

Some highlights of the code design and classes

I made use of .NET generics to greatly simplify the table lookup and correlation operations that would have otherwise required much more coding using simple for loops. The first class of interest is RecentDocsFormat . This class takes a registry key as the constructor parameter and then parses the contents into members of the class. The registry key values under RecentDocs are of type REG_BINARY . The key values consist of a set of values with numerical names such as "0", "1", "2", etc. followed by the key value MRUListEx which is easy to decode being an ordered list of Uint32 entries representing the Most Recent Used files and terminated by the value 0xFFFF. I could not find a definitive declaration of the numerical named key values but the first portion of the binary data is a Unicode string of the filename.

The RegTools class has static functions that assist in the manipulation of binary registry key values used in the RecentDocs key and its subkeys. This is where the .NET 2.0 generic Dictionary<T,T> and List<T,T> were helpful.

The constructor for RecentDocsFormat

This constructor will decode the binary values of the given registry key and construct Dictionary cross mappings between the MRU numerical names and the user friendly file names contained within the key value:

C#
public RecentDocsFormat(RegistryKey key)
    FolderKey = key;
    FileToIndexMapping = new Dictionary<String, String>();
    IndexToFileMapping = new Dictionary<String, String>();
    MruListEx = new List<string>();
    sFileNames = new List<string>();
    //get mru binary list MRUListEx
    byte[] Bytes = (byte[])FolderKey.GetValue("MRUListEx");
    //build MruListEx list
    for (int index = 0; index < Bytes.Length / 
                        sizeof(UInt32); index++)
        UInt32 val = RegTools.GetMruEntry(index, ref Bytes);
        string sVal = val.ToString();
        MruListEx.Add(sVal);
    //get list of keyValues under this registry key
    //and create dictionary mapping between filename and indexname
    List<string> slist = RegTools.ExtractKeyValues(FolderKey);
    foreach (string s in slist)
        object obj = FolderKey.GetValue(s);
        string filename = 
               RegTools.ExtractUnicodeStringFromBinary(obj);
        FileToIndexMapping.Add(filename, s);
        IndexToFileMapping.Add(s, filename);
        sFileNames.Add(filename);
    sIndexNames = RegTools.ExtractKeyValues(FolderKey);

How to delete a filename reference from RecentDocs

RecentDocsFormat has several methods but the ultimate action taken in this application is to prune a filename reference from the various (more than one) registry keys. The RecentDocsFormat method, DeleteThisFile(string filename), accomplishes this. Note the use of the Dictionary key lookup such as String indexName = FileToIndexMapping[sFilename];:

C#
/// <summary>
/// Application calls this to remove the reference
/// and registry entry for this filename
/// </summary>
/// <param name="sFilename"></param>
public void DeleteThisFile(String sFilename)
    //dictionary lookup, filename to indexname mapping
    String indexName = FileToIndexMapping[sFilename];
        //delete from Registry
        FolderKey.DeleteValue(indexName);
        //remove entry from ram based list
        MruListEx.Remove(indexName);
        //update Registry entry
        RegTools.RebuildMruList(FolderKey, MruListEx);
        //and then unlink mapping strings
        FileToIndexMapping.Remove(sFilename);
        IndexToFileMapping.Remove(indexName);
        sFileNames.Remove(sFilename);
        sIndexNames.Remove(indexName);
    catch (ArgumentException)

A non standard ERD

This Entity Relationship like diagram does not conform to any particular or popular methodology but it helped me write the code. Starting with the RecentDocs registry key, we construct the top level RecentDocsFormat instance. Next, we extract the registry subkeys under RecentDocs and create a Dictionary<String, RecentDocsFormat> with the filename extension as the dictionary key, returning an instance of RecentDocsFormat (which encapsulates access to the registry key values). When it is time to delete a file, three targets are updated:

  • the top level RecentDocsFormat,
  • the entry in the associated filename extension subkey, and
  • the Windows shortcut stored in the special folder accessed via MyRecentDocsFolder = Environment.GetFolderPath(Environment.SpecialFolder.Recent);:
  • Wildcard matching

    This application extends the simple single filename selection with the wildcard option:

    There is nothing fancy about this code. If you select *.txt, for example, it will enumerate over the key values looking for a match on the file extension.

    Context menus and PInvoke

    You can also right click on an item and that will popup a context sensitive menu:

    This menu was constructed using a MouseDown event handler on the ListView class and a Click event handler on the TooStripMenuItem class. The first menu item will launch the associated application for this file. It does this by calling the shell function ShellExecute via PInvoke to open the shortcut link. The OS will do the rest, finding the file source and launching the application:

    C#
    listView1.MouseDown += new MouseEventHandler(listView1_MouseDown);
    LaunchToolStripMenuItem.Name = "LaunchToolStripMenuItem";
    LaunchToolStripMenuItem.Size = new System.Drawing.Size(119, 22);
    LaunchToolStripMenuItem.Text = "Launch";
    LaunchToolStripMenuItem.ToolTipText = 
        "Launch associated application for this file";
    LaunchToolStripMenuItem.Click += 
        new System.EventHandler(this.LaunchStripMenuItem_Click);
    //Notice the line ShellApi.ShellExecute
    /// <summary>
    /// MouseDown event handler for ListView
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void listView1_MouseDown(object sender, MouseEventArgs e)
        if (e.Button == MouseButtons.Right)
            //display context sensitive menu
            listView1.ContextMenuStrip.Show(listView1, 
                                      new Point(e.X, e.Y));
            ListItemSelected = listView1.HitTest(e.X, e.Y);
    /// <summary>
    /// Click event handler for Launch context menu
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void LaunchStripMenuItem_Click(object sender, 
                                               EventArgs e)
        ListViewItem item = ListItemSelected.Item;
        if (item != null)
            ShellApi.ShellExecute(IntPtr.Zero, "open", 
                 item.Text + ".lnk", "", "", 
                 ShellApi.ShowCommands.SW_SHOWNOACTIVATE);
    

    Shell call via PInvoke

    This class will allow the .NET application to call the shell32.sll function ShellExecute via PInvoke. I would like to express my appreciation for the site PINVOKE.NET for an excellent reference on how to use PInvoke:

    C#
    /// <summary>
    /// Pinvoke access to shell32.dll api functions
    /// </summary>
    class ShellApi
    /// <summary>
    /// last parameter in ShellExecute
    /// </summary>
    public enum ShowCommands : int
        SW_HIDE             = 0,
        SW_SHOWNORMAL       = 1,
        SW_NORMAL           = 1,
        SW_SHOWMINIMIZED    = 2,
        SW_SHOWMAXIMIZED    = 3,
        SW_MAXIMIZE         = 3,
        SW_SHOWNOACTIVATE   = 4,
        SW_SHOW             = 5,
        SW_MINIMIZE         = 6,
        SW_SHOWMINNOACTIVE  = 7,
        SW_SHOWNA           = 8,
        SW_RESTORE          = 9,
        SW_SHOWDEFAULT      = 10,
        SW_FORCEMINIMIZE    = 11,
        SW_MAX              = 11
    //Possible values for lpOperation
    //"edit"
    //"explore"
    //"find"
    //"open"
    //"print"
    /// <summary>
    /// Invoke win32 shell
    /// </summary>
    /// <param name="hwnd"></param>
    /// <param name="lpOperation"></param>
    /// <param name="lpFile"></param>
    /// <param name="lpParameters"></param>
    /// <param name="lpDirectory"></param>
    /// <param name="nShowCmd"></param>
    /// <returns></returns>
    [DllImport("shell32.dll")]
    public static extern IntPtr ShellExecute(
        IntPtr hwnd,
        string lpOperation,
        string lpFile,
        string lpParameters,
        string lpDirectory,
        ShowCommands nShowCmd);
    

    Overall experience using Visual Studio 2005 for the first time

    I found the Visual Studio 2005 IDE with IntelliSense to be wonderfully productive. Since I am new to most of the .NET 2.0 classes, I basically took a guess on what classes I could use for a particular feature and started typing. As the IntelliSense presented the choices matching my guesses, I could examine the types and parameters of the methods and quickly determine what is available.

    ClickOnce deployment, signing, and automatic downloading of new versions

    I have not done much with these features, but Visual Studio 2005 makes a really good canned solution for allowing the application to check a URL for updates and for downloading the latest version. I was able to simply choose a few options in the Publish pane, and publish this to a web site. If you navigate to the publish URL, you can automatically install the application. During installation, it will check for the prerequisite software such as the .NET 2.0 runtime and the Windows Installer 3.1. After installation, whenever you run the application, it will check for updates and optionally download the new version. When you use the Control Panel to uninstall this application, it will even allow you to rollback to a previous version or simply remove all versions for you. I don't know if this would be used by professional developers for retail software, but overall it was impressive in its scope and simplicity.

    History

  • 2006-02-06: 1.0.0.0
  • Initial release.
  • 2006-02-09 - 1.1.0.0.
  • Added context sensitive menus.
  • Added ability to launch the associated file application via PInvoke.
  • Fixed several exception handling errors.
  • During working hours is a real time firmware developer for custom ASIC's in embedded systems. Writes C/C++/C# code in his sleep and is moderately proficient in Java, Python, hardware description languages and other obscure domain specific languages. Deals with nano/microsecond data propagation delay paths, cache line hit/miss ratios, multicore asic design, etc.
    However for fun likes to dabble in the new technologies of the day and deals at higher level software engineering aspects. This is a very nice idea. Instead of selectively deleting, you can add a "delete all but checked" action wherein the checked MRU items list is saved in the program until unchecked by the user and all but the checked items are deleted. It can be a tiny utility from the tray.
    Sign In·View Thread  Your code is available for HK_C_U\software\microsoft\windows\currentversion\explorer\recentdocs
    but does not work for
    HK_C_U\software\microsoft\windows\currentversion\explorer\comdlg32\OpenSavePidlMRU\*
    Why ?
    Can you find pathname and filename from the second key?
    thanks.
    vb6pr <pr314159@bamboo.lu>
    Sign In·View Thread 
    I will look into this issue and hopefully post either a reply or an update in the future.
    Thank you for bringing this to my attention as I was unaware of this region of the registry. I guess since it does not seem to play a direct role in the My Recent Documents feed then I was not aware nor interested in this section.
    Sign In·View Thread 
    Thanks for the compliment. You can find the full path by Right-Clicking the file and choose "Properties". A standard Windows dialog box will appear showing the file properties. If then choose the Shortcut tab, you will see the full path name in the Target window.
    Sign In·View Thread 
    You can download (free) Visual Studio 2005 here:
    http://msdn2.microsoft.com/en-us/express/aa700756.aspx[^]
    Sign In·View Thread  C:\Documents and Settings\user\Recent
    since I spent some time, but not a lot searching trying to make windows stop utilizing \Recent\ I couldn't find anything, so I just permed it deny all.
    if I don't have a good reason to use it, then why waste a little HDD writing time allowing windows to utilize this dir, I say Smile | :)
    Sign In·View Thread