Delete old files by the last access date

Case Study, DOS - Windows Batch Files Add comments

In my professionnal experience I have to manage many PC running with Windows XP. Those box are used for broadcasting some video flow and download lot of media files which aren’t managed by our software. Naturally, I had to find a solution for dynamically purge the lot of useless media files daily generated. As everyone working in system administration (I suppose…), I didn’t have lot of time for doing this job and our software developper couldn’t add the possibility to destroy old files (still due to lot of projects and short timeline…), so I decide to use the Windows schedule (AT) and doing a batch file.

As we had different software version – and for a maximum flexibility in case of any “kidding” evolution of this software – I take care to use arguments to my script. Here by I’ll describe all the process of this script that can be resume in five parts :

  1. Get and check our arguments
  2. Checking some dates (OS date format, maximum vailidity date)
  3. Parsing the directory to purge and get last access date of files
  4. Testing date and delete useless files
  5. Scheduling the batch file


This script is probably not the best way for doing this stuff, if you have suggestion and comments about it don’t hesitate to share with me your feeling ! ;-)

  • Get and check arguments

I decide to use two argument in this batch files, first one for define the directory path to purge (variable PURGE_DIR), the second one for define the minimum day of validity (variable DAY_LIMIT).

IF [%1] == [] ( GOTO :MISSING ) ELSE ( SET PURGE_DIR=%1 )
IF [%2] == [] ( GOTO :MISSING ) ELSE ( SET DAY_LIMIT=%2 )

As you can see, in case of missing arg I use a GOTO and a label MISSING. I use it for displaying a default help message. I think it’s important to do this basical stuff because of the team work ! Many co-workers of mine doesn’t know anything about batch script but touch the box, so it carefullest to display some help messages or error messages.

  • Checking some dates (OS date format, maximum validity date)

Now we have to check the date format of the current Windows. I use the date format dd/MM/yyyy, any other date format could be use. You just have to adapt the code. In my case, most of the computer where install with this date format so I use it. For the misconfigured PC, I just have to go in the panel configuration fo windows for choose the appropriate date format. A nice tips posted by Mike Bikoulis is to define the date delimiter from the reg entry.

FOR /F “skip=4 tokens=3″ %%y IN (‘REG QUERY “HKCU\Control Panel\International” /v sDate’) DO (
SET DATE_DELIM=%%y
)

For checking the date format we have to use the system registry entry HKEY_CURRENT_USER\Control Panel\International\sShortDate. For checking the registry entry I use a FOR loop that skip the 4 first rows and take the third data considering a tab delimiter.

FOR /F “skip=4 tokens=3 delims= ” %%y IN (‘REG QUERY “HKCU\Control Panel\International” /v sShortDate’) DO (
IF NOT “%%y” == “dd%DATE_DELIM%MM%DATE_DELIM%yyyy” GOTO :DATEFORMAT
)

Now that we are sure of our date format we can parse the DATE variable. I split this variable in three other variable YEAR, MONTH, DAY. In second time we check the validity of the day and month number because we can have a value with 1 or 2 characters.

:: SET CURRENT DATE MINUS DAYS ARGUMENT
SET YEAR=%DATE:~6,4%
SET MONTH=%DATE:~3,2%
SET DAY=%DATE:~0,2%

IF %MONTH:~0,1% EQU 0 ( SET MONTH=%MONTH:~1,1% )
IF %DAY:~0,1% EQU 0 ( SET DAY=%DAY:~1,1% )

I use a UNIX timestamp like as basis of my dates comparison, probably due to my habits to script under linux instead of windows. In fact, it’s not a real unix timestamp but just some compute values, but that will be our reference for define the oldest date of validity for a file to delete. We store this value in the variable LIMIT_UNIX_TIMESTAMP.

SET /A LIMIT_UNIX_TIMESTAMP = (((%YEAR% * 365) + (%MONTH% * 31) + %DAY%) * 24 * 60 ) – ( %DAY_LIMIT% * 24 * 60 )

  • Parsing the directory to purge and get last access date of files

We have to parse the directory to purge for define the file list and getting their last access date. For doing this stuff I use again the FOR loop, quite usefull IMHO ! ;-)

Don’t forget to use the option usebackq in case of file name with special character especially spaces. Each time that we get a file we set a variable CURRENT_FILE for a further usage then we pass the file properties to a subroutine named TESTSTRING. This subroutine is in charge to get the last access date of the file then send it to the finall subroutine that compare dates and delete old files.

FOR /F “usebackq delims=” %%a IN (`DIR /A:-D /B /S %PURGE_DIR%`) DO (

SET CURRENT_FILE=%%a
IF EXIST “%%a” (

FOR /F “usebackq skip=5 tokens=1″ %%b IN (`DIR /T “%%a”`) DO (
CALL :TESTSTRING %%b
)
)
)
GOTO :END

:TESTSTRING
FOR /F “tokens=1-3 delims=” %%c IN (“%1″) DO (
IF NOT [%%e] == [] (
CALL :TESTDATE %%e %%d %%c
)
)

GOTO :END

  • Testing date and delete useless files

The last but not the less… We have to define a new variable that we’ll use in our UNIX like date comparison. So, we define a FILE_UNIX_TIMESTAMP variable with the parameters passed to the subroutine TESTDATE. We compare FILE_UNIX_TIMESTAMP and LIMIT_UNIX_TIMESTAMP with a simple IF structure. Before delete file we still check that the file still exist, if it’s the case we can delete the file by using the command DEL. We could use ERASE command but this command delete also subdirectories. This is really dangerous if used without precaution and could be really damageable !

:TESTDATE

SET YEAR=%1
SET MONTH=%2
SET DAY=%3

IF %MONTH:~0,1% EQU 0 ( SET MONTH=%MONTH:~1,1% )
IF %DAY:~0,1% EQU 0 ( SET DAY=%DAY:~1,1% )

SET /A FILE_UNIX_TIMESTAMP = ((%YEAR% * 365) + (%MONTH% * 31) + (%DAY% * 1 )) * 24 * 60

IF %FILE_UNIX_TIMESTAMP% LSS %LIMIT_UNIX_TIMESTAMP% (
IF EXIST “%CURRENT_FILE%” (
ECHO DELETE “%CURRENT_FILE%”
DEL /Q /F “%CURRENT_FILE%”
)
)

GOTO :END

  • Windows Schedule and coffee time !


Job is soon done, we juste have to put our batch file in a windows schedule (for my part it’s a daily schedule). Don’t forget to give the two arguments to the script when configuring your schedule.

This script can be really usefull if you have to manage lot of Windows XP computer on which you have to purge some directories and you don’t want to go further in the windows possibilities (especially if you are most a linux user than windows user !).

Now you can go for your hourly coffee, and Voilà ! ;-)

Follow is the full source code with some comments.

::BATCH FILE FOR DELETE OLD FILES BY LAST ACCESS DATE
::AUTHOR "Nicolas Brousse"
@ECHO OFF

:: FIRST ARGS MUST BE THE PATH TO THE DIRECTORY TO PURGE
:: SECOND ARGS MUST BE A NUMBER OF DAYS
IF [%1] == [] ( GOTO :MISSING ) ELSE ( SET PURGE_DIR=%1 )
IF [%2] == [] ( GOTO :MISSING ) ELSE ( SET DAY_LIMIT=%2 )

:: WE GET THE PC LOCAL DATE DELIMITER (contrib from Mike Bikoulis)
FOR /F "skip=4 tokens=3" %%y IN ('REG QUERY "HKCU\Control Panel\International" /v sDate') DO (
SET DATE_DELIM=%%y
)

:: WE CHECK THE PC LOCAL SHORT DATE FORMAT
FOR /F "skip=4 tokens=2* delims=     " %%y IN ('REG QUERY "HKCU\Control Panel\International" /v sShortDate') DO (
IF NOT "%%z" == "dd%DATE_DELIM%MM%DATE_DELIM%yyyy" GOTO :DATEFORMAT
)

:: SET CURRENT DATE MINUS DAYS ARGUMENT
SET YEAR=%DATE:~6,4%
SET MONTH=%DATE:~3,2%
SET DAY=%DATE:~0,2%

