From ecf3909a4e724f1b9330071d61d61d594bf62a10 Mon Sep 17 00:00:00 2001 From: James Pattinson Date: Wed, 29 Oct 2025 14:50:08 +0000 Subject: [PATCH] Added Nuke flag --- backupmult.ps1 | 187 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 124 insertions(+), 63 deletions(-) diff --git a/backupmult.ps1 b/backupmult.ps1 index 5eff343..b5b8400 100644 --- a/backupmult.ps1 +++ b/backupmult.ps1 @@ -10,6 +10,9 @@ param( [Parameter(Mandatory=$false)] [int]$LogRetentionDays = 30 + , + [Parameter(Mandatory=$false)] + [switch]$Nuke ) # backupmult.ps1 - Parallel database backup script using Ola H @@ -256,6 +259,49 @@ try { exit 1 } +# If -Nuke is set, delete the contents of each retrieved path (but keep the folder itself). +if ($Nuke) { + Write-Log "INFO: -nuke flag set. Beginning recursive deletion of contents for retrieved paths." + foreach ($p in $paths) { + if (-not $p) { continue } + $pathToCheck = $p.Trim() + + # Determine root to avoid deleting drive root like C:\ + try { $root = [IO.Path]::GetPathRoot($pathToCheck) } catch { $root = $null } + + if ([string]::IsNullOrEmpty($pathToCheck)) { + Write-Log "WARNING: Skipping empty path entry" + continue + } + + if ($root -and ($pathToCheck.TrimEnd('\') -eq $root.TrimEnd('\'))) { + Write-Log "ERROR: Refusing to nuke root path '$pathToCheck'. Skipping." + continue + } + + if (-not (Test-Path -LiteralPath $pathToCheck)) { + Write-Log "WARNING: Path '$pathToCheck' does not exist. Skipping." + continue + } + + Write-Log "INFO: NUKING contents of '$pathToCheck' (deleting all files & subfolders inside)." + try { + # Enumerate children and delete each item so the folder itself remains + Get-ChildItem -LiteralPath $pathToCheck -Force -ErrorAction SilentlyContinue | ForEach-Object { + try { + Remove-Item -LiteralPath $_.FullName -Recurse -Force -ErrorAction Stop + Write-Log "INFO: Deleted: $($_.FullName)" + } catch { + Write-Log "WARNING: Failed to delete $($_.FullName): $($_.Exception.Message)" + } + } + } catch { + Write-Log "ERROR: Failed to enumerate or delete contents of '$pathToCheck': $($_.Exception.Message)" + } + } + Write-Log "INFO: -nuke operation complete. Continuing with backup flow." +} + $directoryParam = $paths -join ', ' # Validate job count @@ -269,67 +315,104 @@ Write-Log "INFO: Starting $Jobs parallel backup jobs" $today = (Get-Date).Date function Get-BackupType($directoryParam) { - # Use first directory to check flags (assuming shared flag logic across all directories) - $firstDir = ($directoryParam -split ',')[0].Trim() - $fullFlag = Join-Path $firstDir "last_full.flag" - $diffFlag = Join-Path $firstDir "last_diff.flag" - - # Check if full backup is overdue + # Support multiple candidate directories. Scan them in deterministic order for existing flags. + $dirs = @() + if ($directoryParam) { + $dirs = $directoryParam -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' } + } + + # Build lists of found flags (in candidate order) + $foundFull = @() + $foundDiff = @() + foreach ($d in $dirs) { + $full = Join-Path $d "last_full.flag" + $diff = Join-Path $d "last_diff.flag" + if (Test-Path $full) { $foundFull += $full } + if (Test-Path $diff) { $foundDiff += $diff } + } + + # Determine if full backup is overdue using the first-found full flag (if any) $isFullBackupOverdue = $false - if (Test-Path $fullFlag) { + if ($foundFull.Count -gt 0) { + $fullFlag = $foundFull[0] try { $lastFullDate = [DateTime]::ParseExact((Get-Content $fullFlag).Trim(), "yyyy-MM-dd", $null) $daysSinceLastFull = ($today - $lastFullDate).Days $isFullBackupOverdue = $daysSinceLastFull -gt $fullBackupOverdueDays - Write-Log "INFO: Last full backup was $daysSinceLastFull days ago. Overdue threshold: $fullBackupOverdueDays days." - } - catch { + Write-Log "INFO: Last full backup was $daysSinceLastFull days ago (from $fullFlag). Overdue threshold: $fullBackupOverdueDays days." + } catch { $isFullBackupOverdue = $true - Write-Log "WARNING: Could not parse last full backup date. Treating as overdue." + Write-Log "WARNING: Could not parse last full backup date in $fullFlag. Treating as overdue." } } else { $isFullBackupOverdue = $true - Write-Log "WARNING: No last full backup date found. Treating as overdue." + Write-Log "WARNING: No last full backup date found in any candidate directories. Treating as overdue." } - - # Determine backup type + + # Helper to ensure directory exists + function Ensure-DirExists([string]$path) { + if (-not (Test-Path $path)) { + try { New-Item -ItemType Directory -Path $path -Force | Out-Null } catch { } + } + } + + # Determine preferred write location: prefer existing related flag location, otherwise first candidate dir + $firstDir = $dirs[0] + + # If it's a full backup day or overdue, plan for full backup if ((Get-Date).DayOfWeek -eq $fullBackupDay -or $isFullBackupOverdue) { - if (-not (Test-Path $fullFlag) -or (Get-Content $fullFlag).Trim() -ne $today.ToString("yyyy-MM-dd")) { - # Create flag directory if it doesn't exist - $flagDir = Split-Path $fullFlag -Parent - if (-not (Test-Path $flagDir)) { - New-Item -ItemType Directory -Path $flagDir -Force | Out-Null - } + # If a full flag exists, use its location; else use firstDir + $targetFullFlag = if ($foundFull.Count -gt 0) { $foundFull[0] } else { Join-Path $firstDir "last_full.flag" } + $targetDir = Split-Path $targetFullFlag -Parent + Ensure-DirExists $targetDir + + $currentValue = $null + if (Test-Path $targetFullFlag) { + try { $currentValue = (Get-Content $targetFullFlag).Trim() } catch { $currentValue = $null } + } + + if (-not $currentValue -or $currentValue -ne $today.ToString("yyyy-MM-dd")) { try { - Set-Content $fullFlag $today.ToString("yyyy-MM-dd") -Encoding UTF8 - Write-Log "INFO: Created full backup flag file: $fullFlag" + Set-Content -Path $targetFullFlag -Value $today.ToString("yyyy-MM-dd") -Encoding UTF8 + Write-Log "INFO: Created/Updated full backup flag file: $targetFullFlag" } catch { - Write-Log "ERROR: Failed to create full backup flag file: $fullFlag. $($_.Exception.Message)" - # Continue with backup but flag won't be set + Write-Log "ERROR: Failed to create/update full backup flag file: $targetFullFlag. $($_.Exception.Message)" } - $reason = if($isFullBackupOverdue) { "overdue" } else { "scheduled" } + $reason = if ($isFullBackupOverdue) { "overdue" } else { "scheduled" } return @{ Type = "FULL"; CleanupTime = 168; Reason = $reason } } else { return @{ Type = "LOG"; CleanupTime = 24; Reason = "full already taken today" } } + } + + # Otherwise, plan for differential + # Prefer an existing diff flag location if present; else prefer the existing full flag location (write diff alongside full); otherwise firstDir + if ($foundDiff.Count -gt 0) { + $targetDiffFlag = $foundDiff[0] + } elseif ($foundFull.Count -gt 0) { + $targetDiffFlag = Join-Path (Split-Path $foundFull[0] -Parent) "last_diff.flag" } else { - if (-not (Test-Path $diffFlag) -or (Get-Content $diffFlag).Trim() -ne $today.ToString("yyyy-MM-dd")) { - # Create flag directory if it doesn't exist - $flagDir = Split-Path $diffFlag -Parent - if (-not (Test-Path $flagDir)) { - New-Item -ItemType Directory -Path $flagDir -Force | Out-Null - } - try { - Set-Content $diffFlag $today.ToString("yyyy-MM-dd") -Encoding UTF8 - Write-Log "INFO: Created diff backup flag file: $diffFlag" - } catch { - Write-Log "ERROR: Failed to create diff backup flag file: $diffFlag. $($_.Exception.Message)" - # Continue with backup but flag won't be set - } - return @{ Type = "DIFF"; CleanupTime = 168; Reason = "differential scheduled" } - } else { - return @{ Type = "LOG"; CleanupTime = 24; Reason = "diff already taken today" } + $targetDiffFlag = Join-Path $firstDir "last_diff.flag" + } + + $targetDir = Split-Path $targetDiffFlag -Parent + Ensure-DirExists $targetDir + + $currentDiffValue = $null + if (Test-Path $targetDiffFlag) { + try { $currentDiffValue = (Get-Content $targetDiffFlag).Trim() } catch { $currentDiffValue = $null } + } + + if (-not $currentDiffValue -or $currentDiffValue -ne $today.ToString("yyyy-MM-dd")) { + try { + Set-Content -Path $targetDiffFlag -Value $today.ToString("yyyy-MM-dd") -Encoding UTF8 + Write-Log "INFO: Created/Updated diff backup flag file: $targetDiffFlag" + } catch { + Write-Log "ERROR: Failed to create/update diff backup flag file: $targetDiffFlag. $($_.Exception.Message)" } + return @{ Type = "DIFF"; CleanupTime = 168; Reason = "differential scheduled" } + } else { + return @{ Type = "LOG"; CleanupTime = 24; Reason = "diff already taken today" } } } @@ -632,28 +715,6 @@ foreach ($job in $jobList) { } } -# Consolidate job logs into main log file -#Write-Log "Consolidating job logs..." -#for ($i = 1; $i -le $Jobs; $i++) { -# $jobLogFile = $logFile -replace '\.log$', "-job$i.log" -# Write-Log "Checking for job log file: $jobLogFile" -# if (Test-Path $jobLogFile) { -# try { -# $jobContent = Get-Content $jobLogFile -ErrorAction Stop -# Write-Log "Found $($jobContent.Count) lines in job $i log" -# foreach ($line in $jobContent) { -# Add-Content -Path $logFile -Value $line -Encoding UTF8 -# } -# # Remove-Item $jobLogFile -Force -# Write-Log "Consolidated log from job $i" -# } catch { -# Write-Log "WARNING: Could not consolidate log from job $i : $($_.Exception.Message)" -# } -# } else { -# Write-Log "WARNING: Job log file not found for job $i" -# } -#} - # Final status check using job output analysis $failedJobIds = $jobResults.Keys | Where-Object { $jobResults[$_].Failed -eq $true }