PowerShell Basics: How to Delete Files Older Than X Days
Published Mar 26 2020 05:26 AM 145K Views
Microsoft

Currently spring cleaning your home... or trying to find something to do while stuck at home?  I've recently taken up the task to clean my NAS and other storage devices of files not touched in over 6 months to clear up storage space.  I once again turn to PowerShell to automate the task.  

 

Lets begin.

 

The following script will delete items in my Downloads directory that are older than 6 months:

 

$Folder = "G:\Downloads"

#Delete files older than 6 months
Get-ChildItem $Folder -Recurse -Force -ea 0 |
? {!$_.PsIsContainer -and $_.LastWriteTime -lt (Get-Date).AddDays(-180)} |
ForEach-Object {
   $_ | del -Force
   $_.FullName | Out-File C:\log\deletedlog.txt -Append
}

#Delete empty folders and subfolders
Get-ChildItem $Folder -Recurse -Force -ea 0 |
? {$_.PsIsContainer -eq $True} |
? {$_.getfiles().count -eq 0} |
ForEach-Object {
    $_ | del -Force
    $_.FullName | Out-File C:\log\deletedlog.txt -Append
}

The latter half of the script deletes any folders or subfolders now empty after the purge.  A deletelog.txt file is created to report on all file and folders that have now been removed.

 

As always, please share below your PowerShell automation scripts to possibly add to or better the script shared above.

4 Comments
Copper Contributor

I wrote a function for just this purpose a few years ago, also has the option to clean up empty folders.

 

Function Remove-AgedItems
{
    <#
    
    .DESCRIPTION
    Function that can be used to remove files older than a specified age and also remove empty folders.

    .PARAMETER Path
    Specifies the target Path.
    
    .PARAMETER Age
    Specifies the target Age in days, e.g. Last write time of the item.
    
    .PARAMETER Force
    Switch parameter that allows for hidden and read-only files to also be removed.
    
    .PARAMETER Empty Folder
    Switch parameter to use empty folder remove function.

    .EXAMPLE
    Remove-AgedItems -Path 'C:\Users\rholland\TesfFunction' -Age 7 #Remove Files In The Target Path That Are Older Than The Specified Age (in days), Recursively.

    Remove-AgedItems -Path 'C:\Users\rholland\TesfFunction' -Age 7 -Force #Remove Files In The Target Path That Are Older Than The Specified Age (in days), Recursively. Force will include hidden and read-only files.

    Remove-AgedItems -Path 'C:\Users\rholland\TesfFunction' -Age 0 -EmptyFolder #Remove All Empty Folders In Target Path.

    Remove-AgedItems -Path 'C:\Users\rholland\TesfFunction' -Age 7 -EmptyFolder #Remove All Empty Folders In Target Path That Are Older Than Specified Age (in days).

    .NOTES
    The -EmptyFolders switch branches the function so that it will only perform its empty folder cleanup operation, it will not affect aged files with this switch.
    It is recommended to first perform a cleanup of the aged files in the target path and them perform a cleanup of the empty folders.

    #>
    
    param ([String][Parameter(Mandatory = $true)]
        $Path,
        [int][Parameter(Mandatory = $true)]
        $Age,
        [switch]$Force,
        [switch]$EmptyFolder)
    
    $CurrDate = (get-date)
    
    if (Test-Path -Path $Path)
    {
        
        $Items = (Get-ChildItem -Path $Path -Recurse -Force -File)
        
        $AgedItems = ($Items | Where-object { $_.LastWriteTime -lt $CurrDate.AddDays(- $Age) })
        
        if ($EmptyFolder.IsPresent)
        {
            
            
            $Folders = @()
            ForEach ($Folder in (Get-ChildItem -Path $Path -Recurse | Where { ($_.PSisContainer) -and ($_.LastWriteTime -lt $CurrDate.AddDays(- $Age)) }))
            {
                $Folders += New-Object PSObject -Property @{
                    Object = $Folder
                    Depth = ($Folder.FullName.Split("\")).Count
                }
            }
            $Folders = $Folders | Sort Depth -Descending
            
            $Deleted = @()
            ForEach ($Folder in $Folders)
            {
                If ($Folder.Object.GetFileSystemInfos().Count -eq 0)
                {
                    
                    Remove-Item -Path $Folder.Object.FullName -Force
                    
                    Start-Sleep -Seconds 0.2
                    
                }
            }
            
        }
        
        else
        {
            
            
            if ($Force.IsPresent)
            {
                
                $AgedItems | Remove-Item -Recurse -Force
                
            }
            
            else
            {
                
                $AgedItems | Remove-Item -Recurse
            }
            
        }
        
    }
    
    Else
    {
        
        Write-Error "Target path has not been found"
        
    }
    
}

Remove-AgedItems -Path 'C:\Users\rholland\TestFunction' -Age 7

Remove-AgedItems -Path 'C:\Users\rholland\TestFunction' -Age 0 -EmptyFolder
Copper Contributor

Well, to not arbitrarily delete root / OS/boot drive stuff, I'd consider a small change.

 

$Folder = Read-Host -Prompt 'Enter the full path to the target folder tree to delete'
If ($Folder -match 'C:')
{
    Write-Warning -Message "You are making a distructive action on the $Folder. Are you sure you want to do this?"

<#
    #Delete files older than 6 months
    Get-ChildItem $Folder -Recurse -Force -ea 0 |
    ? {!$_.PsIsContainer -and $_.LastWriteTime -lt (Get-Date).AddDays(-180)} |
    ForEach-Object {
       $_ | del -Force
       $_.FullName | Out-File C:\log\deletedlog.txt -Append
    }
#>
}
Else
{
    "Working on deleting the folder tree $Folder"
<#
    #Delete empty folders and subfolders
    Get-ChildItem $Folder -Recurse -Force -ea 0 |
    ? {$_.PsIsContainer -eq $True} |
    ? {$_.getfiles().count -eq 0} |
    ForEach-Object {
        $_ | del -Force
        $_.FullName | Out-File C:\log\deletedlog.txt -Append
    }
#>
}
Copper Contributor

 

I wrote this script for CreateTempDrive, it creates a drive based on a temporary folder. All contents in that folder will be automatically removed after a certain retention periond.      

 

This scripts runs daily to clean a folder of all items which are older than the retention period. It will also delete any empty folders that deleting the files have caused. It supports an optional rundate and the generic -whatif and -confirm parameters.
This way you can see which files and folders will be removed in a folder when you run the command on a certain date.  

 

[CmdletBinding(SupportsShouldProcess)]
param (
    [Parameter(Mandatory)]
    [IO.DirectoryInfo] $path,
    [TimeSpan] $retentionPeriod = [TimeSpan]::FromDays(14),
    [DateTime] $now = [DateTime]::MinValue
)

if ($now -eq [DateTime]::MinValue) {

    $now = (Get-Date)
}

$oldFiles = Get-ChildItem $path.FullName -Recurse -File | ?{ $_.LastWriteTime -lt $now.Subtract($retentionPeriod) }
$folders = Get-ChildItem $path.FullName -Recurse -Directory 

$emptyFolders = $folders `
    | ?{ (Get-ChildItem $_.FullName -file -Recurse | ?{ $_.LastWriteTime -gt $now.Subtract($retentionPeriod) }) -eq $null } `
    | Sort-Object -Property @{ Expression={ $_.FullName.Split([IO.Path]::DirectorySeparatorChar).Count }; Descending=$true }  

$oldFiles | Remove-Item -Force
$emptyFolders | Remove-Item -Force -Recurse

 

 

Copper Contributor

Hi @Anthony Bartolo 

Thanks for providing this script which is exactly what I am looking for.

 

There's one big problem with your script that I have noticed in testing, is that it doesn't seem to count files in the subfolders of a folder as being "contents" of the grandparent folder, so if you have a nested folder with files in that are within the date range that we want to keep, it skips them in the "file pass" but when it does the "folder pass" it sees the top level folder as being empty (even though it has a subfolder with legitimate files within it) and deletes that along with all the recent files in the subfolder......

 

When I run it manually it comes up with a prompt "CONFIRM: The item at Microsoft.PowerShell.Core\FileSystem::C:\TESTING\FOLDER_CONTAINING_SUBFOLDERS_CONTAINING_FILES has children and the Recurse parameter was not specified. If you continue, all children will be removed with the item. Are you sure you want to continue?"

 

I can see that there does seem to be a recurse command in the 

#Delete empty folders and subfolders

section, so I am not sure where it thinks another one needs to be added?

 

Please could you advise as to how I would resolve this behaviour which is clearly incorrect and also remove the manual prompt that it is generating (although that actually saved the legitimate files in this instance and maybe won't pop up when the script is working correctly).

 

Either way that need to be rectified too as this script needs to run as an automated process?

 

Thanks!

Version history
Last update:
‎Mar 26 2020 08:12 AM
Updated by: