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 }