IF %MONTH:~0,1% EQU 0 ( SET MONTH=%MONTH:~1,1% )
IF %DAY:~0,1% EQU 0 ( SET DAY=%DAY:~1,1% )

SET /A LIMIT_UNIX_TIMESTAMP = (((%YEAR% * 365) + (%MONTH% * 31) + %DAY%) * 24 * 60 ) - ( %DAY_LIMIT% * 24 * 60 )

:: PARSE THE DIRECTORY TO PURGE THEN CHECK FILES ON THEIR LAST ACCESS DATE
FOR /F "usebackq delims=" %%a IN (`DIR /A:-D /B /S %PURGE_DIR%`) DO (
SET CURRENT_FILE=%%a
IF EXIST "%%a" (

ECHO ---------------------------------------------------------
ECHO TEST "%%a"

FOR /F "usebackq skip=5 tokens=1" %%b IN (`DIR /T "%%a"`) DO (
CALL :TESTSTRING %%b
)
)
)

GOTO :END

:: GET FILE LAST ACCESS DATE
:TESTSTRING
FOR /F "tokens=1-3 delims=%DATE_DELIM%" %%c IN ("%1") DO (
IF NOT [%%e] == [] (
CALL :TESTDATE %%e %%d %%c
)
)
GOTO :END

:: TEST DATE WITH LIMIT_UNIX_TIMESTAMP
:: DELETE TOO OLD FILES, WE COULD DO AN ERASE BUT IT'S LESS SECURE
:TESTDATE

SET YEAR=%1
SET MONTH=%2
SET DAY=%3

IF %MONTH:~0,1% EQU 0 ( SET MONTH=%MONTH:~1,1% )
IF %DAY:~0,1% EQU 0 ( SET DAY=%DAY:~1,1% )

SET /A FILE_UNIX_TIMESTAMP = ((%YEAR% * 365) + (%MONTH% * 31) + (%DAY% * 1 )) * 24 * 60

ECHO    FILE_UNIX_TIMESTAMP  = %FILE_UNIX_TIMESTAMP%
ECHO    LIMIT_UNIX_TIMESTAMP = %LIMIT_UNIX_TIMESTAMP%

IF %FILE_UNIX_TIMESTAMP% LSS %LIMIT_UNIX_TIMESTAMP% (
IF EXIST "%CURRENT_FILE%" (
ECHO DELETE "%CURRENT_FILE%"
DEL /Q /F "%CURRENT_FILE%"
)
)

GOTO :END

:MISSING
ECHO USE : %0 [PATH] [DAYS]
GOTO :END

:DATEFORMAT
ECHO THIS PC HAS MUST HAVE A SHORT DATE FORMAT LIKE dd%DATE_DELIM%MM%DATE_DELIM%yyyy
GOTO :END

