Add NewName option and recovery fixes
This commit is contained in:
+118
-17
@@ -10,7 +10,16 @@ param(
|
||||
[string]$Action,
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$SqlInstance = "sqlfcsql\TESTINST"
|
||||
[string]$SqlInstance = "sqlfcsql\TESTINST",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$DataPath,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$LogPath,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$NewName
|
||||
)
|
||||
|
||||
# Function to catalog backups
|
||||
@@ -82,11 +91,16 @@ function Report-Catalog {
|
||||
}
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
Write-Host "Summary of Databases:"
|
||||
$catalog.Keys | Sort-Object | ForEach-Object { Write-Host " - $_" }
|
||||
}
|
||||
|
||||
# Function to restore database
|
||||
function Restore-Database {
|
||||
param([string]$DbName, [hashtable]$Catalog, [string]$Instance)
|
||||
param([string]$DbName, [hashtable]$Catalog, [string]$Instance, [string]$DataPath, [string]$LogPath, [string]$TargetDbName)
|
||||
|
||||
$restoreDbName = if ($TargetDbName) { $TargetDbName } else { $DbName }
|
||||
|
||||
if (-not $catalog.ContainsKey($DbName)) {
|
||||
Write-Error "Database $DbName not found in catalog"
|
||||
@@ -103,43 +117,121 @@ function Restore-Database {
|
||||
|
||||
$latestFullKey = $dbCatalog['FULL'].Keys | Sort-Object -Descending | Select-Object -First 1
|
||||
$fullFiles = $dbCatalog['FULL'][$latestFullKey] | Sort-Object { $_.Stripe } | ForEach-Object { $_.File }
|
||||
$fullHeader = Get-BackupInfo -Files $fullFiles -Instance $Instance
|
||||
if (-not $fullHeader) {
|
||||
Write-Error "Failed to read FULL backup header for database $DbName"
|
||||
return
|
||||
}
|
||||
|
||||
$fullBaseLsn = $fullHeader.CheckpointLSN
|
||||
if (-not $fullBaseLsn) {
|
||||
$fullBaseLsn = $fullHeader.FirstLSN
|
||||
}
|
||||
|
||||
# Create directories if specified
|
||||
if ($DataPath -and -not (Test-Path $DataPath)) {
|
||||
New-Item -ItemType Directory -Path $DataPath -Force
|
||||
}
|
||||
if ($LogPath -and -not (Test-Path $LogPath)) {
|
||||
New-Item -ItemType Directory -Path $LogPath -Force
|
||||
}
|
||||
|
||||
# Get file list for MOVE clauses
|
||||
$fileListStr = $fullFiles | ForEach-Object { "DISK = '$($_.FullName)'" }
|
||||
$fileListOnlyQuery = "RESTORE FILELISTONLY FROM $($fileListStr -join ', ')"
|
||||
$fileListResult = Invoke-Sqlcmd -ServerInstance $Instance -Query $fileListOnlyQuery -QueryTimeout 0 -ErrorAction Stop
|
||||
|
||||
$moveClauses = @()
|
||||
$dataFileIndex = 0
|
||||
$logFileIndex = 0
|
||||
foreach ($file in $fileListResult) {
|
||||
$logicalName = $file.LogicalName
|
||||
$type = $file.Type
|
||||
$originalName = $file.PhysicalName
|
||||
$extension = [System.IO.Path]::GetExtension($originalName)
|
||||
$baseName = [System.IO.Path]::GetFileName($originalName)
|
||||
|
||||
# When restoring as a different database name, rewrite physical file names
|
||||
# so they do not clash with the source database files on disk.
|
||||
if ($TargetDbName) {
|
||||
if ($type -eq 'D') {
|
||||
$dataFileIndex++
|
||||
$newBaseName = if ($dataFileIndex -eq 1) { "${restoreDbName}_Data" } else { "${restoreDbName}_Data$dataFileIndex" }
|
||||
$baseName = "$newBaseName$extension"
|
||||
} elseif ($type -eq 'L') {
|
||||
$logFileIndex++
|
||||
$newBaseName = if ($logFileIndex -eq 1) { "${restoreDbName}_Log" } else { "${restoreDbName}_Log$logFileIndex" }
|
||||
$baseName = "$newBaseName$extension"
|
||||
}
|
||||
}
|
||||
|
||||
if ($type -eq 'D' -and $DataPath) {
|
||||
$newPath = Join-Path $DataPath $baseName
|
||||
$moveClauses += "MOVE '$logicalName' TO '$newPath'"
|
||||
} elseif ($type -eq 'L' -and $LogPath) {
|
||||
$newPath = Join-Path $LogPath $baseName
|
||||
$moveClauses += "MOVE '$logicalName' TO '$newPath'"
|
||||
}
|
||||
}
|
||||
|
||||
# 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..."
|
||||
$withClause = if ($moveClauses) { "WITH $($moveClauses -join ', '), NORECOVERY" } else { "WITH NORECOVERY" }
|
||||
$restoreQuery = "RESTORE DATABASE [$restoreDbName] FROM $($fileListStr -join ', ') $withClause"
|
||||
if ($TargetDbName) {
|
||||
Write-Host "Restoring FULL backup for $DbName as $restoreDbName..."
|
||||
} else {
|
||||
Write-Host "Restoring FULL backup for $DbName..."
|
||||
}
|
||||
Invoke-Sqlcmd -ServerInstance $Instance -Query $restoreQuery -QueryTimeout 0 -ErrorAction Stop
|
||||
|
||||
# Apply DIFF backups after the FULL
|
||||
# Choose the latest DIFF that is compatible with the selected FULL.
|
||||
$selectedDiffKey = $null
|
||||
if ($dbCatalog.ContainsKey('DIFF')) {
|
||||
$compatibleDiffs = @()
|
||||
$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..."
|
||||
$diffHeader = Get-BackupInfo -Files $diffFiles -Instance $Instance
|
||||
if (-not $diffHeader) {
|
||||
Write-Warning "Skipping DIFF backup $key for $DbName because its header could not be read"
|
||||
continue
|
||||
}
|
||||
|
||||
if ($diffHeader.DifferentialBaseLSN -eq $fullBaseLsn) {
|
||||
$compatibleDiffs += @{ Key = $key; Files = $diffFiles }
|
||||
}
|
||||
}
|
||||
|
||||
if ($compatibleDiffs.Count -gt 0) {
|
||||
$selectedDiff = $compatibleDiffs | Sort-Object { $_.Key } -Descending | Select-Object -First 1
|
||||
$selectedDiffKey = $selectedDiff.Key
|
||||
$fileList = $selectedDiff.Files | ForEach-Object { "DISK = '$($_.FullName)'" }
|
||||
$restoreQuery = "RESTORE DATABASE [$restoreDbName] FROM $($fileList -join ', ') WITH NORECOVERY"
|
||||
Write-Host "Applying DIFF backup $selectedDiffKey for $restoreDbName..."
|
||||
Invoke-Sqlcmd -ServerInstance $Instance -Query $restoreQuery -QueryTimeout 0 -ErrorAction Stop
|
||||
}
|
||||
}
|
||||
|
||||
# Apply LOG backups after the FULL
|
||||
$logStartKey = if ($selectedDiffKey) { $selectedDiffKey } else { $latestFullKey }
|
||||
|
||||
# Apply LOG backups after the latest restored FULL/DIFF point
|
||||
if ($dbCatalog.ContainsKey('LOG')) {
|
||||
$logKeys = $dbCatalog['LOG'].Keys | Where-Object { $_ -gt $latestFullKey } | Sort-Object
|
||||
$logKeys = $dbCatalog['LOG'].Keys | Where-Object { $_ -gt $logStartKey } | 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..."
|
||||
$restoreQuery = "RESTORE LOG [$restoreDbName] FROM $($fileList -join ', ') WITH NORECOVERY"
|
||||
Write-Host "Applying LOG backup $key for $restoreDbName..."
|
||||
Invoke-Sqlcmd -ServerInstance $Instance -Query $restoreQuery -QueryTimeout 0 -ErrorAction Stop
|
||||
}
|
||||
}
|
||||
|
||||
# Final recovery
|
||||
$restoreQuery = "RESTORE DATABASE [$DbName] WITH RECOVERY"
|
||||
Write-Host "Finalizing restore for $DbName..."
|
||||
$restoreQuery = "RESTORE DATABASE [$restoreDbName] WITH RECOVERY"
|
||||
Write-Host "Finalizing restore for $restoreDbName..."
|
||||
Invoke-Sqlcmd -ServerInstance $Instance -Query $restoreQuery -QueryTimeout 0 -ErrorAction Stop
|
||||
|
||||
Write-Host "Restore completed for $DbName"
|
||||
Write-Host "Restore completed for $restoreDbName"
|
||||
}
|
||||
|
||||
# Function to print backup summary
|
||||
@@ -271,6 +363,11 @@ function Get-BackupInfo {
|
||||
}
|
||||
|
||||
# Main script
|
||||
if ($Action -ne "restore" -and $NewName) {
|
||||
Write-Error "NewName is only valid with restore action"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ($Action -eq "catalog") {
|
||||
$catalog = Catalog-Backups -Path $LiveMountRoot
|
||||
if ($DatabaseName) {
|
||||
@@ -287,9 +384,13 @@ if ($Action -eq "catalog") {
|
||||
Write-Error "DatabaseName is required for restore action"
|
||||
exit 1
|
||||
}
|
||||
if (-not $DataPath -or -not $LogPath) {
|
||||
Write-Error "DataPath and LogPath are required for restore action"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$catalog = Catalog-Backups -Path $LiveMountRoot
|
||||
Restore-Database -DbName $DatabaseName -Catalog $catalog -Instance $SqlInstance
|
||||
Restore-Database -DbName $DatabaseName -Catalog $catalog -Instance $SqlInstance -DataPath $DataPath -LogPath $LogPath -TargetDbName $NewName
|
||||
} elseif ($Action -eq "verify") {
|
||||
$catalog = Catalog-Backups -Path $LiveMountRoot
|
||||
if ($DatabaseName) {
|
||||
|
||||
Reference in New Issue
Block a user