Add ClearOldLogs.ps1

This commit is contained in:
2026-08-21 16:41:21 +10:00
parent a0c4064e55
commit d0c2935d76

207
ClearOldLogs.ps1 Normal file
View File

@@ -0,0 +1,207 @@
<#
.SYNOPSIS
Interactive deploy for a per-client "Clear Old Logs" scheduled task.
Grants NETWORK SERVICE Modify on the target dirs, registers a daily
Base64-encoded delete task running as NT AUTHORITY\NETWORK SERVICE,
previews the blast radius, and (optionally) test-fires it.
.NOTES
ASP 7.1 change-control applies. Any AV exclusion / registry change is
a separate item and gates through Tom. This script only touches ACLs
on the named log dirs and the Task Scheduler.
Task convention: TaskPath = \<Client>\ TaskName = <Client> - Clear Old Logs
Principal: NT AUTHORITY\NETWORK SERVICE (*S-1-5-20) RunLevel Limited
#>
#Requires -Version 5.1
[CmdletBinding()]
param()
$ErrorActionPreference = 'Stop'
function Confirm-YesNo {
param([string]$Prompt, [bool]$Default = $true)
$suffix = if ($Default) { '[Y/n]' } else { '[y/N]' }
while ($true) {
$r = Read-Host "$Prompt $suffix"
if ([string]::IsNullOrWhiteSpace($r)) { return $Default }
switch -Regex ($r.Trim()) {
'^(y|yes)$' { return $true }
'^(n|no)$' { return $false }
default { Write-Host " yes or no, Beltalowda." -ForegroundColor DarkYellow }
}
}
}
function Read-Default {
param([string]$Prompt, [string]$Default)
$r = Read-Host "$Prompt (default: $Default)"
if ([string]::IsNullOrWhiteSpace($r)) { return $Default }
return $r.Trim()
}
# ---- 0. Elevation gate ---------------------------------------------------
$id = [Security.Principal.WindowsIdentity]::GetCurrent()
$isAdmin = ([Security.Principal.WindowsPrincipal]$id).IsInRole(
[Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
Write-Host "Not elevated. icacls /T and the NETWORK SERVICE principal need admin." -ForegroundColor Red
Write-Host "Re-launch this in an elevated PowerShell and try again." -ForegroundColor Red
exit 1
}
Write-Host ""
Write-Host "=== Log Cleanup Task Deploy ===" -ForegroundColor Cyan
Write-Host "Box: $env:COMPUTERNAME" -ForegroundColor DarkCyan
Write-Host ""
# ---- 1. Client -----------------------------------------------------------
$Client = Read-Default -Prompt 'Client / task name prefix' -Default 'SALSL'
# ---- 2. Paths ------------------------------------------------------------
# Defaults reflect the SALSL box. The smtpproxy path varies per host, so
# it's prompted explicitly rather than assumed.
$defaultLogPath = "C:\Logging\$Client"
$defaultProxyPath = 'C:\smtpproxy.net\logging'
$logPath = Read-Default -Prompt 'Application log path' -Default $defaultLogPath
$proxyPath = Read-Default -Prompt 'smtpproxy log path' -Default $defaultProxyPath
$Paths = New-Object System.Collections.Generic.List[string]
$Paths.Add($logPath)
$Paths.Add($proxyPath)
# allow extra dirs if this box has more than the two
while (Confirm-YesNo -Prompt 'Add another log directory?' -Default $false) {
$extra = Read-Host ' Path'
if (-not [string]::IsNullOrWhiteSpace($extra)) { $Paths.Add($extra.Trim()) }
}
# ---- 3. Retention + schedule --------------------------------------------
$RetentionDays = Read-Default -Prompt 'Retention (days)' -Default '90'
if ($RetentionDays -notmatch '^\d+$' -or [int]$RetentionDays -le 0) {
Write-Host "Retention must be a positive integer. Aborting." -ForegroundColor Red
exit 1
}
$RetentionDays = [int]$RetentionDays
$RunAt = Read-Default -Prompt 'Run daily at' -Default '3am'
# ---- 4. Validate paths BEFORE touching anything --------------------------
# The real silent failure isn't just low privilege - it's a path that
# doesn't exist. icacls and the task will both return 0 against nothing.
Write-Host ""
Write-Host "Validating paths..." -ForegroundColor Cyan
$missing = @()
foreach ($p in $Paths) {
if (Test-Path -LiteralPath $p -PathType Container) {
Write-Host " [ok] $p" -ForegroundColor Green
} else {
Write-Host " [MISSING] $p" -ForegroundColor Red
$missing += $p
}
}
if ($missing.Count -gt 0) {
Write-Host ""
Write-Host "One or more paths don't exist. Fix the typo or create the dir first." -ForegroundColor Red
Write-Host "Refusing to deploy against a phantom directory - that's the exact silent" -ForegroundColor Red
Write-Host "failure we're guarding against." -ForegroundColor Red
exit 1
}
# ---- 5. Summary + confirm ------------------------------------------------
$taskName = "$Client - Clear Old Logs"
$taskPath = "\$Client\"
Write-Host ""
Write-Host "--- Summary ---" -ForegroundColor Cyan
Write-Host " Client : $Client"
Write-Host " Task : $taskPath$taskName"
Write-Host " Retention : $RetentionDays days"
Write-Host " Run daily at : $RunAt"
Write-Host " Principal : NT AUTHORITY\NETWORK SERVICE (Limited)"
Write-Host " Paths :"
$Paths | ForEach-Object { Write-Host " $_" }
Write-Host ""
$existing = Get-ScheduledTask -TaskPath $taskPath -TaskName $taskName -ErrorAction SilentlyContinue
if ($existing) {
Write-Host "NOTE: a task named '$taskName' already exists at $taskPath - it will be OVERWRITTEN." -ForegroundColor Yellow
}
if (-not (Confirm-YesNo -Prompt 'Proceed?' -Default $true)) {
Write-Host "Aborted. Nothing changed." -ForegroundColor Yellow
exit 0
}
# ---- 6. Grant NETWORK SERVICE Modify (idempotent) ------------------------
if (Confirm-YesNo -Prompt 'Grant NETWORK SERVICE Modify on the paths? (safe to re-run)' -Default $true) {
foreach ($p in $Paths) {
Write-Host " icacls grant on $p" -ForegroundColor DarkCyan
& icacls $p /grant '*S-1-5-20:(OI)(CI)M' /T /C | Out-Null
if ($LASTEXITCODE -ne 0) {
Write-Host " icacls returned $LASTEXITCODE on $p - check the path/permissions." -ForegroundColor Yellow
}
}
} else {
Write-Host " Skipping ACL grant (assuming already set)." -ForegroundColor DarkYellow
}
# ---- 7. Blast-radius preview (deletes NOTHING) ---------------------------
Write-Host ""
Write-Host "Blast radius (files older than $RetentionDays days) - NOTHING deleted yet:" -ForegroundColor Cyan
$cutoff = (Get-Date).AddDays(-$RetentionDays)
$totalDoomed = 0
foreach ($p in $Paths) {
$doomed = @(Get-ChildItem -LiteralPath $p -Recurse -File -ErrorAction SilentlyContinue |
Where-Object { $_.LastWriteTime -lt $cutoff })
$totalDoomed += $doomed.Count
Write-Host " $p : $($doomed.Count) file(s)" -ForegroundColor $(if ($doomed.Count) { 'Yellow' } else { 'Green' })
}
Write-Host " TOTAL would be removed on next run: $totalDoomed" -ForegroundColor Yellow
Write-Host ""
if (-not (Confirm-YesNo -Prompt 'Blast radius look right? Register the task?' -Default $true)) {
Write-Host "Aborted before registration. ACLs may already be set; task NOT created." -ForegroundColor Yellow
exit 0
}
# ---- 8. Build encoded delete command + register --------------------------
# Base64 sidesteps every quoting/pipe issue in the task argument field.
$pathList = ($Paths | ForEach-Object { "'$_'" }) -join ','
$cmd = "Get-ChildItem $pathList -Recurse -File | Where-Object LastWriteTime -lt (Get-Date).AddDays(-$RetentionDays) | Remove-Item -Force"
$enc = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($cmd))
$psExe = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe"
$action = New-ScheduledTaskAction -Execute $psExe -Argument "-NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand $enc"
$trigger = New-ScheduledTaskTrigger -Daily -At $RunAt
$principal = New-ScheduledTaskPrincipal -UserId 'NT AUTHORITY\NETWORK SERVICE' -LogonType ServiceAccount -RunLevel Limited
$settings = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Hours 1)
Register-ScheduledTask -TaskName $taskName -TaskPath $taskPath -Action $action -Trigger $trigger `
-Principal $principal -Settings $settings `
-Description "Deletes files older than $RetentionDays days (recursive) under the $Client log dirs. ASP 7.1." -Force | Out-Null
Write-Host "Task registered: $taskPath$taskName" -ForegroundColor Green
# ---- 9. Test on demand ---------------------------------------------------
if (Confirm-YesNo -Prompt 'Test-fire the task now and check the result?' -Default $true) {
Start-ScheduledTask -TaskPath $taskPath -TaskName $taskName
Start-Sleep -Seconds 8
$info = Get-ScheduledTaskInfo -TaskPath $taskPath -TaskName $taskName
Write-Host ""
Write-Host " LastRunTime : $($info.LastRunTime)"
$col = if ($info.LastTaskResult -eq 0) { 'Green' } else { 'Red' }
Write-Host " LastTaskResult : $($info.LastTaskResult)" -ForegroundColor $col
if ($info.LastTaskResult -eq 0) {
Write-Host ""
Write-Host "Result 0. Now eyeball the dirs - old files gone, recent ones untouched." -ForegroundColor Green
Write-Host "Don't write the KB article until you've confirmed that by eye." -ForegroundColor DarkYellow
} else {
Write-Host ""
Write-Host "Non-zero result. Task ran but something's off - check the dirs and" -ForegroundColor Red
Write-Host "the principal's Modify rights before trusting an unattended run." -ForegroundColor Red
}
}
Write-Host ""
Write-Host "Done." -ForegroundColor Cyan