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

Secure Delete .NET

, 21 Jan 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
A Windows Explorer like interface using the sdelete program

Introduction

Secure Delete .NET is a Windows Explorer like user interface that uses the sdelete program to perform secure file shredding.

sdelete is an executable program with a command line interface. The source code in this article simply provides a way to browse directories, add multiple files to a 'selected for deletion' list and perform the delete.

Requirements

  • Visual Studio 2010
  • .NET 4.0 Framework
  • sdelete

NB: You must download sdelete from the technet link above and install in your %system% directory before this program will work.

Background

While there is nothing groundbreaking in this article, I think that it solves a number of common issues and will show you how to tie them all together. Some of the problems addressed by this article include:

  • Using a class to create a mutex and only allow 1 instance of the application to run
  • An implementation of a UserOptions class to store and retrieve user choices through serialization
  • Using ToolStripContainer and SplitterContainer controls to create a multi-paned, resizeable interface
  • Using a Task to query directory details
  • Using Task Continuations to process exceptions or success of original Task
  • Loading a directory structure into a TreeView as a continuation
  • Loading associated directories and files into a ListView
  • Loading a large amount of ListViewItems into a ListView with good performance without using VirtualMode
  • How to drag and drop ListViewItems between two ListViews
  • Implementing a FileSystemWatcher to watch for directory changes and correctly updating the UI by invoking delegates
  • Using a BackgroundWorker to perform a threaded task
  • A custom implementation of the BackgroundWorker Cancellation and Exception event model to provide our own behaviour
  • Displaying a progress form while the background worker is running and updating the UI

Using the Program

The UI is fairly simple and should be instantly recognisable to you.

SS-2011.01.14-11.31.16.png
The Left Pane: Directory View

The pane on the left will display any fixed drives attached to your computer and also your 'My Documents' folder.

Clicking on a node in the tree view will cause it to expand and display all the subdirectories.

The Right Pane: Files View

When you click on an item in the directory view, as well as expanding the tree it will also load all files that are contained in the directory to the right hand pane.

The files view is where you can select any files you wish to perform a secure deletion on.

Selecting Files

Use standard windows selection methods (multi select highlight using the mouse, or using the Shift\Ctrl keys while clicking the files) to perform a multiple selection.

Once files are highlighted, use drag and drop to put the files in the 'Selected' list.

The Bottom Pane: Selected Files

Any files you have currently selected for deletion are displayed in the bottom pane. A context menu is available on this view by right clicking the windows.

  • Delete Selected. Will begin the delete operation and securely delete any of the files in the list.
  • Remove selected from List. If you decide you don't want to delete certain files, you can perform a multiple select and choose this menu option to remove the files from the list.
List Views

Both of the list views are designed to look similar to windows explorer 'Details' view. There are a couple of pieces of information that we need to retrieve in order to achieve this look and feel:

  • The icon associated with the file extension
  • The file description associated with the extension

Both of these can be retrieved from a call to SHGetFileInfo and passing certain flags.

As directories are expanded and views populated, the program will retrieve information about file extensions and add them to a 'Known Files' cache. This helps with performance so we don't have to keep calling the API. See the LoadFilesToList method in the main SecureDelete form for details.

NB: I know Icon.ExtractAssociatedIcon can be used to retrieve a file icon, but is there any other way to retrieve 'File Type Description' other than SHGetFileInfo? I decided to do both at the same time here.

Comments welcome!

User Options

The user options screen is very simple and provides a few settings that can be amended:

SS-2011.01.14-11.46.22.png
  • The number of passes to perform when deleting a file. The default setting is 3. The higher you set here, the longer it will take to delete files.
  • Logging enabled. If you select this, it will capture the output of sdelete and write to the log file.
  • Log File Location. The directory you want the sdelete-log-file to be written to.

When you click OK on the options dialog, you will perform a serialisation of the UserOptions class to your application data folder.

These settings are then persisted between application sessions.

