Home > PowerShell > PowerShell Functions To Delete Old Files And Empty Directories

PowerShell Functions To Delete Old Files And Empty Directories

I thought I’d share some PowerShell (PS) functions that I wrote for some clean-up scripts at work.  I use these functions to delete files older than a certain date. Note that these functions require PS v3.0; slower PS v2.0 compatible functions are given at the end of this article.

# Function to remove all empty directories under the given path.
# If -DeletePathIfEmpty is provided the given Path directory will also be deleted if it is empty.
# If -OnlyDeleteDirectoriesCreatedBeforeDate is provided, empty folders will only be deleted if they were created before the given date.
# If -OnlyDeleteDirectoriesNotModifiedAfterDate is provided, empty folders will only be deleted if they have not been written to after the given date.
function Remove-EmptyDirectories([parameter(Mandatory)][ValidateScript({Test-Path $_})][string] $Path, [switch] $DeletePathIfEmpty, [DateTime] $OnlyDeleteDirectoriesCreatedBeforeDate = [DateTime]::MaxValue, [DateTime] $OnlyDeleteDirectoriesNotModifiedAfterDate = [DateTime]::MaxValue, [switch] $OutputDeletedPaths, [switch] $WhatIf)
{
    Get-ChildItem -Path $Path -Recurse -Force -Directory | Where-Object { (Get-ChildItem -Path $_.FullName -Recurse -Force -File) -eq $null } | 
        Where-Object { $_.CreationTime -lt $OnlyDeleteDirectoriesCreatedBeforeDate -and $_.LastWriteTime -lt $OnlyDeleteDirectoriesNotModifiedAfterDate } | 
        ForEach-Object { if ($OutputDeletedPaths) { Write-Output $_.FullName } Remove-Item -Path $_.FullName -Force -WhatIf:$WhatIf }

    # If we should delete the given path when it is empty, and it is a directory, and it is empty, and it meets the date requirements, then delete it.
    if ($DeletePathIfEmpty -and (Test-Path -Path $Path -PathType Container) -and (Get-ChildItem -Path $Path -Force) -eq $null -and
        ((Get-Item $Path).CreationTime -lt $OnlyDeleteDirectoriesCreatedBeforeDate) -and ((Get-Item $Path).LastWriteTime -lt $OnlyDeleteDirectoriesNotModifiedAfterDate))
    { if ($OutputDeletedPaths) { Write-Output $Path } Remove-Item -Path $Path -Force -WhatIf:$WhatIf }
}

# Function to remove all files in the given Path that were created before the given date, as well as any empty directories that may be left behind.
function Remove-FilesCreatedBeforeDate([parameter(Mandatory)][ValidateScript({Test-Path $_})][string] $Path, [parameter(Mandatory)][DateTime] $DateTime, [switch] $DeletePathIfEmpty, [switch] $OutputDeletedPaths, [switch] $WhatIf)
{
    Get-ChildItem -Path $Path -Recurse -Force -File | Where-Object { $_.CreationTime -lt $DateTime } | 
		ForEach-Object { if ($OutputDeletedPaths) { Write-Output $_.FullName } Remove-Item -Path $_.FullName -Force -WhatIf:$WhatIf }
    Remove-EmptyDirectories -Path $Path -DeletePathIfEmpty:$DeletePathIfEmpty -OnlyDeleteDirectoriesCreatedBeforeDate $DateTime -OutputDeletedPaths:$OutputDeletedPaths -WhatIf:$WhatIf
}

# Function to remove all files in the given Path that have not been modified after the given date, as well as any empty directories that may be left behind.
function Remove-FilesNotModifiedAfterDate([parameter(Mandatory)][ValidateScript({Test-Path $_})][string] $Path, [parameter(Mandatory)][DateTime] $DateTime, [switch] $DeletePathIfEmpty, [switch] $OutputDeletedPaths, [switch] $WhatIf)
{
    Get-ChildItem -Path $Path -Recurse -Force -File | Where-Object { $_.LastWriteTime -lt $DateTime } | 
	ForEach-Object { if ($OutputDeletedPaths) { Write-Output $_.FullName } Remove-Item -Path $_.FullName -Force -WhatIf:$WhatIf }
    Remove-EmptyDirectories -Path $Path -DeletePathIfEmpty:$DeletePathIfEmpty -OnlyDeleteDirectoriesNotModifiedAfterDate $DateTime -OutputDeletedPaths:$OutputDeletedPaths -WhatIf:$WhatIf
}

