From 2e34c9ce41359813938f94b277a1e3714890e25b Mon Sep 17 00:00:00 2001 From: Andreas Jordan Date: Sat, 29 Aug 2026 14:14:39 +0200 Subject: [PATCH] Restore-DbaDatabase - Honor EnableException in the backup information pipeline The splats for Get-DbaBackupInformation and Select-DbaBackupInformation never passed EnableException on, so with -EnableException a missing full backup (or a failed scan) degraded to a warning inside the callee, control returned, and the restore continued as a successful-looking no-op over an empty backup history. The empty-history bail-out was a plain warning for the same reason. This is how a test fixture could restore diff+log without their full for years and only fail on a minute boundary (#10620). EnableException now flows into both splats and into the per-file Get-DbaBackupInformation call of the SQL Server 2000 path, and the "No backups passed through" bail-out is a Stop-Function. Without -EnableException nothing changes: Stop-Function writes the identical warning and returns. The Test-DbaBackupInformation call already ran with EnableException inside try/catch and stays as it is, and Format-DbaBackupInformation has no failure path to propagate. Verified in the lab on SQL03\SQL2019: 84 tests, 76 passed, 0 failed, 7 skipped, including three new regression tests - diff+log without the full throws with EnableException, still warns and returns nothing without it, and an empty directory throws instead of warning. Fixes #10621 (do Restore-DbaDatabase) Co-Authored-By: Claude Fable 5 --- public/Restore-DbaDatabase.ps1 | 11 ++++- tests/Restore-DbaDatabase.Tests.ps1 | 70 +++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/public/Restore-DbaDatabase.ps1 b/public/Restore-DbaDatabase.ps1 index c8b0286da456..59e9030cf9bc 100644 --- a/public/Restore-DbaDatabase.ps1 +++ b/public/Restore-DbaDatabase.ps1 @@ -676,7 +676,12 @@ function Restore-DbaDatabase { if ($RestoreInstance.VersionMajor -eq 8 -and $true -ne $TrustDbBackupHistory) { foreach ($file in $Path) { - $bh = Get-DbaBackupInformation -SqlInstance $RestoreInstance -Path $file + $splatBackupInformation = @{ + SqlInstance = $RestoreInstance + Path = $file + EnableException = $EnableException + } + $bh = Get-DbaBackupInformation @splatBackupInformation $bound = $PSBoundParameters $bound['TrustDbBackupHistory'] = $true $bound['Path'] = $bh @@ -752,6 +757,7 @@ function Restore-DbaDatabase { IgnoreLogBackup = $IgnoreLogBackup StorageCredential = $StorageCredential NoXpDirRecurse = $NoXpDirRecurse + EnableException = $EnableException } $BackupHistory += Get-DbaBackupInformation @parms } @@ -815,7 +821,7 @@ function Restore-DbaDatabase { } if ($PSCmdlet.ParameterSetName -like "Restore*") { if ($BackupHistory.Count -eq 0 -and $RestoreInstance.VersionMajor -ne 8) { - Write-Message -Level Warning -Message "No backups passed through. `n This could mean the SQL instance cannot see the referenced files, the file's headers could not be read or some other issue" + Stop-Function -Message "No backups passed through. `n This could mean the SQL instance cannot see the referenced files, the file's headers could not be read or some other issue" return } Write-Message -message "Processing DatabaseName - $DatabaseName" -Level Verbose @@ -859,6 +865,7 @@ function Restore-DbaDatabase { ContinuePoints = $ContinuePoints LastRestoreType = $LastRestoreType DatabaseName = $DatabaseName + EnableException = $EnableException } $FilteredBackupHistory = $BackupHistory | Select-DbaBackupInformation @parms } diff --git a/tests/Restore-DbaDatabase.Tests.ps1 b/tests/Restore-DbaDatabase.Tests.ps1 index 35dd31ae76fb..a9f705400f9e 100644 --- a/tests/Restore-DbaDatabase.Tests.ps1 +++ b/tests/Restore-DbaDatabase.Tests.ps1 @@ -172,6 +172,76 @@ Describe $CommandName -Tag IntegrationTests { } + Context "Honors EnableException when no full backup anchors the chain #10621" { + BeforeAll { + $PSDefaultParameterValues["*-Dba*:EnableException"] = $true + + # Build a full/diff/log chain with one file per backup, so the diff and log never share a + # file with the full (the default backup file name has minute resolution and would hide + # the missing full by accident). + $chainDbName = "dbatoolsci_chaintest_$(Get-Random)" + $null = New-DbaDatabase -SqlInstance $TestConfig.InstanceSingle -Name $chainDbName + $splatChainBackup = @{ + SqlInstance = $TestConfig.InstanceSingle + Database = $chainDbName + Path = $backupPath + } + $null = Backup-DbaDatabase @splatChainBackup -Type Full -FilePath "chaintest_full.bak" + $chainDiff = Backup-DbaDatabase @splatChainBackup -Type Diff -FilePath "chaintest_diff.bak" + $chainLog = Backup-DbaDatabase @splatChainBackup -Type Log -FilePath "chaintest_log.trn" + + # An existing directory without any backup files, to hit the "No backups passed through" path. + $emptyBackupDir = "$backupPath\emptydir" + $null = New-Item -Path $emptyBackupDir -ItemType Directory + + $PSDefaultParameterValues.Remove("*-Dba*:EnableException") + } + + AfterAll { + $PSDefaultParameterValues["*-Dba*:EnableException"] = $true + + $null = Get-DbaDatabase -SqlInstance $TestConfig.InstanceSingle -Database $chainDbName | Remove-DbaDatabase + + $PSDefaultParameterValues.Remove("*-Dba*:EnableException") + } + + It "Throws when only diff and log are passed with EnableException" { + $splatRestore = @{ + SqlInstance = $TestConfig.InstanceSingle + Path = $chainDiff.BackupPath, $chainLog.BackupPath + DatabaseName = $chainDbName + WithReplace = $true + EnableException = $true + } + { Restore-DbaDatabase @splatRestore } | Should -Throw "*Fullname property not found*" + } + + It "Still warns and returns nothing without EnableException" { + $splatRestore = @{ + SqlInstance = $TestConfig.InstanceSingle + Path = $chainDiff.BackupPath, $chainLog.BackupPath + DatabaseName = $chainDbName + WithReplace = $true + WarningAction = "SilentlyContinue" + } + $results = Restore-DbaDatabase @splatRestore + $WarnVar | Should -BeLike "*Fullname property not found*" + $results | Should -BeNullOrEmpty + } + + It "Throws when the path holds no backups at all with EnableException" { + $splatRestore = @{ + SqlInstance = $TestConfig.InstanceSingle + Path = $emptyBackupDir + DatabaseName = $chainDbName + WithReplace = $true + EnableException = $true + } + { Restore-DbaDatabase @splatRestore } | Should -Throw "*No backups passed through*" + } + } + + Context "Database is restored with correct renamings" { BeforeAll { $null = Get-DbaDatabase -SqlInstance $TestConfig.InstanceSingle -ExcludeSystem -EnableException | Remove-DbaDatabase -EnableException