Click here to Skip to main content
Click here to Skip to main content

Prune My Recent Documents and associated Registry keys

, 15 Feb 2006 CPOL
Rate this:
Please Sign up or sign in to vote.
The development of an application which allows for selective pruning of the My Recent Documents files.

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:

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];:

/// <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];
    try
    {
        //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:

  1. the top level RecentDocsFormat,
  2. the entry in the associated filename extension subkey, and
  3. 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:

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:

/// <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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Ed Korsberg
Software Developer (Senior)
United States United States
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.
Follow on   Twitter

Comments and Discussions

 
GeneralNTFS perms to deny access to Recent files dir Pinmembernova_12-Apr-06 13:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141223.1 | Last Updated 15 Feb 2006
Article Copyright 2006 by Ed Korsberg
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid