Click here to Skip to main content
6,595,854 members and growing! (17,645 online)
Email Password   helpLost your password?
Desktop Development » Files and Folders » Utilities     Intermediate License: The Code Project Open License (CPOL)

DeleteOld : Console Application to Delete Files by Age

By CodeBureau - Matt Simner

A C# console application for deleting files based on age.
C++, C#, Windows, .NET 1.1VS.NET2003, Dev
Posted:12 Feb 2005
Updated:17 Oct 2008
Views:48,598
Bookmarked:45 times
Announcements
Loading...
 
Search    
Advanced Search
Add to IE Search
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
11 votes for this article.
Popularity: 4.10 Rating: 3.94 out of 5
1 vote, 9.1%
1
1 vote, 9.1%
2

3
4 votes, 36.4%
4
5 votes, 45.5%
5

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


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

Other popular Files and Folders articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 16 of 16 (Total in Forum: 16) (Refresh)FirstPrevNext
GeneralGreat app, nice code, good article PinmemberMichael Loughlin2:36 29 Jan '09  
QuestionAwesome script Pinmembergreg cummings3:27 29 Sep '08  
AnswerRe: Awesome script PinmemberCodeBureau - Matt Simner15:05 12 Oct '08  
AnswerRe: Awesome script PinmemberCodeBureau - Matt Simner15:51 12 Oct '08  
GeneralCool! PinmemberDaniel Carvalho Liedke10:07 10 Sep '07  
Generaldate format Pinmembera.plus.0112:18 2 Aug '07  
GeneralFeature Request = Except Certain Files Pinmembercgoodman197612:42 6 Mar '07  
GeneralThank you! Pinmembermkitchin14:16 28 Nov '06  
Generalhour argument in TimeFrame Pinmemberdudik4:16 17 Apr '06  
GeneralWarning extension bug Pinmembernate@promeddx.com11:20 9 Aug '05  
GeneralRe: Warning extension bug PinmemberCodeBureau - Matt Simner3:33 21 Aug '05  
Generalswitch Pinmemberdarrylobrien10:11 21 Jul '05  
GeneralRe: switch PinmemberCodeBureau - Matt Simner2:21 25 Jul '05  
Generalcan u del files newr then and older then? Pinmemberjbu21:49 18 Jun '05  
GeneralNeed to reformat the article PinmemberJudah Himango12:39 1 Mar '05  
GeneralUnable to download files PinmemberAlexanderAC11:48 17 Feb '05  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 17 Oct 2008
Editor: Sean Ewington
Copyright 2005 by CodeBureau - Matt Simner
Everything else Copyright © CodeProject, 1999-2009
Web21 | Advertise on the Code Project