The Remove-EmptyDirectories function removes all empty directories under the given path, and optionally (via the DeletePathIfEmpty switch) the path directory itself if it is empty after cleaning up the other directories. It also takes a couple parameters that may be specified if you only want to delete the empty directories that were created before a certain date, or that haven’t been written to since a certain date.

The Remove-FilesCreatedBeforeDate and Remove-FilesNotModifiedAfterDate functions are very similar to each other.  They delete all files under the given path whose Created Date or Last Written To Date, respectfully, is less than the given DateTime.  They then call the Remove-EmptyDirectories function with the provided date to clean up any left over empty directories.

To call the last 2 functions, just provide the path to the file/directory that you want it to delete if older than the given date-time.  Here are some examples of calling all the functions:

# Delete all files created more than 2 days ago.
Remove-FilesCreatedBeforeDate -Path "C:\Some\Directory" -DateTime ((Get-Date).AddDays(-2)) -DeletePathIfEmpty

# Delete all files that have not been updated in 8 hours.
Remove-FilesNotModifiedAfterDate -Path "C:\Another\Directory" -DateTime ((Get-Date).AddHours(-8))

# Delete a single file if it is more than 30 minutes old.
Remove-FilesCreatedBeforeDate -Path "C:\Another\Directory\SomeFile.txt" -DateTime ((Get-Date).AddMinutes(-30))

# Delete all empty directories in the Temp folder, as well as the Temp folder itself if it is empty.
Remove-EmptyDirectories -Path "C:\SomePath\Temp" -DeletePathIfEmpty

# Delete all empty directories created after Jan 1, 2014 3PM.
Remove-EmptyDirectories -Path "C:\SomePath\WithEmpty\Directories" -OnlyDeleteDirectoriesCreatedBeforeDate ([DateTime]::Parse("Jan 1, 2014 15:00:00"))

# See what files and directories would be deleted if we ran the command.
Remove-FilesCreatedBeforeDate -Path "C:\SomePath\Temp" -DateTime (Get-Date) -DeletePathIfEmpty -WhatIf

# Delete all files and directories in the Temp folder, as well as the Temp folder itself if it is empty, and output all paths that were deleted.
Remove-FilesCreatedBeforeDate -Path "C:\SomePath\Temp" -DateTime (Get-Date) -DeletePathIfEmpty -OutputDeletedPaths

Notice that I am using Get-Date to get the current date and time, and then subtracting the specified amount of time from it in order to get a date-time relative to the current time; you can use any valid DateTime though, such as a hard-coded date of January 1st, 2014 3PM.

I use these functions in some scripts that we run nightly via a scheduled task in Windows.  Hopefully you find them useful too.

 

PowerShell v2.0 Compatible Functions

As promised, here are the slower PS v2.0 compatible functions.  The main difference is that they use $_.PSIsContainer in the Where-Object clause rather than using the –File / –Directory Get-ChildItem switches.  The Measure-Command cmdlet shows that using the switches is about 3x faster than using the where clause, but since we are talking about milliseconds here you likely won’t notice the difference unless you are traversing a large file tree (which I happen to be for my scripts that we use to clean up TFS builds).

# Function to remove all empty directories under the given path.
# If -DeletePathIfEmpty is provided the given Path directory will also be deleted if it is empty.
# If -OnlyDeleteDirectoriesCreatedBeforeDate is provided, empty folders will only be deleted if they were created before the given date.
# If -OnlyDeleteDirectoriesNotModifiedAfterDate is provided, empty folders will only be deleted if they have not been written to after the given date.
function Remove-EmptyDirectories([parameter(Mandatory=$true)][ValidateScript({Test-Path $_})][string] $Path, [switch] $DeletePathIfEmpty, [DateTime] $OnlyDeleteDirectoriesCreatedBeforeDate = [DateTime]::MaxValue, [DateTime] $OnlyDeleteDirectoriesNotModifiedAfterDate = [DateTime]::MaxValue, [switch] $OutputDeletedPaths, [switch] $WhatIf)
{
    Get-ChildItem -Path $Path -Recurse -Force | Where-Object { $_.PSIsContainer -and (Get-ChildItem -Path $_.FullName -Recurse -Force | Where-Object { !$_.PSIsContainer }) -eq $null } | 
        Where-Object { $_.CreationTime -lt $OnlyDeleteDirectoriesCreatedBeforeDate -and $_.LastWriteTime -lt $OnlyDeleteDirectoriesNotModifiedAfterDate } | 
        ForEach-Object { if ($OutputDeletedPaths) { Write-Output $_.FullName } Remove-Item -Path $_.FullName -Force -WhatIf:$WhatIf }

    # If we should delete the given path when it is empty, and it is a directory, and it is empty, and it meets the date requirements, then delete it.
    if ($DeletePathIfEmpty -and (Test-Path -Path $Path -PathType Container) -and (Get-ChildItem -Path $Path -Force) -eq $null -and
        ((Get-Item $Path).CreationTime -lt $OnlyDeleteDirectoriesCreatedBeforeDate) -and ((Get-Item $Path).LastWriteTime -lt $OnlyDeleteDirectoriesNotModifiedAfterDate))
    { if ($OutputDeletedPaths) { Write-Output $Path } Remove-Item -Path $Path -Force -WhatIf:$WhatIf }
}

# Function to remove all files in the given Path that were created before the given date, as well as any empty directories that may be left behind.
function Remove-FilesCreatedBeforeDate([parameter(Mandatory=$true)][ValidateScript({Test-Path $_})][string] $Path, [parameter(Mandatory)][DateTime] $DateTime, [switch] $DeletePathIfEmpty, [switch] $OutputDeletedPaths, [switch] $WhatIf)
{
    Get-ChildItem -Path $Path -Recurse -Force | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $DateTime } | 
		ForEach-Object { if ($OutputDeletedPaths) { Write-Output $_.FullName } Remove-Item -Path $_.FullName -Force -WhatIf:$WhatIf }
    Remove-EmptyDirectories -Path $Path -DeletePathIfEmpty:$DeletePathIfEmpty -OnlyDeleteDirectoriesCreatedBeforeDate $DateTime -OutputDeletedPaths:$OutputDeletedPaths -WhatIf:$WhatIf
}

# Function to remove all files in the given Path that have not been modified after the given date, as well as any empty directories that may be left behind.
function Remove-FilesNotModifiedAfterDate([parameter(Mandatory=$true)][ValidateScript({Test-Path $_})][string] $Path, [parameter(Mandatory)][DateTime] $DateTime, [switch] $DeletePathIfEmpty, [switch] $OutputDeletedPaths, [switch] $WhatIf)
{
    Get-ChildItem -Path $Path -Recurse -Force | Where-Object { !$_.PSIsContainer -and $_.LastWriteTime -lt $DateTime } | 
	ForEach-Object { if ($OutputDeletedPaths) { Write-Output $_.FullName } Remove-Item -Path $_.FullName -Force -WhatIf:$WhatIf }
    Remove-EmptyDirectories -Path $Path -DeletePathIfEmpty:$DeletePathIfEmpty -OnlyDeleteDirectoriesNotModifiedAfterDate $DateTime -OutputDeletedPaths:$OutputDeletedPaths -WhatIf:$WhatIf
}

