From 59cabe37b153b3fe6ef36db89f9e9ed37ed02996 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0t=C4=9Bp=C3=A1n?= Date: Tue, 21 Jul 2026 13:14:41 +0200 Subject: [PATCH] Add Invoke-OldFilePurge script and documentation for file cleanup utility - Implemented Invoke-OldFilePurge.ps1 to remove files older than a specified number of days. - Added README.md with usage instructions, parameters, and logging details. --- Invoke-OldFilePurge/Invoke-OldFilePurge.ps1 | 163 ++++++++++++++++++++ Invoke-OldFilePurge/README.md | 119 ++++++++++++++ 2 files changed, 282 insertions(+) create mode 100644 Invoke-OldFilePurge/Invoke-OldFilePurge.ps1 create mode 100644 Invoke-OldFilePurge/README.md diff --git a/Invoke-OldFilePurge/Invoke-OldFilePurge.ps1 b/Invoke-OldFilePurge/Invoke-OldFilePurge.ps1 new file mode 100644 index 0000000..eca6358 --- /dev/null +++ b/Invoke-OldFilePurge/Invoke-OldFilePurge.ps1 @@ -0,0 +1,163 @@ +<# +.SYNOPSIS + Removes files older than a specified number of days from a target directory. + +.DESCRIPTION + Cleans up old files from a given path. Designed for high-volume deletion of small files + (e.g. Tomcat temp, IIS logs). Supports recursive scan, logging, and progress reporting. + +.PARAMETER Path + Mandatory. Target directory to clean up. + +.PARAMETER DaysOld + Files older than this many days will be deleted. Default: 7. + +.PARAMETER Recurse + If specified, subdirectories are also scanned. Default: enabled. + +.PARAMETER LogDir + Directory for log output. Created if it does not exist. Default: C:\Temp. + +.PARAMETER LogEnabled + Enable or disable logging. Default: $true. + +.EXAMPLE + .\Invoke-OldFilePurge.ps1 -Path "C:\Program Files\Apache Software Foundation\Tomcat 9.0\temp" + +.EXAMPLE + .\Invoke-OldFilePurge.ps1 -Path "D:\IIS\Logs" -DaysOld 30 -LogDir "D:\Logs" + +.NOTES + Author: Petr Štěpán + Created: 2026-07-02 + Version: 1.0.1 + Requires: PowerShell 5.1+ + Changelog: + 1.0.1 - Enforced minimum PowerShell version, enabled strict mode and + stop-on-error, fixed $AllFiles.Count being unreliable on + Windows PowerShell 5.1 when 0 or 1 file matched. + 1.0.0 - Initial version +#> + +#Requires -Version 5.1 + +[CmdletBinding(SupportsShouldProcess)] +param ( + [Parameter(Mandatory, Position = 0, HelpMessage = "Path to the directory to clean up.")] + [ValidateNotNullOrEmpty()] + [string]$Path, + + [Parameter()] + [ValidateRange(1, 3650)] + [int]$DaysOld = 7, + + [Parameter()] + [switch]$Recurse = $true, + + [Parameter()] + [string]$LogDir = "C:\Temp", + + [Parameter()] + [bool]$LogEnabled = $true +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +# --- Validate source path ----------------------------------------------- +if (-not (Test-Path -LiteralPath $Path -PathType Container)) { + Write-Error "Path '$Path' does not exist or is not a directory." + return +} + +# --- Prepare log file ----------------------------------------------------- +if ($LogEnabled) { + if (-not (Test-Path -LiteralPath $LogDir)) { + New-Item -ItemType Directory -Path $LogDir -Force | Out-Null + Write-Verbose "Log directory created: $LogDir" + } + $LogFile = Join-Path $LogDir ("FileCleanup_{0}.log" -f (Get-Date -Format 'yyyyMMdd_HHmmss')) +} + +function Write-Log { + param([string]$Message, [string]$Level = "INFO") + $line = "{0} [{1}] {2}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Level.PadRight(5), $Message + if ($LogEnabled) { $line | Out-File -FilePath $LogFile -Append -Encoding UTF8 } + Write-Verbose $line +} + +# --- Collect target files -------------------------------------------------- +$Cutoff = (Get-Date).AddDays(-$DaysOld) +$GciParams = @{ + LiteralPath = $Path + File = $true + Recurse = $Recurse.IsPresent + Force = $true # include hidden/system files +} + +Write-Log "=== Invoke-OldFilePurge started ===" +Write-Log "Path : $Path" +Write-Log "DaysOld : $DaysOld (cutoff: $($Cutoff.ToString('yyyy-MM-dd HH:mm:ss')))" +Write-Log "Recurse : $($Recurse.IsPresent)" +Write-Log "LogFile : $(if ($LogEnabled) { $LogFile } else { 'disabled' })" + +Write-Host "Scanning '$Path' for files older than $DaysOld days..." -ForegroundColor Cyan +# Wrapped in @() so .Count is reliable on PS 5.1 even when 0 or exactly 1 file matches +$AllFiles = @(Get-ChildItem @GciParams -ErrorAction SilentlyContinue | + Where-Object { $_.LastWriteTime -lt $Cutoff }) + +$Total = $AllFiles.Count +if ($Total -eq 0) { + Write-Host "No files found matching the criteria." -ForegroundColor Green + Write-Log "No files matched. Exiting." + return +} + +Write-Log "Files to delete: $Total" + +# --- Batch deletion optimised for large numbers of small files ------------- +# .NET IO is significantly faster than Remove-Item in a loop for many small files. +$Deleted = 0 +$Failed = 0 +$BatchSize = 500 # update progress every N files +$StopWatch = [System.Diagnostics.Stopwatch]::StartNew() + +foreach ($File in $AllFiles) { + if ($PSCmdlet.ShouldProcess($File.FullName, "Delete")) { + try { + [System.IO.File]::Delete($File.FullName) + $Deleted++ + Write-Log "DELETED: $($File.FullName)" + } + catch { + $Failed++ + Write-Log "FAILED : $($File.FullName) - $($_.Exception.Message)" -Level "WARN" + } + } + + # Progress bar - update every BatchSize files or on the last one + if (($Deleted + $Failed) % $BatchSize -eq 0 -or ($Deleted + $Failed) -eq $Total) { + $Processed = $Deleted + $Failed + $Pct = [math]::Round(($Processed / $Total) * 100, 1) + $Elapsed = $StopWatch.Elapsed + $Rate = if ($Elapsed.TotalSeconds -gt 0) { [math]::Round($Processed / $Elapsed.TotalSeconds) } else { 0 } + + Write-Progress ` + -Activity "Invoke-OldFilePurge - $Path" ` + -Status "Deleted: $Deleted | Failed: $Failed | Rate: $Rate files/s" ` + -PercentComplete $Pct ` + -CurrentOperation "$Processed / $Total files processed" + } +} + +$StopWatch.Stop() +Write-Progress -Activity "Invoke-OldFilePurge" -Completed + +# --- Summary ----------------------------------------------------------- +$Summary = "Completed in $($StopWatch.Elapsed.ToString('hh\:mm\:ss\.ff')) - Deleted: $Deleted | Failed: $Failed | Total: $Total" +Write-Log $Summary +Write-Log "=== Invoke-OldFilePurge finished ===" + +Write-Host "" +Write-Host $Summary -ForegroundColor $(if ($Failed -gt 0) { 'Yellow' } else { 'Green' }) +if ($LogEnabled) { Write-Host "Log: $LogFile" -ForegroundColor DarkGray } diff --git a/Invoke-OldFilePurge/README.md b/Invoke-OldFilePurge/README.md new file mode 100644 index 0000000..e6c7ee5 --- /dev/null +++ b/Invoke-OldFilePurge/README.md @@ -0,0 +1,119 @@ +# Invoke-OldFilePurge + +## Overview + +This PowerShell script deletes files older than a specified number of days from a target directory. + +It is designed for high-volume deletion of small files — for example Tomcat temp folders, IIS logs, or other directories that accumulate large numbers of files over time. The function: + +- Scans a directory (optionally recursively) for files older than a given age +- Deletes matching files using `[System.IO.File]::Delete()` for speed +- Reports progress (percentage, rate, deleted/failed counts) via `Write-Progress` +- Logs every deletion (and failure) to a timestamped log file +- Supports `-WhatIf` / `-Confirm` via `SupportsShouldProcess` + +--- + +# Prerequisites + +## PowerShell + +PowerShell 5.1 or later. + +--- + +# Files + +## Invoke-OldFilePurge.ps1 + +Defines the `Invoke-OldFilePurge` function. Dot-source the file or copy the function into your session/module before use. + +```powershell +. .\Invoke-OldFilePurge.ps1 +``` + +--- + +# Parameters + +| Parameter | Mandatory | Default | Description | +|-----------|-----------|---------|-------------| +| `Path` | Yes | — | Target directory to clean up. | +| `DaysOld` | No | `7` | Files with `LastWriteTime` older than this many days are deleted. Range: 1–3650. | +| `Recurse` | No | Enabled | If specified, subdirectories are also scanned. | +| `LogDir` | No | `C:\Temp` | Directory for the log file. Created automatically if it does not exist. | +| `LogEnabled` | No | `$true` | Enables or disables logging. | + +--- + +# Running the script + +Preview what would be deleted without actually deleting anything: + +```powershell +Invoke-OldFilePurge.ps1 -Path "C:\Program Files\Apache Software Foundation\Tomcat 9.0\temp" -WhatIf +``` + +Delete files older than 7 days (default): + +```powershell +Invoke-OldFilePurge.ps1 -Path "C:\Program Files\Apache Software Foundation\Tomcat 9.0\temp" +``` + +Delete files older than 30 days and write logs to a custom directory: + +```powershell +Invoke-OldFilePurge.ps1 -Path "D:\IIS\Logs" -DaysOld 30 -LogDir "D:\Logs" +``` + +Run without logging: + +```powershell +Invoke-OldFilePurge.ps1 -Path "D:\Temp" -LogEnabled $false +``` + +--- + +# Logging + +Each execution (when `LogEnabled` is `$true`) creates a log file: + +``` +FileCleanup_YYYYMMDD_HHMMSS.log +``` + +Example: + +``` +2026-07-02 09:12:11 [INFO ] === Invoke-OldFilePurge started === +2026-07-02 09:12:11 [INFO ] Path : D:\IIS\Logs +2026-07-02 09:12:11 [INFO ] DaysOld : 30 (cutoff: 2026-06-02 09:12:11) +2026-07-02 09:12:11 [INFO ] Recurse : True +2026-07-02 09:12:11 [INFO ] Files to delete: 1245 +2026-07-02 09:12:12 [INFO ] DELETED: D:\IIS\Logs\u_ex260101.log +2026-07-02 09:12:12 [WARN ] FAILED : D:\IIS\Logs\u_ex260102.log — file in use +2026-07-02 09:13:05 [INFO ] Completed in 00:00:53.42 — Deleted: 1244 | Failed: 1 | Total: 1245 +2026-07-02 09:13:05 [INFO ] === Invoke-OldFilePurge finished === +``` + +--- + +# Progress reporting + +While running, the script displays a progress bar updated every 500 processed files (or on the last file), showing the count deleted, failed, and the deletion rate (files/second). + +--- + +# Error Handling + +- Validates that `Path` exists and is a directory before starting. +- Each file deletion is wrapped in `try/catch`; failures are counted and logged with `[WARN]` but do not stop the run. +- Supports `-WhatIf` and `-Confirm` (via `SupportsShouldProcess`) to preview or confirm deletions before they happen. + +--- + +# Notes + +- Files are matched purely on `LastWriteTime`; there is no filter on file extension or name. +- Hidden and system files are included (`-Force`). +- Deletion uses `[System.IO.File]::Delete()` instead of `Remove-Item`, which is significantly faster for large numbers of small files.