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

DeleteOld : Console Application to Delete Files by Age

, 17 Oct 2008
Rate this:
Please Sign up or sign in to vote.
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.

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

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

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.

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:

[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)

About the Author

CodeBureau - Matt Simner
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

 
QuestionHow to test this application PinmemberJiewei Xu22-Jun-13 7:06 
QuestionDeep FilePaths Pinmemberjasondemont29-Apr-13 8:22 
AnswerRe: Deep FilePaths Pinmemberjasondemont29-Apr-13 8:27 
GeneralRe: Deep FilePaths Pinmemberjasondemont29-Apr-13 8:28 
GeneralMissing reference - nunit.framework PinmemberMark Vinogradov11-Apr-11 11:20 
GeneralRe: Missing reference - nunit.framework PinmemberCodeBureau - Matt Simner11-Apr-11 12:09 
GeneralMy vote of 5 Pinmemberid_cegep14-Jan-11 13:08 
GeneralGreat app, nice code, good article PinmemberMichael Loughlin29-Jan-09 1:36 
QuestionAwesome script Pinmembergreg cummings29-Sep-08 2:27 
AnswerRe: Awesome script PinmemberCodeBureau - Matt Simner12-Oct-08 14:05 
AnswerRe: Awesome script PinmemberCodeBureau - Matt Simner12-Oct-08 14:51 
GeneralCool! PinmemberDaniel Carvalho Liedke10-Sep-07 9:07 
Generaldate format Pinmembera.plus.012-Aug-07 11:18 
GeneralFeature Request = Except Certain Files Pinmembercgoodman19766-Mar-07 11:42 
GeneralThank you! Pinmembermkitchin28-Nov-06 13:16 
Generalhour argument in TimeFrame Pinmemberdudik17-Apr-06 3:16 
small bug in the argument parsing in line 207
instead of "if (Regex.IsMatch(_arguments[ArgumentNames.AgeIncrement], @"[s|n|d|m|y]{1}")) "
should be
if (Regex.IsMatch(_arguments[ArgumentNames.AgeIncrement], @"[s|n|h|d|m|y]{1}"))
 
the hour (h) was missing ....
 

GeneralWarning extension bug Pinmembernate@promeddx.com9-Aug-05 10:20 
GeneralRe: Warning extension bug PinmemberCodeBureau - Matt Simner21-Aug-05 2:33 
Generalswitch Pinmemberdarrylobrien21-Jul-05 9:11 
GeneralRe: switch PinmemberCodeBureau - Matt Simner25-Jul-05 1:21 
Questioncan u del files newr then and older then? Pinmemberjbu218-Jun-05 0:49 
GeneralNeed to reformat the article PinmemberJudah Himango1-Mar-05 11:39 
GeneralUnable to download files PinmemberAlexanderAC17-Feb-05 10:48 

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 | Mobile
Web04 | 2.8.140721.1 | Last Updated 17 Oct 2008
Article Copyright 2005 by CodeBureau - Matt Simner
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid