207 lines
9.1 KiB
PowerShell
207 lines
9.1 KiB
PowerShell
<#
|
|
.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 |