Click here to Skip to main content
15,860,972 members
Articles / Programming Languages / C#
Article

DeleteOld : Console Application to Delete Files by Age

Rate me:
Please Sign up or sign in to vote.
4.44/5 (14 votes)
17 Oct 2008CPOL3 min read 111.4K   1.6K   48   24
A C# console application for deleting files based on age.

deleteold.gif

Introduction

DeleteOld is a C# console application to delete 'old' files. The application uses a series of command-line switches to control the execution. I've taken ideas from similar apps, so certainly can't claim to have invented this concept!

I found that the (free) applications out there, that do this, either don't work fully or were a bit 'clunky' — so I decided to 'reinvent the wheel' and write my own implementation in C#.

Updates (Oct 2008)

This version now includes the following new functionality (mostly from comments on the article)

  • Allow files to be deleted 'newer' than a certain age as well as 'older'
  • Allow files to be deleted based on an absolute date (overriding the timeframe arguments)
  • Allow the 'root' path to be preserved if 'remove empty folders' is selected and the path specified is empty.
  • Fixed a bug in Arguments parsing regex as noted by dudik
  • Changed output datetime format to 'full' rather than specific dd/mm/yyyy as noted by a.plus.01

Using the Code

The biggest challenge originally was command line parameters. I used a slightly modified version of Richard Lopes's Arguments class to achieve what I wanted with a variety of mandatory and optional parameters — some are name/value pairs and others are simple switches.

The rest is fairly straightforward (in C# terms) and should be relatively easy to follow. Once the input parameters have been validated, the main processing is a fairly simple recursive loop around finding and deleting files in a directory tree.

It's important to note that we need to set a consistent time for file time comparison purposes — so the following code gets a 'benchmark date' (based on the requested timeframe) before processing begins.

C#
/// <summary>
/// Sets the delete time benchmark.
/// </summary>
private void SetDeleteTimeBenchmark()
{
	if (age != int.MaxValue)
	{
		if (timeFrame == AgeIncrement.Second)
			deleteBaselineDate = runTime.AddSeconds(-age);
		if (timeFrame == AgeIncrement.Minute)
			deleteBaselineDate = runTime.AddMinutes(-age);
		if (timeFrame == AgeIncrement.Hour)
			deleteBaselineDate = runTime.AddHours(-age);
		if (timeFrame == AgeIncrement.Day)
			deleteBaselineDate = runTime.AddDays(-age);
		if (timeFrame == AgeIncrement.Month)
			deleteBaselineDate = runTime.AddMonths(-age);
		if (timeFrame == AgeIncrement.Year)
			deleteBaselineDate = runTime.AddYears(-age);
	}
	//If a date has been provided then this takes precedence
	if (absoluteDate != null && absoluteDate != DateTime.MinValue)
		deleteBaselineDate = absoluteDate;
}

We can then process our actual command and take all of our command-line options into account as we go:

C#
/// <summary>
/// Deletes the files (potentially recursively) from given folder.
/// </summary>
private int DeleteFilesFromFolder(DirectoryInfo folder, bool simulateOnly)
{
	foreach(FileInfo file in folder.GetFiles(filter))
	{
		if (!deleteNewer ? file.LastWriteTime <
                      deleteBaselineDate : file.LastWriteTime > deleteBaselineDate)
		{
			try
			{
				if (! quietMode)
					outputStream.WriteLine("Deleting {0}",
                                                file.FullName);
				if (! simulateOnly)
					file.Delete();
			}
			catch (Exception ex)
			{
				if (! quietMode)
					errorOutputStream.WriteLine(
                                              "Could not delete file {0} : Reason {1}",
                                              file.FullName, ex.Message);
			}
		}
	}

	if (recurseSubFolders)
	{
		foreach(DirectoryInfo subFolder in folder.GetDirectories())
		{
			DeleteFilesFromFolder(subFolder, simulateOnly);
		}
	}

	if (removeEmptyFolders)
	{
		if (folder.FullName == path && dontRemoveEmptyRootFolder)
		{
			outputStream.WriteLine("Leaving empty root folder {0}",
                               folder.FullName);
		}
		else if (folder.GetFiles().Length == 0 &&
                      folder.GetDirectories().Length == 0)
		{
			if (! quietMode)
				outputStream.WriteLine("Deleting empty folder {0}",
                                         folder.FullName);

			try
			{
				if (! simulateOnly)
					folder.Delete(true);
			}
			catch (Exception ex)
			{
				if (! quietMode)
					errorOutputStream.WriteLine(
                                               "Could not delete folder {0} : Reason {1}",
                                               folder.FullName, ex.Message);
			}
		}
	}

	return 0;
}

You'll notice that there is an NUnit tests class bundled into the project, so if you want to build the project and don't have NUnit 2.2 installed, you'll need to exclude DeleteOldTests.cs from the project and remove the reference to NUnit.

Points of Interest

Unit Tests

One of the interesting obstacles was how to usefully unit test such an application. Console apps are a bit troublesome in that all the code is typically written into the .exe (to keep deployment light), and a separate .NET assembly (e.g. Unit Tests) can't reference a .exe assembly. I could have of course used a post-build event to make a copy as DeleteOld.dll and reference that from a tests assembly but I felt, that was a bit too much of a compromise.

I typically wanted to test output from the command based on the input parameters and the files I was looking to delete. This meant that the easiest way, was to provide programmatic access to override the output streams used at runtime. The default constructor uses standard Console streams:

C#
public DeleteOld(Arguments args)
{
	outputStream = new StreamWriter(Console.OpenStandardOutput());
	outputStream.AutoFlush = true;
	errorOutputStream = new StreamWriter(Console.OpenStandardError());
	errorOutputStream.AutoFlush = true;

	//etc...
}

The unit tests use the other constructor to pass in a MemoryStream.

C#
public DeleteOld(Arguments args, Stream outputStream, Stream errorOutputStream)
{
	this.outputStream = new StreamWriter(outputStream);
	this.outputStream.AutoFlush = true;
	this.errorOutputStream = new StreamWriter(errorOutputStream);
	this.errorOutputStream.AutoFlush = true;

	//etc...

}

This technique effectively enables the tests to do the following in order to setup and check the output of the command:

C#
[Test]
public void DirectoryNotExist()
{

	Environment.CurrentDirectory = Path.Combine(Path.GetTempPath(), "DeleteOldtests");
	string[] args = GetInvalidArguments2();
	MemoryStream ms = new MemoryStream();
	Arguments arguments = new Arguments(args);

	DeleteOld DeleteOld = new DeleteOld(arguments, ms, ms);
	Assert.IsFalse(DeleteOld.Validated);
	string output = System.Text.Encoding.Default.GetString(ms.ToArray());

	//Ensure output is correct
	Assert.IsTrue(output.StartsWith(
             @"Directory does not exist : c:\vfchtugierujkjhsdfsdf\ffklksdfs\jlkjkvvdd1"));

}

There's also a public Validated property in the class to assist with unit testing.

I think I had more fun (and spent more time) with the unit test side of things, than the actual functionality, which was pretty straightforward once you've parsed all of the arguments.

License

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


Written By
Software Developer (Senior) Codebureau
Australia Australia
Started with COBOL/CICS/DB2 - ended up with C#/ASP.NET. I'm a half-geek (or so my wife tells me!)

Comments and Discussions

 
QuestionMultiple File filter Pin
Murugavel Sadagopan9-Jul-15 1:54
Murugavel Sadagopan9-Jul-15 1:54 
QuestionHow to test this application Pin
Jiewei Xu22-Jun-13 7:06
Jiewei Xu22-Jun-13 7:06 
QuestionDeep FilePaths Pin
jasondemont29-Apr-13 8:22
jasondemont29-Apr-13 8:22 
AnswerRe: Deep FilePaths Pin
jasondemont29-Apr-13 8:27
jasondemont29-Apr-13 8:27 
GeneralRe: Deep FilePaths Pin
jasondemont29-Apr-13 8:28
jasondemont29-Apr-13 8:28 
GeneralMissing reference - nunit.framework Pin
Mark Vinogradov11-Apr-11 11:20
Mark Vinogradov11-Apr-11 11:20 
GeneralRe: Missing reference - nunit.framework Pin
CodeBureau - Matt Simner11-Apr-11 12:09
CodeBureau - Matt Simner11-Apr-11 12:09 
GeneralMy vote of 5 Pin
id_cegep14-Jan-11 13:08
id_cegep14-Jan-11 13:08 
GeneralGreat app, nice code, good article Pin
Michael Loughlin29-Jan-09 1:36
Michael Loughlin29-Jan-09 1:36 
QuestionAwesome script Pin
greg cummings29-Sep-08 2:27
greg cummings29-Sep-08 2:27 
AnswerRe: Awesome script Pin
CodeBureau - Matt Simner12-Oct-08 14:05
CodeBureau - Matt Simner12-Oct-08 14:05 
AnswerRe: Awesome script Pin
CodeBureau - Matt Simner12-Oct-08 14:51
CodeBureau - Matt Simner12-Oct-08 14:51 
GeneralCool! Pin
Daniel Liedke10-Sep-07 9:07
professionalDaniel Liedke10-Sep-07 9:07 
Very nice utility, thanks!! Smile | :)



