59cabe37b1
- Implemented Invoke-OldFilePurge.ps1 to remove files older than a specified number of days. - Added README.md with usage instructions, parameters, and logging details.
164 lines
5.6 KiB
PowerShell
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 }
|