Files
Azure/Invoke-OldFilePurge/Invoke-OldFilePurge.ps1
T
Petr Štěpán 59cabe37b1 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.
2026-07-21 13:14:41 +02:00

164 lines
5.6 KiB
PowerShell

<#
.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 }