TaylorN
TaylorN

Reputation: 399

Delete files older than xx days

I need to delete some files programmatically (preferably using Powershell) from a folder than are older than a given amount of days.

I've written a simple script that will do this, however the issue I'm having is that it seems to have trouble even starting to delete, due to the sheer amount of files in the folder.

I'm looking for a way to delete in batches probably. So it would maybe get the first 1000, delete, and then so on.

Right now, the folder probably has several hundred thousand files, and is practically impossible to traverse.

Param(
  [Parameter(Mandatory=$true)][string]$Path,
  [Parameter(Mandatory=$true)][string]$DaysToDelete
)

$limit = (Get-Date).AddDays($DaysToDelete)
$LogFile = "FileCleanupLog-$(Get-Date -f yyyyMMdd_HH_mm_ss).txt";

function Log-Message
{
   Param ([string]$logtext)
   Add-content $LogFile -value $logtext
}

If (-Not (Test-Path $Path))
{
    Write-Host "Invalid Path provided!" -ForegroundColor Red
    Exit
}

$files = Get-ChildItem -Path $Path -Recurse -Force | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $limit }

If ($files.Count -GT 1) {
    $files | % {$directory=$_.DirectoryName;(Log-Message "Deleting File $directory\$_");$_ } | Remove-Item -Force 
}

Upvotes: 3

Views: 706

Answers (3)

Matt
Matt

Reputation: 46710

I have to admit that I am slightly mistaken with how I wanted robocopy to work. While it can delete files, when told, it still has to perform a copy operation. So this suggestion is best run on the target machine and not using UNC paths. Disappointment aside I still think this is a viable solution. The main thing here is that robocopy will select only the files we need to without any post processing.

$sourceDirectory = "D:\temp\New folder"
$dummyDirectory = "D:\temp\trashbin"
$loggingFile = "D:\temp\FileCleanupLog-$(Get-Date -f yyyyMMdd_HH_mm_ss).txt"

# Build the dummy directory. It will be deleted in the end.
New-Item -Path $dummyDirectory -ItemType Directory | Out-Null

& robocopy.exe $sourceDirectory /njh /ndl /nc /njs /minage:$days /mov /e /ns /np /l | Set-Content $loggingFile

# Purge the dummy directory with all the content we don't want
Remove-Item -Path $dummyDirectory -Force -Confirm:$false -Recurse

Here is what all the switches used represent. Most are to clean up the output for logging. The log should just have a list of full paths that were removed. This currently wont affect directory structure. A switch change will address this if required. You will also see that /l is used for logging only. You can use that switch to test to see if the files you want are being removed. For actual production testing you would need to remove that.

/minage:N        Specifies the minimum file age (exclude files newer than N days or date).
/njh             Specifies that there is no job header.
/njs             Specifies that there is no job summary.
/l               Specifies that files are to be listed only (and not copied, deleted, or time stamped).
/mov             Moves files, and deletes them from the source after they are copied.
/ndl             Specifies that directory names are not to be logged.
/nc              Specifies that file classes are not to be logged.\
/np              Specifies that the progress of the copying operation (the number of files or directories copied so far) will not be displayed.

This will also perform faster if it is not spending time displaying data on the screen. That is why I specifically put /np in there.

Upvotes: 1

Tony Hinkle
Tony Hinkle

Reputation: 4742

To meet your criteria of deleting files in batches of 1000, use the following. The select -first 1000 will cause it to only delete 1000 files each time it goes through the while loop.

while($true){
    $files = Get-ChildItem -Path $Path -Recurse -Force | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $limit } | select -first 1000
    If ($files.Count -GT 1) {
        $files | % {$directory=$_.DirectoryName;(Log-Message "Deleting File $directory\$_");$_ } | Remove-Item -Force 
    } else {
        exit 
    }
}

I don't know if this will be faster--it depends on if PowerShell is smart enough to stop get-childitem after it finds the first 1000 files or not.

Upvotes: 1

Jason Boyd
Jason Boyd

Reputation: 7029

Instead of looping over all of the files and storing the files that are marked for deletion in a list and then looping over each file in the list again just pipe each file to the next command as you find them.

So replace this:

$files = Get-ChildItem -Path $Path -Recurse -Force | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $limit }

If ($files.Count -GT 1) {
    $files | % {$directory=$_.DirectoryName;(Log-Message "Deleting File $directory\$_");$_ } | Remove-Item -Force 
}

With something like this:

Get-ChildItem -Path $Path -Recurse -Force `
| Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $limit } `
| % {
    $directory=$_.DirectoryName
    (Log-Message "Deleting File $directory\$_")
    $_ } `
| Remove-Item -Force

Upvotes: 2

Related Questions