Compare commits
2 Commits
ecf3909a4e
...
a7737ebe6f
| Author | SHA1 | Date | |
|---|---|---|---|
| a7737ebe6f | |||
| 8c203251ad |
232
RestoreScript.ps1
Normal file
232
RestoreScript.ps1
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$LiveMountRoot,
|
||||||
|
|
||||||
|
[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 }
|
||||||
|
|
||||||
|
# Find the type position (FULL, DIFF, LOG)
|
||||||
|
$typeIndex = -1
|
||||||
|
$validTypes = @('FULL', 'DIFF', 'LOG')
|
||||||
|
for ($i = 0; $i -lt $parts.Length; $i++) {
|
||||||
|
if ($validTypes -contains $parts[$i]) {
|
||||||
|
$typeIndex = $i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($typeIndex -eq -1 -or ($parts.Length - $typeIndex) -lt 3) { continue }
|
||||||
|
|
||||||
|
# Assume parts[0] is prefix if typeIndex > 1, else dbName is parts[0]
|
||||||
|
if ($typeIndex -eq 1) {
|
||||||
|
$dbName = $parts[0]
|
||||||
|
} else {
|
||||||
|
$dbName = $parts[1..($typeIndex-1)] -join '_'
|
||||||
|
}
|
||||||
|
$type = $parts[$typeIndex]
|
||||||
|
$dateStr = $parts[$typeIndex + 1]
|
||||||
|
$timeStr = $parts[$typeIndex + 2]
|
||||||
|
$stripe = 0
|
||||||
|
if ($parts.Length -gt $typeIndex + 3) {
|
||||||
|
$stripe = [int]$parts[$typeIndex + 3]
|
||||||
|
}
|
||||||
|
$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)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get backup header information
|
||||||
|
$header = Get-BackupInfo -Files $files -Instance $Instance
|
||||||
|
if ($header) {
|
||||||
|
Write-Host " Backup Details:"
|
||||||
|
Write-Host " Start Date: $($header.BackupStartDate)"
|
||||||
|
Write-Host " Finish Date: $($header.BackupFinishDate)"
|
||||||
|
Write-Host " First LSN: $($header.FirstLSN)"
|
||||||
|
Write-Host " Last LSN: $($header.LastLSN)"
|
||||||
|
if ($header.DatabaseBackupLSN) {
|
||||||
|
Write-Host " Database Backup LSN: $($header.DatabaseBackupLSN)"
|
||||||
|
}
|
||||||
|
if ($header.DifferentialBaseLSN) {
|
||||||
|
Write-Host " Differential Base LSN: $($header.DifferentialBaseLSN)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write-Host ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to get backup header information
|
||||||
|
function Get-BackupInfo {
|
||||||
|
param([System.IO.FileInfo[]]$Files, [string]$Instance)
|
||||||
|
|
||||||
|
$fileList = $Files | ForEach-Object { "DISK = '$($_.FullName)'" }
|
||||||
|
$headerQuery = "RESTORE HEADERONLY FROM $($fileList -join ', ')"
|
||||||
|
|
||||||
|
try {
|
||||||
|
$header = Invoke-Sqlcmd -ServerInstance $Instance -Query $headerQuery -QueryTimeout 0
|
||||||
|
return $header
|
||||||
|
} catch {
|
||||||
|
Write-Warning "Failed to get header for $($Files[0].Name): $($_.Exception.Message)"
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main script
|
||||||
|
if ($Action -eq "catalog") {
|
||||||
|
$catalog = Catalog-Backups -Path $LiveMountRoot
|
||||||
|
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 $LiveMountRoot
|
||||||
|
Restore-Database -DbName $DatabaseName -Catalog $catalog -Instance $SqlInstance
|
||||||
|
} elseif ($Action -eq "verify") {
|
||||||
|
$catalog = Catalog-Backups -Path $LiveMountRoot
|
||||||
|
if ($DatabaseName) {
|
||||||
|
$filteredCatalog = @{}
|
||||||
|
if ($catalog.ContainsKey($DatabaseName)) {
|
||||||
|
$filteredCatalog[$DatabaseName] = $catalog[$DatabaseName]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$filteredCatalog = $catalog
|
||||||
|
}
|
||||||
|
Verify-Backups -Catalog $filteredCatalog -Instance $SqlInstance
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user