Click here to Skip to main content
15,886,567 members
Articles / Programming Languages / C#

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.7K   1.6K   48  
A C# console application for deleting files based on age.
using System;
using System.IO;
using System.Text.RegularExpressions;
using System.Text;

namespace CodeBureau.DeleteOld
{
	/// <summary>
	/// Console Application to delete files older than a specified age.
	/// </summary>
	public class DeleteOld
	{

		#region Private Variables

		private Arguments	    arguments;
		private bool		    validated			        = false;
		private bool		    recurseSubFolders	        = false;
		private bool		    removeEmptyFolders          = false;
        private bool            dontRemoveEmptyRootFolder   = false;
		private bool		    showHelp			        = false;
        private bool            deleteNewer                 = false;
		private string		    path				        = Environment.CurrentDirectory;
		private string		    filter				        = "*.*";
		private int			    age				            =   int.MaxValue;
		private string		    timeFrame			        = AgeIncrement.Day;
		private bool		    quietMode			        = false;
		private bool		    simulateOnly		        = false;
        private DateTime        absoluteDate;
		private DateTime	    deleteBaselineDate;
		private DateTime	    runTime;
		private StreamWriter	outputStream;
		private StreamWriter	errorOutputStream;

		#endregion

		#region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="DeleteOld"/> class.
        /// </summary>
        /// <param name="args">The arguments.</param>
		public DeleteOld(Arguments args)
		{
			outputStream = new StreamWriter(Console.OpenStandardOutput());
			outputStream.AutoFlush = true;
			errorOutputStream = new StreamWriter(Console.OpenStandardError());
			errorOutputStream.AutoFlush = true;

			arguments = args;
			if (!ParseArguments() || showHelp) 
			{
				ShowUsage();
				validated = false;
			}
			else 
				validated = true;
				
		}

        /// <summary>
        /// Initializes a new instance of the <see cref="DeleteOld"/> class.
        /// </summary>
        /// <param name="args">The args.</param>
        /// <param name="outputStream">The output stream (used primarily for unit testing).</param>
        /// <param name="errorOutputStream">The error output stream (used primarily for unit testing).</param>
		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;

			arguments = args;
			if (!ParseArguments() || showHelp) 
			{
				ShowUsage();
				validated = false;
			}
			else 
				validated = true;

		}

		#endregion

		#region Public Static Methods 

		/// <summary>
		/// Program entry point
		/// </summary>
		/// <param name="args"></param>
		/// <returns></returns>
		public static int Main(string[] args) 
		{
			DeleteOld app = new DeleteOld(new Arguments(args));
			if (app.validated) 
			{
				return app.ProcessCommand();
			}
			else
				return -1;
		}

        /// <summary>
        /// Get's a usage string for the application.
        /// </summary>
        /// <returns></returns>
		public static string Usage() 
		{
			
			StringBuilder sb = new StringBuilder();
			sb.Append("\n");
			sb.Append("Deletes files older/newer than a specified age\n\n");
            sb.Append("DeleteOld [-p] [-f] -a|-d [-t] [--s] [--r] [--nr] [--w] [--q] [--n]\n\n");
			sb.Append("-p\tSpecifies path to search (enclose long paths in \"\").  \n\tDefault is current directory.\n");
			sb.Append("-f\tSpecifies filter to search.  Default is *.*\n");
			sb.Append("-a\tYoungest age of files to delete.  Used in conjunction with TimeFrame.\n");
			sb.Append("-t\tTimeFrame.  One of the following:\n");
			sb.Append("\t\ts = Second\tn = Minute\th = Hour\n");
			sb.Append("\t\td = day\t\tm = Month\ty = Year\n");
			sb.Append("\tDefault is d (Day).\n");
            sb.Append("-d\tAbsolute Date (overrides -a and -t).  Can be any valid datetime string.\n");
			sb.Append("--s\tRecurse Sub Folders.  Default is Off.\n");
			sb.Append("--r\tRemove empty folders.  Default is Off.\n");
            sb.Append("--nr\tDo not remove empty root folder (if --r used).  Default is Off.\n");
            sb.Append("--w\tDelete newer (deletes files 'newer' than supplied timeframe).\n");
            sb.Append("--q\tQuiet Mode (this suppresses any output).\n");
			sb.Append("--n\tDo nothing - simulate only what 'would occur'.\n\n");
            sb.Append("e.g. \tThe following deletes all files matching *.dat in c:\\temp older than \n\t(last modified) 10 days ago (based on current Date and Time).\n\n");
			sb.Append("\tIt also recurses and removes any empty folders within the tree and pipes\toutput to a text file.\n\n");
			sb.Append("DeleteOld -p \"C:\\temp\\dev\" -f *.dat -a 10  -t d --s --r >c:\\out.txt\n\n");
			
			return sb.ToString();

		}

		#endregion

		#region Private Methods

		/// <summary>
		/// Parse input arguments for completeness
		/// </summary>
		/// <returns></returns>
		private bool ParseArguments() 
		{

			//Help
			if (String.Compare(arguments[ArgumentNames.Help], bool.TrueString, true) == 0)
			{
				showHelp = true;
				return false;
			}

			//Quiet
			if (String.Compare(arguments[ArgumentNames.Quiet], bool.TrueString, true) == 0)
				quietMode = true;

			//Simulate Only
			if (String.Compare(arguments[ArgumentNames.Nothing], bool.TrueString, true) == 0)
				simulateOnly = true;

			//Mandatory Parameters
			//Age
            if (arguments[ArgumentNames.Age] != null) 
			{
				try 
				{
					age = int.Parse(arguments[ArgumentNames.Age]); 
					if (age < 0 || age == int.MaxValue)
						return false;
				}
				catch (Exception) 
				{
					if (! quietMode)
						errorOutputStream.WriteLine("Age was invalid");
					return false;
				}
			}

            //Absolute Date
            if (arguments[ArgumentNames.Date] != null)
            {
                if (!DateTime.TryParse(arguments[ArgumentNames.Date], out absoluteDate))
                {
                    errorOutputStream.WriteLine("Absolute Date '{0}' was invalid", arguments[ArgumentNames.Date]);
                    return false;
                }

            }

            if (arguments[ArgumentNames.Date] == null && arguments[ArgumentNames.Age] == null)
            {
                if (!quietMode)
                    errorOutputStream.WriteLine("Age or Absolute Date must be supplied");
                return false;
            }

			//Optional Parameters
			//Path
			if (arguments[ArgumentNames.Path] != null)
				path = arguments[ArgumentNames.Path];

			if (!Directory.Exists(path)) 
			{
				errorOutputStream.WriteLine("Directory does not exist : {0}", path);
				return false;
			}

			//Filter
			if (arguments[ArgumentNames.Filter] != null)
				filter = arguments[ArgumentNames.Filter];

			//TimeFrame
			if (arguments[ArgumentNames.AgeIncrement] != null) 
			{
				if (Regex.IsMatch(arguments[ArgumentNames.AgeIncrement], @"[s|n|h|d|m|y]{1}"))				
					timeFrame = arguments[ArgumentNames.AgeIncrement];
				else 
				{
					if (! quietMode)
						errorOutputStream.WriteLine("Time Frame was invalid");
					return false;
				}
			}

			//Recurse SubFolders
			if (String.Compare(arguments[ArgumentNames.Recurse], bool.TrueString, true) == 0)
				recurseSubFolders = true;

			//Remove Empty Folders
			if (String.Compare(arguments[ArgumentNames.RemoveEmptyFolders], bool.TrueString, true) == 0)
				removeEmptyFolders = true;

            //Remove Empty Root Folder
            if (String.Compare(arguments[ArgumentNames.RemoveEmptyRootFolder], bool.TrueString, true) == 0)
                dontRemoveEmptyRootFolder = true;


            //Delete Newer (rather than older than)
            if (String.Compare(arguments[ArgumentNames.DeleteNewer], bool.TrueString, true) == 0)
                deleteNewer = true;


			//Check for 'any' invalid arguments
			foreach(string arg in arguments.InnerStringDictionary.Keys) 
			{
				if (! Regex.IsMatch(arg, String.Format("[{0}|{1}|{2}|{3}|{4}|{5}|{6}|{7}|{8}|{9}|{10}|{11}]", ArgumentNames.Path,
					ArgumentNames.Age,
					ArgumentNames.AgeIncrement,
					ArgumentNames.Filter,
					ArgumentNames.Help,
					ArgumentNames.Nothing,
					ArgumentNames.Quiet,
					ArgumentNames.Recurse,
					ArgumentNames.RemoveEmptyFolders,
                    ArgumentNames.RemoveEmptyRootFolder,
                    ArgumentNames.Date,
                    ArgumentNames.DeleteNewer)))
				{
					errorOutputStream.WriteLine("Invalid argument specified : {0}", arg);
					return false;
				}

			}

			return true;
		}

		/// <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;
		}

		/// <summary>
		/// Deletes the files (potentially recursively) from given folder.
		/// </summary>
		/// <param name="folder">Folder.</param>
		/// <returns></returns>
		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;
		}
											
		/// <summary>
		/// Shows the usage string.
		/// </summary>
		private void ShowUsage() 
		{
			outputStream.Write(Usage());
		}

		#endregion

		#region Public Methods

		/// <summary>
		/// Process the console command
		/// </summary>
		/// <returns></returns>
		public int ProcessCommand() 
		{
			if (Directory.Exists(path)) 
			{
                string deleteType = !deleteNewer ? "older" : "newer";
				runTime = DateTime.Now;
				SetDeleteTimeBenchmark();
				if (! quietMode) 
				{
					if (simulateOnly) 
						outputStream.WriteLine("SIMULATION ONLY:\nDeleting {0} from {1} {3} than {2}", filter, path, deleteBaselineDate.ToString("F"), deleteType);
					else
						outputStream.WriteLine("Deleting {0} from {1} {3} than {2}", filter, path, deleteBaselineDate.ToString("F"), deleteType);
				}
				return DeleteFilesFromFolder(new DirectoryInfo(path), simulateOnly);
			}
			else
				errorOutputStream.WriteLine("Directory does not exist : {0}", path);
				ShowUsage();
				return -1;

		}

		#endregion

		#region Public Attributes

		/// <summary>
		/// Return whether the parameters validated OK
		/// </summary>
		public bool Validated 
		{
			get {return validated;}
		}

		#endregion

	}

	#region Constants

	/// <summary>
	/// Argument Names constants
	/// </summary>
	public class ArgumentNames
	{
		private ArgumentNames() {}

		public static readonly string Path = "p";
		public static readonly string Recurse = "s";
		public static readonly string Help = "h";
		public static readonly string RemoveEmptyFolders = "r";
		public static readonly string Age = "a";
		public static readonly string AgeIncrement = "t";
		public static readonly string Filter = "f";
		public static readonly string Quiet = "q";
		public static readonly string Nothing = "n";
        public static readonly string RemoveEmptyRootFolder = "nr";
        public static readonly string Date = "d";
        public static readonly string DeleteNewer = "w";

	}

	/// <summary>
	/// Age Increment (TimeFrame) constants
	/// </summary>
	public class AgeIncrement
	{
		private AgeIncrement() {}

		public static readonly string Second = "s";
		public static readonly string Minute = "n";
		public static readonly string Hour = "h";
		public static readonly string Day = "d";
		public static readonly string Month = "m";
		public static readonly string Year = "y";

	}

	#endregion

}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

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