From 8c203251adffeb6d1c18d50444f63f71c9acd556 Mon Sep 17 00:00:00 2001 From: James Pattinson Date: Thu, 30 Oct 2025 11:00:36 +0000 Subject: [PATCH] first restore script --- RestoreScript.ps1 | 183 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 RestoreScript.ps1 diff --git a/RestoreScript.ps1 b/RestoreScript.ps1 new file mode 100644 index 0000000..374ad9f --- /dev/null +++ b/RestoreScript.ps1 @@ -0,0 +1,183 @@ +param( + [Parameter(Mandatory=$true)] + [string]$FolderPath, + + [Parameter(Mandatory=$false)] + [string]$DatabaseName, + + [Parameter(Mandatory=$true)] + [ValidateSet("catalog", "restore", "verify")] + [string]$Action, + + [Parameter(Mandatory=$false)] + [string]$SqlInstance = "sqlfcsql\TESTINST" +) + +# Function to catalog backups +function Catalog-Backups { + param([string]$Path) + + $backupFiles = Get-ChildItem -Path $Path -Recurse -File | Where-Object { $_.Extension -eq '.bak' -or $_.Extension -eq '.trn' } + + $catalog = @{} + + foreach ($file in $backupFiles) { + $baseName = $file.BaseName + $parts = $baseName -split '_' + if ($parts.Length -lt 4) { continue } + $dbName = $parts[1] + $type = $parts[2] + $dateStr = $parts[3] + $timeStr = $parts[4] + $stripe = 0 + if ($parts.Length -gt 5) { + $stripe = [int]$parts[5] + } + $key = "$dateStr$timeStr" + + if (-not $catalog.ContainsKey($dbName)) { $catalog[$dbName] = @{} } + if (-not $catalog[$dbName].ContainsKey($type)) { $catalog[$dbName][$type] = @{} } + if (-not $catalog[$dbName][$type].ContainsKey($key)) { $catalog[$dbName][$type][$key] = @() } + $catalog[$dbName][$type][$key] += @{File = $file; Stripe = $stripe} + } + + return $catalog +} + +# Function to report catalog +function Report-Catalog { + param([hashtable]$Catalog) + + Write-Host "Database Backups Catalog:" + Write-Host "=========================" + + foreach ($db in $catalog.Keys | Sort-Object) { + Write-Host "Database: $db" + foreach ($type in $catalog[$db].Keys | Sort-Object) { + Write-Host " Type: $type" + foreach ($key in $catalog[$db][$type].Keys | Sort-Object -Descending) { + Write-Host " Backup: $key" + $files = $catalog[$db][$type][$key] | Sort-Object { $_.Stripe } + foreach ($item in $files) { + Write-Host " $($item.File.FullName)" + } + } + } + Write-Host "" + } +} + +# Function to restore database +function Restore-Database { + param([string]$DbName, [hashtable]$Catalog, [string]$Instance) + + if (-not $catalog.ContainsKey($DbName)) { + Write-Error "Database $DbName not found in catalog" + return + } + + $dbCatalog = $catalog[$DbName] + + # Find the latest FULL backup + if (-not $dbCatalog.ContainsKey('FULL')) { + Write-Error "No FULL backup found for database $DbName" + return + } + + $latestFullKey = $dbCatalog['FULL'].Keys | Sort-Object -Descending | Select-Object -First 1 + $fullFiles = $dbCatalog['FULL'][$latestFullKey] | Sort-Object { $_.Stripe } | ForEach-Object { $_.File } + + # Restore FULL with NORECOVERY + $fileList = $fullFiles | ForEach-Object { "DISK = '$($_.FullName)'" } + $restoreQuery = "RESTORE DATABASE [$DbName] FROM $($fileList -join ', ') WITH NORECOVERY" + Write-Host "Restoring FULL backup for $DbName..." + Invoke-Sqlcmd -ServerInstance $Instance -Query $restoreQuery -QueryTimeout 0 + + # Apply DIFF backups after the FULL + if ($dbCatalog.ContainsKey('DIFF')) { + $diffKeys = $dbCatalog['DIFF'].Keys | Where-Object { $_ -gt $latestFullKey } | Sort-Object + foreach ($key in $diffKeys) { + $diffFiles = $dbCatalog['DIFF'][$key] | Sort-Object { $_.Stripe } | ForEach-Object { $_.File } + $fileList = $diffFiles | ForEach-Object { "DISK = '$($_.FullName)'" } + $restoreQuery = "RESTORE DATABASE [$DbName] FROM $($fileList -join ', ') WITH NORECOVERY" + Write-Host "Applying DIFF backup $key for $DbName..." + Invoke-Sqlcmd -ServerInstance $Instance -Query $restoreQuery -QueryTimeout 0 + } + } + + # Apply LOG backups after the FULL + if ($dbCatalog.ContainsKey('LOG')) { + $logKeys = $dbCatalog['LOG'].Keys | Where-Object { $_ -gt $latestFullKey } | Sort-Object + foreach ($key in $logKeys) { + $logFiles = $dbCatalog['LOG'][$key] | Sort-Object { $_.Stripe } | ForEach-Object { $_.File } + $fileList = $logFiles | ForEach-Object { "DISK = '$($_.FullName)'" } + $restoreQuery = "RESTORE LOG [$DbName] FROM $($fileList -join ', ') WITH NORECOVERY" + Write-Host "Applying LOG backup $key for $DbName..." + Invoke-Sqlcmd -ServerInstance $Instance -Query $restoreQuery -QueryTimeout 0 + } + } + + # Final recovery + $restoreQuery = "RESTORE DATABASE [$DbName] WITH RECOVERY" + Write-Host "Finalizing restore for $DbName..." + Invoke-Sqlcmd -ServerInstance $Instance -Query $restoreQuery -QueryTimeout 0 + + Write-Host "Restore completed for $DbName" +} + +# Function to verify backups +function Verify-Backups { + param([hashtable]$Catalog, [string]$Instance) + + foreach ($db in $catalog.Keys | Sort-Object) { + Write-Host "Verifying backups for database: $db" + foreach ($type in $catalog[$db].Keys | Sort-Object) { + foreach ($key in $catalog[$db][$type].Keys | Sort-Object) { + $files = $catalog[$db][$type][$key] | Sort-Object { $_.Stripe } | ForEach-Object { $_.File } + $fileList = $files | ForEach-Object { "DISK = '$($_.FullName)'" } + $verifyQuery = "RESTORE VERIFYONLY FROM $($fileList -join ', ')" + Write-Host "Verifying $type backup $key for $db..." + try { + Invoke-Sqlcmd -ServerInstance $Instance -Query $verifyQuery -QueryTimeout 0 + Write-Host "Verification successful for $type $key" + } catch { + Write-Host "Verification failed for $type $key : $($_.Exception.Message)" + } + } + } + Write-Host "" + } +} + +# Main script +if ($Action -eq "catalog") { + $catalog = Catalog-Backups -Path $FolderPath + if ($DatabaseName) { + $filteredCatalog = @{} + if ($catalog.ContainsKey($DatabaseName)) { + $filteredCatalog[$DatabaseName] = $catalog[$DatabaseName] + } + Report-Catalog -Catalog $filteredCatalog + } else { + Report-Catalog -Catalog $catalog + } +} elseif ($Action -eq "restore") { + if (-not $DatabaseName) { + Write-Error "DatabaseName is required for restore action" + exit 1 + } + + $catalog = Catalog-Backups -Path $FolderPath + Restore-Database -DbName $DatabaseName -Catalog $catalog -Instance $SqlInstance +} elseif ($Action -eq "verify") { + $catalog = Catalog-Backups -Path $FolderPath + if ($DatabaseName) { + $filteredCatalog = @{} + if ($catalog.ContainsKey($DatabaseName)) { + $filteredCatalog[$DatabaseName] = $catalog[$DatabaseName] + } + } else { + $filteredCatalog = $catalog + } + Verify-Backups -Catalog $filteredCatalog -Instance $SqlInstance +} \ No newline at end of file