Hey, Scripting Guy! How Can I Use Windows PowerShell to Delete All the Files in a Folder Older Than 90 Days?

Hey, Scripting Guy! How Can I Use Windows PowerShell to Delete All the Files in a Folder Older Than 90 Days?

  • Comments 11
  • Likes

Hey, Scripting Guy! Question

Hey, Scripting Guy! In Windows PowerShell, how can I determine the number of days difference between two dates? I want to be able to delete all the files in a folder that are more than 90 days old.

-- JN

SpacerHey, Scripting Guy! AnswerScript Center

Hey, JN. You know, today is October 31st and, in the US at least, it’s the day when the dark underworld takes over: witches parade through the streets, cackling and laughing; ghosts and goblins cavort on the front lawn; devils and demons come up and ring your doorbell; it’s the one day out of the year when evil truly reigns supreme. In other words, it’s – no, it’s not the Scripting Editor’s birthday (although that would explain a lot, wouldn’t it?). Instead, it’s Halloween.

Note. We just double-checked and it’s not the Scripting Editor’s birthday; her birthday is in July. For those of you who like to plan ahead, however, she is beginning to run low on eye of newt and toe of frog, although she seems to still have plenty of wool of bat and tongue of dog.

In the US, Halloween is marked by children dressing up in costumes (the scarier the better) and going around ringing peoples’ doorbells and asking for candy. You know, we’re going to go triple-check that date. Now that we think about it, we’re pretty sure that’s exactly what the Scripting Editor did on her last birthday.

For those of you unfamiliar with the holiday, the primary symbol of Halloween is the jack-o-lantern, a symbol you make by hollowing out a pumpkin, carving a face into the hollowed-out gourd, then placing a lighted candle inside. According to legend, the jack-o-lantern got its start many, many years ago, when a drunkard and ne’er do well named Jack tricked the Devil into climbing a tree; once the Devil was safely ensconced in the branches, Jack carved a cross into the tree trunk, something that prevented the Devil from climbing down. Jack agreed to help the Devil down on one condition: that the Devil would never bother him again.

A few years later Jack died, and when he went up to heaven he was denied entrance because of his wicked ways. That sent him … downstairs … where he was refused entrance because he had previously tricked the Devil. As a result, Jack was condemned to roam the earth forever although, as a sort of consolation prize, the Devil did give him a lighted candle to help him make his way through the dark.

You know what? We’re going to quadruple-check that date. That story sounds like it was copied, word-for-word, from the Scripting Editor’s resume.

At any rate, and in the spirit of the occasion, the Scripting Guy who writes this column decided to do something frightening and horrifying for today’s column. And what could be more frightening or more horrifying than a Windows PowerShell script that deletes all the files in a folder more than 90 days old?!?

Well, as it turns out, just about everything is more frightening than that:

$a = Get-ChildItem C:\Scripts
foreach($x in $a)
    {
        $y = ((Get-Date) - $x.CreationTime).Days
        if ($y -gt 90 -and $x.PsISContainer -ne $True)
            {$x.Delete()}
    }

Before we explain how this works, let’s look at a simpler example, one that merely subtracts two dates. Here’s what that sample script looks like:

$x = Get-Date
$y = Get-Date "1/1/2007"
$x - $y

As you can see, there’s nothing very complicated about this particular script. In the first line, we use the Get-Date cmdlet to retrieve the current date and time and store it in a variable named $x. In line 2, we use the Get-Date cmdlet to retrieve a date-time value equivalent to January 1, 2007. Note that we didn’t specify a time when retrieving this second value; consequently, our date-time instance will be given a default time of 12:00:00 AM. What if we wanted to specify a particular time? No problem; as the good folks at Nike would say, just do it:

$y = Get-Date "1/1/2007 9:45 PM"

OK, so we have two date-time values, $x and $y; now what? Well, if we want to determine the time interval between these two dates all we have to do is subtract one from the other. Subtract $y from $x and look what you get back:

Days              : 302
Hours             : 9
Minutes           : 27
Seconds           : 47
Milliseconds      : 515
Ticks             : 261268675156250
TotalDays         : 302.394299949363
TotalHours        : 7257.46319878472
TotalMinutes      : 435447.791927083
TotalSeconds      : 26126867.515625
TotalMilliseconds : 26126867515.625

Right there, at the top of the list, is the value we’re looking for; it turns out that there are 302 days between the current date (October 30, 2007, the day this column was written) and January 1, 2007. Not bad, huh?

Well, OK, you’re right: it’s not bad, but it’s not necessarily all that good, either. That’s because we have a lot of extraneous property values (Hours, Minutes, Second, Milliseconds, etc.) that we aren’t interested in. But that’s all right; let’s modify the last line of our script and see what happens:

($x - $y).Days

What have we done here? Well, we’re still subtracting $y from $x; the only difference is that, this time, we’re performing this little bit of date arithmetic inside a pair of parentheses. Why? Well, we need to do two things here. First, we need to subtract one date value from another ($x - $y); that’s going to give us an instance of the .NET Framework class System.TimeSpan.

Second, we want to display just the value of the Days property for that TimeSpan object. To ensure that we first get the TimeSpan object and only then try to retrieve the value of the Days property we enclose our little arithmetical equation inside a set of parentheses. PowerShell always carries out commands in parentheses before it does anything else; thus it’s going to perform the date calculation first and then, once it has the TimeSpan object, go out and retrieve the value of the Days property.

Will that really work? See for yourself:

302

Now, at long last, we’re ready to talk about the script that deletes all the files in a folder that are more than 90 days old. That script starts out by using the Get-ChildItem cmdlet to retrieve a collection of all the objects found in the folder C:\Scripts, storing that collection in a variable named $a:

$a = Get-ChildItem C:\Scripts

Note. What’s that? Some of you would like to perform this same task against all the files in any subfolders of C:\Scripts as well? Okey-doke; in that case, just add the –recurse parameter to your Get-ChildItem command:

$a = Get-ChildItem C:\Scripts -recurse

Next we set up a foreach loop to loop through all the items in our collection. Inside that loop, the first thing we do is this:

$y = ((Get-Date) - $x.CreationTime).Days

When we first showed you this script you might have looked at this line of code and said, “Uh-oh.” Now, however, you should be able to see exactly what we’re doing here.

So what are we doing here? Well, for starters, we’re using Get-Date to retrieve the current date and time. (Note that Get-Date is in a nested pair of parentheses; that ensures that we get the current date and time before we do anything else.) Once we have the current date and time we then grab the value of the first item’s CreationTime property and subtract that from the value retrieved by Get-Date. That equation, as we know, is going to return a TimeSpan object; once we have that object, we then skim off the value of the Days property and store that value in a variable named $y.

And you’re right: it only sounds complicated. In reality, it’s fairly straightforward.

That brings us to this line of code:

if ($y -gt 90 -and $x.PsISContainer -ne $True)

There are two things going on here. First, the variable $y tells us the number of days between the current date and time and the date and time that the first item in our collection was created. What we want to do is check to see if this value is greater than (-gt) 90; if it is, then this item is a candidate for deletion.

Why just a “candidate” for deletion? Well, we only want to delete files; we don’t want to delete folders. That’s where this little bit of code comes in:

$x.PsISContainer -ne $True

Here we’re simply making sure that the PsISContainer property is not equal to (-ne) True (represented by the PowerShell built-in variable $True). If PsISContainer is true then that means we’re working with a folder, and we don’t want to work with (or delete) folders. If PsISContainer is False, then that’s fine: that means we’re dealing with a file.

So suppose we do have a file that happens to be more than 90 days old; what then? Well, in that case, all we have to do is call the Delete method and deleted the thing:

{$x.Delete()}

And then it’s back to the top of the loop, where we repeat the process with the next item in the collection.

Now, admittedly, we might have been able to simplify this script a tiny bit. (Or maybe not; that’s more an aesthetic judgment than anything else.) However, we went this particular route so we could kill two birds with one stone: we could show you the basics of PowerShell date arithmetic and we could delete all the old files in a folder. Consider this our Halloween treat to the scripting world: two for the price one.

Note. No, we didn’t really kill two birds with one stone. That was all done with special effects that combined cleverly-costumed stunt men and computer animation.

Anyway, we hope that answers your question, JN. As for the Scripting Guy who writes this column, he’s going to go home and hand out candy to the trick-or-treaters, despite the fact that he doesn’t really like Halloween. Is that because he’s opposed to devils and demons? Oh, heck no; he works at Microsoft, remember? No, it’s just that Halloween used to be a holiday exclusively for kids, and now many adults have co-opted the day for themselves. In fact, many parents don’t even allow their children to go trick-or-treating; they’re afraid the kids will come back with poisoned candy. (The number of documented cases where children have come home from trick-or-treating with poisoned candy? Zero.)