Happy coding!

  1. Carl Reid
    October 16th, 2013 at 03:02 | #1

    Thanks for submitting these scripts, coincidently I was looking to write something similar for tidying up old releases and will use your scripts as a starter for mine.

    I did however hit problems with your Remove-EmptyDirectories function. It looks like it hits a null reference in the Get-ChildItems and then falls over when trying to access the Length property which of course doesn’t exist on a null object.

    You can test this by simply creating two folders under c:\temp
    c:\temp\new1
    c:\temp\new1\new2

    Then run your example:
    Remove-FilesNotModifiedAfterDate -Path “C:\Temp” -DateTime ((Get-Date).AddDays(-2)) -DeletePathIfEmpty

    Results in:

    Where-Object : Property ‘Length’ cannot be found on this object. Make sure that it exists.
    At line:4 char:49
    + Get-ChildItem -Path $Path -Recurse -Force | Where-Object { $_.PSIsContainer …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [Where-Object], PropertyNotFoundException
    + FullyQualifiedErrorId : PropertyNotFoundStrict,Microsoft.PowerShell.Commands.WhereObjectCommand

    I have modified it like this which seems ok although not as concise!

    function Remove-EmptyDirectories([parameter(Mandatory=$true)][ValidateScript({Test-Path $_})][string] $Path, [switch] $DeletePathIfEmpty)
    {
    $childDirectories = Get-ChildItem -Path $Path -Recurse -Force | Where-Object { $_.PSIsContainer } | Sort -Property @{ Expression = {$_.FullName.Split(‘\’).Count} } -Desc
    foreach ($childDirectory in $childDirectories)
    {
    $childFiles = Get-ChildItem -Path $childDirectory.FullName -Recurse -Force | Where-Object { -not $childDirectory.PSIsContainer }
    if (($childFiles -eq $null) -or ($childFiles.Length -eq 0))
    {
    Remove-Item -Path $childDirectory.FullName -Force -Recurse
    }
    }

    # If we should delete the given path when it is empty, and it is a directory, and it is empty, then delete it.
    if ($DeletePathIfEmpty -and (Test-Path -Path $Path -PathType Container) -and (Get-ChildItem -Path $Path -Force).Length -eq 0) { Remove-Item -Path $Path -Force }
    }

    Cheers,

    carl

  2. October 16th, 2013 at 11:39 | #2

    @Carl Reid
    Hi Carl, I’m not able to reproduce your problem. I’ve created your sample scenario, as well as empty directories up to 5 levels deep and everything works fine for me. What version of PowerShell are you using? I’m using v3.0. If you are using a lower version maybe that is the problem.

    Thanks for posting your modified solution though in case anybody else runs into the same problem.

  3. October 16th, 2013 at 12:50 | #3

    @Carl Reid
    I confirmed a hunch I had that the error you were seeing arose when using “Set-StrictMode -Version Latest” in the script. I typically use this in my scripts, but wasn’t using it when I did my testing.

    I’ve updated my code to simply check for $null instead of trying to check .Length, so the problem is fixed and it is still nice and concise 🙂

    Thanks.

  4. Marc
    November 30th, 2013 at 05:14 | #4

    I have a newbie question… I save this .ps1 file to my disk, but if I run the function names in powershell it’s telling me always that this is not a name of a cmdlet or a function or a script or an executable.

    I’d like to run “Remove-FilesNotModifiedAfterDate -Path “C:\Temp” -DateTime ((Get-Date).AddDays(-2))” directly in powershell.

    What I’m doing wrong here?

  5. November 30th, 2013 at 08:40 | #5

    @Marc
    Make sure you first dot source the script into your PowerShell session (to make the functions available) using:

    . [Path to Remove-FilesOlderThan.ps1]

    e.g. If using an absolute file path:

    . “C:\Some Folder\Remove-FilesOlderThan.ps1”

    e.g. If using a relative file path, for example your command prompt session is currently in the same directory where the script file is saved:

    . “.\Remove-FilesOlderThan.ps1”

    Once you do this step you should then be able to call the Remove-FilesNotModifiedAfterDate function.

  6. Robert
    January 17th, 2014 at 13:40 | #6

    It would seem there is a potential race condition in your script to remove old files when used to cleanup something like a server output directory. Specifically, if an application creates an empty directory newer than the CreatedBeforeDate or NotModifiedAfterDate and your Remove-Files… script finds it before the application writes a file in the directory, the directory will be deleted and will potentially leave the application broken. IMO, the date of the directory should be checked to meet the CreatedBeforeDate or NotModifiedAfterDate criteria to qualify for deletion.

  7. January 17th, 2014 at 15:23 | #7

    @Robert
    Do you mean that you are writing files to a directory using some app, while at the same time running this script to clean files up in that same directory? That sounds like poor planning. If you are scheduling this script to run regularly, you should try and do it when you know other applications will not be writing to the same directory.

    Having said that, I agree with you that to stay true to their names, the Remove-Files* functions should also check the dates on directories, not just files, so I’ve updated the scripts on this post to do just that. Thanks 🙂

  8. JEmlay
    December 19th, 2014 at 12:23 | #8

    This is a horrible solution! You’re using CREATED and MODIFIED times. ($_.LastWriteTime)

    A file could have been written recently yet modified years ago. You’re script has the ability to delete WAY MORE then a person might want.

    I just got done stripping out all the extra’s that aren’t wanted. Creation time only!

  9. December 20th, 2014 at 14:07 | #9

    @JEmlay
    Why did you need to modify anything? If you are only concerned with the date/time a file was created, then just use the Remove-FilesCreatedBeforeDate function, and not the Remove-FilesNotModifiedAfterDate fuction.

  10. Michael
    August 23rd, 2015 at 23:13 | #10

    love the scripts – but how would you add output to these so a list of what is deleted can be produced?

  11. Chris
    September 29th, 2015 at 05:11 | #11

    Dan,

    How can I use the “Remove-EmptyDirectories” with the “-DeletePathIfEmpty” flag but not delete the root folder defined in the $path variable?

    i.e. In your example, not delete the ‘temp’ folder?

  12. September 29th, 2015 at 11:19 | #12

    @Chris
    Just don’t provide the -DeletePathIfEmpty flag. A better more descriptive name might have been -DeleteRootPathIfEmpty.

  13. Chris
    September 30th, 2015 at 06:43 | #13

    @deadlydog
    Excellent! Thank you!

  14. Peter
    October 7th, 2015 at 04:53 | #14

    Michael :
    love the scripts – but how would you add output to these so a list of what is deleted can be produced?

    I would also like this, I am trying to Out-File the function call however does not seem to be working for me, how would we output to the file what the script did?

    thank you

  15. October 26th, 2015 at 11:14 | #15

    @Michael

    @Peter
    Hey guys, I’ve updated the scripts to now take 2 additional switch parameters:
    -WhatIf will now tell you what files/directories would be deleted without actually deleting them.
    -OutputDeletedPaths will output every file/directory that is deleted. So if provided it will output all of the deleted paths to the console window. If you want, you can also pipe the output to something like Out-File to create a file with every path that was deleted.

    I hope that helps you 🙂

  16. November 27th, 2015 at 12:05 | #16

    Thank you. I spent a few days playing with this function thing you revealed to me and today I’m putting the finishing touches on my magnum opus of a backup script that handles two different vm environments, moves things to tape and removes the oldest backups to make room for new ones.

    Thank you again for sharing this wonderful creation.

  17. Alex
    February 9th, 2016 at 13:49 | #17

    Hello,
    I am pretty new in the power shell.
    Thank you very much for sharing your knowledge.
    I saved your scripts in a file and named it as cleanup.ps1
    Now I would like to run it in windows schedule task every night to clean up my server and delete all files older than 30 days ago.
    I am wondering if there a a way to pass the parameters to the function “Remove-FilesCreatedBeforeDate”

  18. Alex
    February 9th, 2016 at 13:51 | #18

    An how can I call “Remove-FilesCreatedBeforeDate” from the windows schedule task ?

  19. Josh
    April 29th, 2016 at 11:05 | #19

    @Michael

    In my scripts I usually add a Tee-Variable before the last step (in this case, deleting the files), then add a line that pipes that variable to an Export-CSV with a time stamp.

    I actually found this script because I was looking to delete those exported CSVs after a certain time as part of my nightly maintenance processes, so thanks @deadlydog for providing a tidy set of functions for accomplishing this.

  20. November 20th, 2016 at 03:47 | #20

    Great work .
    Splendid!!

  21. Mary
    March 27th, 2017 at 06:38 | #21

    is there a possible way to do the same in powershell v 1.0 please

  22. Ian
    April 12th, 2017 at 04:17 | #22

    Scripts are a big help! Wondering if you could explain why this is happening though as I can’t see it?

    I’m calling “Remove-FilesNotModifiedAfterDate” but when it is running I am being presented with the message “The item at “path” has children and the Recurse parameter was not specified. If you continue, all children will be removed with the item”

    Now, as far as I can see the Recurse switch is set with -Force? The main problem is, while the message box is popping up, I can’t automate it :/

    Thanks!

  23. Jamie
    May 26th, 2017 at 04:47 | #23

    @Ian
    Ian, if you add -Recurse just after -Force in line 9 in the first script block on this page that’ll sort you out.

  24. Jamie
    June 12th, 2017 at 03:21 | #24

    Great script thanks but when trying to use a wildcard with the path parameter it doesn’t want to work as expected.

    e.g I have the path “C:\Temp\Applications\New Folder” & “New Folder(2)”. This is just a test for a similar but much larger folder hierarchy I’ll be using in the live environment.

    Using the following command it only removes the second folder contents but without the wildcard it checks all folders:

    Remove-FilesCreatedBeforeDate -Path “C:\Temp\Applications\New*” -DateTime ((Get-Date).AddDays(-0)) -Whatif -OutputDeletedPaths

    Am I missing something?

  25. Jamie
    June 14th, 2017 at 03:40 | #25

    Ok, managed to get round this by adding the switch “-Exclude” to line 9 under the Remove-EmptyDirectories function. Would like to know why specifying a wildcard in the parameter wouldn’t work but at least this can be addressed in another way.

  1. April 24th, 2015 at 13:12 | #1