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.
This commit is contained in:
Petr Štěpán
2026-07-21 13:14:41 +02:00
commit 59cabe37b1
2 changed files with 282 additions and 0 deletions
+163
View File
@@ -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 }
+119
View File
@@ -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: 13650. |
| `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.