Loading the Directory Tree

We can use a Task to get all of the directory details since Directory.GetDirectories can be quite slow when there are lots of directories to retrieve (e.g. C:\Windows).

var task = Task.Factory.StartNew(() =>
{
	List<string> directoryDetails = new List<string>();
	foreach (string directory in Directory.GetDirectories(startPath))
	{
		directoryDetails.Add(directory);
	}
	return directoryDetails;
});

Task failed = task.ContinueWith(t => HandleTaskError(t.Exception),
	TaskContinuationOptions.OnlyOnFaulted);

Task ok = task.ContinueWith(t => 
	PopulateTree(t.Result, startPath, node), 
		TaskContinuationOptions.OnlyOnRanToCompletion);

Loading the files contained within the directory is also contained in a Task.

The method GetFilesFromDirectory starts a new task that calls Directory.GetFiles and generates an ExplorerFileInfo instance for each found file.

For each file retrieved, it checks the cache of 'Known File Types' for a file Icon and file type description. If one isn't found, it calls the API SHGetFileInfo to retrieve the information. It then passes a list of ExplorerFileInfo instances to LoadFilesToList.

private void LoadFilesToList(List<ExplorerFileInfo> files)
{
	try
	{
		this.filesList.Items.Clear();
		this.filesList.BeginUpdate();

		ListViewItem[] viewItems = new ListViewItem[files.Count];

		for (int i = 0; i < viewItems.Length; ++i)
		{
			ExplorerFileInfo file = files[i];
			string[] items = new string[]{ file.Name, file.FileSize,
				file.FileTypeDescription, 
				file.LastWriteTime.ToString()};

			ListViewItem listItem = 
				new ListViewItem(items, file.Extension);
			listItem.Name = file.FullName;
			listItem.SubItems[1].Tag = file.Length;

			viewItems[i] = listItem;
		}

		this.filesList.Items.AddRange(viewItems);
	}
	finally                
	{
		this.filesList.EndUpdate();
	}            
}

There are a couple of things to note in LoadFilesToList.

  • Use of BeginUpdate and EndUpdate. Really helps with performance, but I don't see enough people using them.
  • Building up a list of items and adding to the ListView using AddRange. This is much quicker than adding to the ListView within the loop via the Items.Add method.

Try browsing to a location that has a large number of files in (many thousand) - performance should still be acceptable.

The Deletion Process

Depending on the number of files selected and the number of passes to perform while deleting the files, the process could take a significant amount of time to complete. We should therefore ensure the UI is responsive during this period and show a window that displays the current percentage completed.

The BackgroundWorker is the obvious choice to use for this since it provides events for marshalling back to the UI thread for both reporting progress and 'Work Completed'.

While the Task pattern provides excellent threading options, the BackgroundWorker still has its place because of its ability to provide frequent progress reporting.

Reporting progress from Tasks is an excellent blog on this subject.

The following code checks for the existence of the sdelete program, builds a list of files to delete, initialises the progress window, starts the worker thread and passes a custom object to its arguments and finally displays the progress window as a modal dialog.

if (Program.AskQuestion(Resources.DeletionPrompt) == DialogResult.Yes)
{
	// Check here to show a message before starting a thread
	if (!FileCleaner.CheckForDeleteProgram())
	{
		Program.ShowError(Resources.DeletionProgramNotFound);
		return;
	}

	List<string> filesToDelete = new List<string>();
	foreach (ListViewItem item in this.selectedFilesList.Items)
	{
		filesToDelete.Add(item.Name);
	}

	progressWindow.Begin(0, filesToDelete.Count);

	// Starts the deletion process on the worker thread and displays the progress
	// of the operation
	DeletionStartArguments arguments = 
	new DeletionStartArguments(filesToDelete, UserOptions.Current.NumberOfPasses);
	this.deletionWorker.RunWorkerAsync(arguments);

	progressWindow.ShowDialog(this);
}