Instead, the parents hire a babysitter, make the kids stay at home, then the parents dress up in costumes and go out and party. The Scripting Guy who writes this column thinks that’s a terrible thing to do to children, which is why he doesn’t really like Halloween anymore.

By contrast, the Scripting Editor loves Halloween; she says it’s the one day out of the year when she can “really be herself.” You know what? We’re going to go check that date one last time ….

Editor’s Note: Just to set the record straight, the Scripting Editor was not born on October 31. Not that it would mean anything negative if she had been – there are plenty of perfectly normal people out there who were born on Halloween. As for how the Scripting Editor spends Halloween? She spends it handing out candy – with the help of the Scripting Dog – to all the kids whose parents do let them trick-or-treat. (One year, only about five years ago, that wound up being over 100 kids.)

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • what if i only wanted to delete files with extension *.log

    $a = Get-ChildItem C:\Scripts\ -Include*.log

    but then i get an error on Cannot find an overload for "op_Subtraction" and the argument count: "2".

    At :line:12 char:26

    +         $y = ((Get-Date) - <<<<  $x.LastWriteTime).Days

  • brekfist: I can re-create that error if the directory is empty, doesn't exist, etc. Also check for the potentially missing space after "-Include"?

  • You will also see this error if all of the files ending with .log have already been deleted or if the .log files exist in a compressed folder (such as .zip)

  • bash

    find directory -type f -mtime +30 -exec rm {} \;

  • In response to brekfist's post: I also received the "Cannot find an overload for "op_Subtraction" and the argument count: "2", when there were no files in the directory that matched my filter.

    I found this post helpful: www.thomasmaurer.ch/.../powershell-check-variable-for-null

    So I re-wrote the script like this, making sure $a is not null:

    # Looking for files with ".bak" extension

    $a = Get-ChildItem "C:\Scripts" -include *.bak -recurse

    # Make sure $a is not null

    if ($a)

    {

       foreach($x in $a)

           {

               $y = ((Get-Date) - $x.CreationTime).Days

    #probably don't need the PsIsContainer check since filtering by extension ".bak"

               if ($y -gt 30 -and $x.PsISContainer -ne $True)

                   {$x.Delete()}

           }

    }

    Good Luck!

  • Just an FYI: If you run this, it will purge files based on creation date, not last modified date. This may (will) cause your users to yell and scream, and potentially cause your boss to call you at 4:00 AM.

    Here's mine:

    $today = get-date -uformat "%Y_%m_%d"

    $log = "Purge_" + $today + ".log"

    $purge_paths = gc "Purge_dirs.txt"

    foreach ($purge_path in $purge_paths)

       {

       $a = Get-ChildItem $purge_path -Recurse

       foreach($x in $a)

           {

               $y = ((Get-Date) - $x.LastWriteTime).Days

               if ($y -gt 365 -and $x.PsISContainer -ne $True)

                   {

                   $deleted = "Deleting - " + $x.fullname + " - Last Write Time - " + $x.LastWriteTime

    #               Write-Host $deleted

                   add-content $log -value $deleted

                   $x.Delete()

                   }

           }

       }

  • Instead of filtering PsISContainer just use the -Directory Parameter for get-childitem.

    $a = Get-ChildItem C:\Temp\Test -recurse -File

    $Now = Get-Date

    foreach ($x in $a)

    {

       If (($Now - $x.LastWriteTime).Days -gt -1)

           {$x.Delete()}

    }

  • @Michael you are absolutely right. In Windows PowerShell 3.0 we add the -directory switch that makes using PsIsContainer in this manner unnecessary. Thanks for point this out.

  • Referring to the title of the post, I guess $x.LastWriteTime should be used in place of $x.CreationTime.

    Obviously, depends on our requirement!

  • Hey, thanks for the script and more importantly, the great step by step explanation.  I am brand spanking new to PowerShell and just opened a window and went along with you and understood everything! Great job!

  • What if I have a set of subfolder that I want to do this same process to? I have an app that creates backups in subfolders. So I want to delete the older folders.