Blog Post

ITOps Talk Blog
1 MIN READ

PowerShell Basics: How to Delete Files Older Than X Days

AnthonyBartolo's avatar
Mar 26, 2020

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.

Updated Mar 26, 2020
Version 2.0

4 Comments

  • scriven_j's avatar
    scriven_j
    Copper Contributor

    Hi AnthonyBartolo 

    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!

  • _sietse_'s avatar
    _sietse_
    Copper Contributor

     

    I wrote this script for https://github.com/sietsevdschoot/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

     

     

  • Daniel Taylor's avatar
    Daniel Taylor
    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
        }
    #>
    }
  • Ricky Holland's avatar
    Ricky Holland
    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