Generaldate format Pin
a.plus.012-Aug-07 11:18
a.plus.012-Aug-07 11:18 
GeneralFeature Request = Except Certain Files Pin
cgoodman19766-Mar-07 11:42
cgoodman19766-Mar-07 11:42 
GeneralThank you! Pin
mkitchin28-Nov-06 13:16
mkitchin28-Nov-06 13:16 
Generalhour argument in TimeFrame Pin
dudik17-Apr-06 3:16
dudik17-Apr-06 3:16 
GeneralWarning extension bug Pin
nate chadwick9-Aug-05 10:20
nate chadwick9-Aug-05 10:20 
GeneralRe: Warning extension bug Pin
CodeBureau - Matt Simner21-Aug-05 2:33
CodeBureau - Matt Simner21-Aug-05 2:33 
Generalswitch Pin
darrylobrien21-Jul-05 9:11
darrylobrien21-Jul-05 9:11 
GeneralRe: switch Pin
CodeBureau - Matt Simner25-Jul-05 1:21
CodeBureau - Matt Simner25-Jul-05 1:21 
Questioncan u del files newr then and older then? Pin
jbu2018-Jun-05 0:49
jbu2018-Jun-05 0:49 
GeneralNeed to reformat the article Pin
Judah Gabriel Himango1-Mar-05 11:39
sponsorJudah Gabriel Himango1-Mar-05 11:39 
GeneralUnable to download files Pin
AlexanderAC17-Feb-05 10:48
AlexanderAC17-Feb-05 10:48 

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

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