Functions/Public/Remove-StaleFile.ps1
|
function Remove-StaleFile { <# .SYNOPSIS Removes old files from a folder while keeping the newest ones. .DESCRIPTION The Remove-StaleFile function helps manage disk space by automatically cleaning up old files while preserving the most recent ones. It supports multiple filter patterns and can group files by name patterns to keep the newest files for each group separately. Files that are currently locked by another process are automatically skipped with a warning. .PARAMETER Path Specifies the folder path to process. The path must exist or an error is returned. .PARAMETER Keep Specifies the number of newest files to keep. Files beyond this count (sorted by LastWriteTime descending) are deleted. Default is 5. .PARAMETER Filter Specifies file filter pattern(s) to match. Accepts wildcards. Examples: '*.xlsx', '*.log', or an array like @('*.xlsx', '*.csv') .PARAMETER NamePattern Specifies optional filename pattern(s) to group files by before applying retention. When specified, the function keeps the newest files for EACH pattern separately. Examples: 'Report-*', 'Backup_*', or @('Report-*', 'Audit-*') .PARAMETER WhatIf Shows what would happen if the cmdlet runs. The cmdlet is not run. .PARAMETER Confirm Prompts you for confirmation before running the cmdlet. .INPUTS None. You cannot pipe objects to Remove-StaleFile. .OUTPUTS None. This function does not generate any output. .EXAMPLE Remove-StaleFile -Path "C:\Logs" -Filter "*.log" -Keep 10 Keeps the 10 newest .log files in C:\Logs and deletes the rest. .EXAMPLE Remove-StaleFile -Path "C:\Output" -Filter @('*.xlsx', '*.csv') -Keep 5 Keeps the 5 newest Excel and CSV files combined (sorted by LastWriteTime). .EXAMPLE Remove-StaleFile -Path "C:\Reports" -Filter "*.csv" -NamePattern "DailyReport-*" -Keep 7 Keeps only the 7 newest files matching 'DailyReport-*.csv' pattern. .EXAMPLE Remove-StaleFile -Path "C:\Backups" -Filter "*.zip" -NamePattern @('Full-*', 'Incremental-*') -Keep 3 Keeps the 3 newest 'Full-*.zip' files AND the 3 newest 'Incremental-*.zip' files separately. .EXAMPLE Remove-StaleFile -Path "C:\Logs" -Filter "*.log" -Keep 5 -WhatIf Shows which files would be deleted without actually deleting them. .NOTES Author: Sune Alexandersen Narud Version: 1.0.0 Date: February 5, 2026 Requires: - PowerShell 5.1 or later Optional: - OrionDesign module for enhanced output formatting (Write-Action, Get-OrionTheme) .LINK Test-FileLock .LINK https://github.com/suneworld/OrionUtils #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] param( [Parameter(Mandatory = $true, Position = 0, HelpMessage = "The folder path containing files to clean up.")] [ValidateNotNullOrEmpty()] [ValidateScript({ Test-Path -LiteralPath $_ -PathType Container })] [string]$Path, [Parameter(Position = 1, HelpMessage = "Number of newest files to keep. Default is 5.")] [ValidateRange(1, [int]::MaxValue)] [int]$Keep = 5, [Parameter(Mandatory = $true, Position = 2, HelpMessage = "File filter pattern(s) like '*.log' or @('*.xlsx', '*.csv')")] [ValidateNotNullOrEmpty()] [string[]]$Filter, [Parameter(HelpMessage = "Optional filename pattern(s) to group files by for separate retention.")] [string[]]$NamePattern = @() ) begin { Write-Verbose "Starting Remove-StaleFile on path: $Path" Write-Verbose "Keeping $Keep newest file(s) matching: $($Filter -join ', ')" # Get folder name for display $folderName = Split-Path -Leaf $Path } process { # Get all files matching the filters $allFiles = @() foreach ($filterPattern in $Filter) { $matchingFiles = Get-ChildItem -Path $Path -Filter $filterPattern -File -ErrorAction SilentlyContinue $allFiles += $matchingFiles } # Remove duplicates (in case patterns overlap) $allFiles = $allFiles | Sort-Object FullName -Unique if ($allFiles.Count -eq 0) { if (Get-Command -Name 'Write-Action' -ErrorAction SilentlyContinue) { Write-Action "No files found matching the specified filters: $($Filter -join ', ')" -Complete } else { Write-Host " No files found matching: $($Filter -join ', ')" -ForegroundColor Yellow } return } Write-Verbose "Found $($allFiles.Count) file(s) matching filters" # Process files - either by name patterns or as a single group if ($NamePattern.Count -gt 0) { # Group files by name patterns and process each group separately foreach ($pattern in $NamePattern) { $groupFiles = $allFiles | Where-Object { $_.Name -like $pattern } | Sort-Object LastWriteTime -Descending if ($groupFiles.Count -eq 0) { if (Get-Command -Name 'Write-Action' -ErrorAction SilentlyContinue) { Write-Action "No files found matching pattern '$pattern'" -Complete } else { Write-Host " No files found matching pattern: $pattern" -ForegroundColor Yellow } continue } Write-Host "Processing files matching pattern: $pattern" -ForegroundColor Cyan Write-Verbose "Found $($groupFiles.Count) file(s) for pattern: $pattern" if ($groupFiles.Count -le $Keep) { $mutedColor = 'DarkGray' if (Get-Command -Name 'Get-OrionTheme' -ErrorAction SilentlyContinue) { try { $theme = Get-OrionTheme if ($theme.Muted) { $mutedColor = $theme.Muted } } catch { } } Write-Host " $folderName\$pattern - Nothing to delete ($($groupFiles.Count) file(s), keeping $Keep)" -ForegroundColor $mutedColor continue } # Select files to delete for this group $filesToDelete = $groupFiles | Select-Object -Skip $Keep foreach ($file in $filesToDelete) { if ($PSCmdlet.ShouldProcess($file.FullName, "Delete file")) { Remove-FileWithLockCheck -FilesToDelete $file } } } } else { # Process all files as a single group (original behavior) $files = $allFiles | Sort-Object LastWriteTime -Descending if ($files.Count -le $Keep) { $mutedColor = 'DarkGray' if (Get-Command -Name 'Get-OrionTheme' -ErrorAction SilentlyContinue) { try { $theme = Get-OrionTheme if ($theme.Muted) { $mutedColor = $theme.Muted } } catch { } } Write-Host " $folderName\$($Filter -join ', ') - Nothing to delete ($($files.Count) file(s), keeping $Keep)" -ForegroundColor $mutedColor return } # Select files to delete $filesToDelete = $files | Select-Object -Skip $Keep Write-Verbose "Deleting $($filesToDelete.Count) file(s)" foreach ($file in $filesToDelete) { if ($PSCmdlet.ShouldProcess($file.FullName, "Delete file")) { Remove-FileWithLockCheck -FilesToDelete $file } } } } end { Write-Verbose "Remove-StaleFile completed" } } |