:END
  • dth
    try this instead, to delete files older than 10 days:

    forfiles -p "<path>" -s -m *.* -d -10 -c "cmd /C del @FILE"
  • neglas
    Great script. Does just what I needed, and worked instantly. Thanks!
  • Vonkee
    Hi,

    How can I modify the code to filter only certain files only, e.g. Excel file *.xls?

    Thanks
  • Petri
    Hi,

    Could this same code be used (with modification of course) to check number of folders and output when was the last time a file was modified inside each folder?

    THanks,

    Petri
  • Decoy
    IMHO, exceptions (immune dirs) is also a good idea. :)
  • Decoy
    Thanks!
    Works for me after "skip=4" was removed on line 11 (getting date delimiter).
    BTW, is there any way to remove empty dirs just after purge is comleted?
  • Carlos
    Thanks a lot! xD
  • Steini
    To handle unicode text in logfiles you can start CMD.EXE with /U parameter !!! that way I could print out (>>) strange filenames to text and email it.
  • Sergey
    Thank you for this script. I have changed it to move too stable files from writable for all users directory on my fileserver to other not shared folders, named by date (to force users keep such files in theirs personal dirs)

    It seems that string
    FOR /F "usebackq skip=5 tokens=1" %%b IN (`DIR /T "%%a"`) DO (
    should be replaced with
    FOR /F "usebackq skip=5 tokens=1" %%b IN (`DIR /T:A "%%a"`) DO (
    to get last accessed time instead of default creation time. But script seems to immediately modify last accessed time by itself. :( So it becomes useless. I'v got to use flag /T:W instead.

    "4 was unexpected at this time" - i had the same errors until i replaced " and ' symbols.

    Command chcp might be useful, if filename contains symbols in national codepages. For example i wrote "chcp 1251" in the beginning and "chcp 437" at the end of my script.

    One should replace
    FOR /F “skip=4 tokens=3″ %%y IN (’REG QUERY
    with
    FOR /F “skip=2 tokens=3″ %%y IN (’REG QUERY
    in both cases on Windows Server 2003.

    FOR /F “skip=4 tokens=2*″ %%y IN (’REG QUERY did not worked for me while
    FOR /F “skip=4 tokens=3″ %%y IN (’REG QUERY does.
  • Davelop
    Hi,

    I have an error too with an French XP pro version :
    "4 était inattendu"

    Is this not a problem with the quote and apostrophe character(“ or ″ or ’...) in the code ?
    ex: in “skip=4 tokens=3″ the two quote are not the same

    Thanks a lot
  • chris
    Hi,

    I made some minor modifications...works well but one first attempt to run the code it doesnt catch the filename and therefore doesnt work. For many attempts afterwords it seems to work just fine...any suggestions?
  • Richard
    I use Windows XP pro and the sDate value on my system is simply backslash (the separator), however I have a HKCU variable called sShortDate, and that value on my system is set to dd/mm/yyyy.

    The code you quote ...
    :: WE GET THE PC LOCAL DATE DELIMITER (contrib from Mike Bikoulis)
    FOR /F “skip=4 tokens=3″ %%y IN (’REG QUERY “HKCU\Control Panel\International” /v sDate’) DO (
    SET DATE_DELIM=%%y

    ...

    why are you skipping tokens when there is just a single value in this sDate field? This code should only be used in fields like sShortDate. I think you have a cut and paste error there?
  • Christine, you should probably change the "skip" value for parsing the REG QUERY of the return Date (cf. Mike's comment). This value depends of your Windows Version.
  • Christine
    I keep getting an error "0 was expected at this time"
    Please, what will I do?
    Thanks!
  • Jing Qu
    Mike,

    Could you send me a text format code for this "deleteoldfiles", I modified the quote, still don't work. Error is like "Panel'\International" /v sDate') DO ( was unexpected at this time."
    Thank you,
    jing.qu@gmail.com
  • Mike Bikoulis
    It seems that the amount of rows to skip when parsing the REG QUERY output is operating system dependent. On XP it is 4 (as currently in the script) but I found out that on Windows Server 2003 I need a value of 2.

    Cheers,

    Mike
  • ROHAN REKHI
    Hi,

    Thanks for your detail response.But I don't curretly have any C# compiler to compile the C# code.I will need to stick with the Batch file.
  • SMorgan
    Works much better as a C# console app and is not very hard. Here is the code:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.IO;

    namespace DeleteOldFiles
    {
    class Program
    {
    static void Main(string[] args)
    {
    int n;
    // FileInfo[] yFI;
    bool bTestMode;
    bool bRecurse;

    bTestMode = true;
    bRecurse = false;

    if (args.Length == 0
    || (args.Length > 0 ? args[0] : string.Empty) == string.Empty
    || int.TryParse(args.Length > 1 ? args[1] : string.Empty, out n) == false)
    {
    Console.WriteLine("Usage: DeleteOldFiles [] [ = Recurse Folders");
    Console.WriteLine(" Deletes files from older than .");
    }
    else
    {
    foreach (string s in args)
    {
    if (s.ToLower() == "really")
    bTestMode = false;
    if (s.ToLower() == "recurse")
    bRecurse = true;
    }
    Console.WriteLine("Delete files in " + args[0] + " which are older than " + args[1] + " days.");
    Console.WriteLine(bTestMode?" ** Test Mode - no files will be deleted":"");
    Console.WriteLine(bRecurse?" ** RECURSION requested - entire tree will be processed!.":"");

    // + (bTestMode?"Test Mode - no files will be deleted/r/n":"")
    // + (bRecurse?"RECURSION requested - entire tree will be scanned.":""));

    DeleteOldFiles.Program.ProcessFolder(args[0], Convert.ToInt32(args[1]), bTestMode);
    }
    }

    private static void ProcessFolder(string sFolder, int nDays, bool bTestMode)
    {
    FileSystemInfo[] yFI;
    DirectoryInfo DI;
    DI = null;
    yFI = null;

    try
    {
    DI = new DirectoryInfo(sFolder);
    yFI = DI.GetFileSystemInfos();
    }
    catch (Exception ex)
    {
    Console.Write("\r\n ERROR: Could not find path: " + sFolder + "\r\n Msg: " + ex.Message);
    return;
    }

    foreach (FileSystemInfo FIL in yFI)
    {
    // if ((FileAttributes)FIL.Attributes && FileAttributes.Directory > 0)
    if (FIL.Attributes.CompareTo(FileAttributes.Directory) == 0 )
    ProcessFolder(FIL.FullName, nDays, bTestMode);
    else
    {
    int nAge;
    nAge = ((TimeSpan)(DateTime.Today - Convert.ToDateTime(FIL.LastWriteTime))).Days;

    if (nAge > nDays)
    {
    Console.WriteLine("\r\n==> File: " + FIL.FullName);
    Console.WriteLine(" File is " + nAge.ToString() + " days old and will be DELETED.");
    if (bTestMode)
    Console.WriteLine(" TESTMODE, no deletion carried out.");
    else
    {
    Console.WriteLine(" File has been deleted...");
    FIL.Delete();
    }
    }
    }
    }
    }
    }
    }
  • ROHAN REKHI
    I am running it exactly as it has to be as follows:

    sample c:\working\ 10

    But still I keep getting the error
    4 was unexpected at this time.

    Please advice.Your help is appriciated here.
  • Do you have more information about the error ? It's little obscure... :-)

    You should run the script like that : script.bat arg1 arg2
    Where arg1 is the path to the directory to purge, and arg2 for the minimum day of validity.
  • ROHAN REKHI
    While I try to run the query I get the error

    4 was not expected at this time.

    Could somebody please advice.
  • ROHAN REKHI
    Hi,

    This is really nice.I can use it in archiving the log files in Weblogic Box.I have only one question.How should I pass the arguments to the script .Is it something like

    C:\Working 10
  • You have to adapt this code but you can use it for such action. I'll try to post an example in the coming week...
  • rm.fish
    Q: what if i want to find & move a last modified date directories to other place. Can i use these code?
  • DJC
    This was really helpful! I modified the code for the variations of months (not all 31) and can live with the one day off when we have leap year. THANKS!
  • sin
    Ok, I fixed quotes. It was a quest to find that ` is required in FOR IN (`command`) loop (` can not be replaced with ').

    I have problems with registry query loops. They both say "File not found `REG". I solved this inserting "usebackq" into both FOR options.
  • sin
    Wow! Just what I'm looking for! Copy/paste/save do not work because of unicode quotes, have to replace...
  • In fact, the calculate timestamp is just a fake :-) for having a simple date comparison between two dates.

    The real meaning of Unix Timestamp is the number of seconds elapsed since midnight UTC on the morning of January 1, 1970.

    Doing a such convert in batch doesn't seem to be useful in our script as we keep the same algorithm all the time. So, It's just a simple way to compare two dates by their approximate equivalence in seconds.

    The variables name is misleading. Sorry :-)
  • Olly Thomas
    Great script - Just a question - does the unix time stamp format always assume 31 days in a month, and 365 days in a year? what happens about 30 day months and leap years? Thanks! Olly.
  • Thank you Mike for your contrib ! I add your fixes in the main post for more legibility of the full post.

    What I notice was :
    - Suggest use of a date delimiter
    - Bug fix in a missing goto
    - Bug fix in the for loop of the check date
  • Mike Bikoulis
    This was really useful! Found some small bugs which I've corrected in the code attached below:

    [ ... SNIP ... ]
blog comments powered by Disqus
WP Theme & Icons by N.Design Studio
Entries RSS Comments RSS Log in