The deletionWorker_DoWork method provides the implementation for the worker thread. In this method, I have provided some custom behaviour for cases where either a Cancellation or Exception had occurred.

Why change the standard behaviour?

The background worker model states that we should set e.Cancel to true if a cancellation is pending on the thread. If an exception occurs in the worker method, it should be left unhandled and it would be reported in the Error field of RunWorkerCompletedEventArgs.

However, this program was designed without access to Transactional NTFS. Additionally, some of the files being deleted could be quite large so using one of the Transactional APIs for .NET would not be advisable. If the delete process in DoWork has deleted 2 files out of 10 when a cancellation or exception event occurs, we just want to exit DoWork and report on the files that were deleted before the event.

Therefore, we always want to exit the routine and pass back a DeletionCompletedArguments object to the RunWorkerCompleted event handler which contains the files that have been deleted.

If we didn't do this, trying to access the Result object would cause either a TargetInvocationException in the case of an error, or an InvalidOperationException in the case of a cancellation.

We can still implement the same behaviour to test for cancellations or exceptions in the RunWorkerCompleted method, but we've taken control of the operation by wrapping into our own objects.

Deleting a File

We can delete the file by starting a process and passing in the required arguments. A class called FileCleaner wraps this functionality:

using (Process p = new Process())
{
	p.StartInfo.FileName = Resources.DeletionProgram;
	p.StartInfo.Arguments = string.Format("-p {0} -q \"{1}\"", passes, fileName);
	p.StartInfo.CreateNoWindow = true;
	p.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
	p.StartInfo.UseShellExecute = false;
	p.StartInfo.RedirectStandardOutput = true;
	p.Start();

	string output = p.StandardOutput.ReadToEnd();
	p.WaitForExit();

	fileDeleted = !File.Exists(fileName);

	if (UserOptions.Current.LogOperations)
	{
		log.Info(output);
		log.Info(string.Format(Resources.FileDeletionStatus, 
			fileName, fileDeleted.ToString()));
	}
}

NB: If this is the first time you have ever used sdelete, when the process is started you will see a dialog result asking you to accept the terms. Click accept to continue with the operation.

We could get around this by having this program automatically creating the registry key.

How to create the sdelete 'Accepted Terms' registry key

However, I'll leave it to you to read the terms and click Accept!

Points

This isn't a fully blown replica of Windows Explorer and doesn't currently implement all of the behaviour you might expect from an explorer interface. The source in this article was designed to show some of the techniques available to provide a responsive, familiar UI to a user.

Some of the classes in the application were originally from articles I've read on CodeProject. I've included links to the original articles in the class headers.

History

  • 21/01/2011 - UI fixes
    • Added custom TreeView and ListView controls that reduce flicker
    • Better handling of directory security exceptions
    • General tidy up of a few methods
  • 20/01/2011 - Bug fixes
    • Added flag to ProgressWindow so the FileSystemWatcher events don't execute if a deletion process is in operation
    • Refactored a few methods so the FileSystemWatcher 'Changed' event doesn't have to repopulate the entire list view.
    • Added a refresh after a deletion process, the window wasn't updating properly in all cases
  • 14/01/2011 - Initial release

License

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

Share

About the Author

Dylan Morley
Technical Lead
United Kingdom United Kingdom
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 PinmemberBrian Pendleton24-Jan-11 4:51 
GeneralRe: My vote of 5 PinmemberDylan Morley24-Jan-11 5:03 
Question2008 version of program? PinmemberNuNn22-Jan-11 16:15 
AnswerRe: 2008 version of program? PinmemberDylan Morley23-Jan-11 3:45 
GeneralMy vote of 5 PinmemberMarcus Kramer19-Jan-11 5:56 
GeneralMy vote of 4 PinmemberSlacker00718-Jan-11 22:59 

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
Web02 | 2.8.141220.1 | Last Updated 21 Jan 2011
Article Copyright 2011 by Dylan Morley
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid