internal/Test-DbaLsnChain.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
function Test-DbaLsnChain
{
<#
.SYNOPSIS
Checks that a filtered array from Get-FilteredRestore contains a restorabel chain of LSNs
 
.DESCRIPTION
Finds the anchoring Full backup (or multiple if it's a striped set).
Then filters to ensure that all the backups are from that anchor point (LastLSN) and that they're all on the same RecoveryForkID
Then checks that we have either enough Diffs and T-log backups to get to where we want to go. And checks that there is no break between
LastLSN and FirstLSN in sequential files
  
.PARAMETER FilteredRestoreFiles
This is just an object consisting of the output from Read-DbaBackupHeader. Normally this will have been filtered down to a restorable chain
before arriving here. (ie; only 1 anchoring Full backup)
  
.NOTES
Author: Stuart Moore (@napalmgram), stuart-moore.com
Tags:
dbatools PowerShell module (https://dbatools.io, clemaire@gmail.com)
Copyright (C) 2016 Chrissy LeMaire
License: GNU GPL v3 https://opensource.org/licenses/GPL-3.0
 
.EXAMPLE
Test-DbaLsnChain -FilteredRestoreFiles $FilteredFiles
 
Checks that the Restore chain in $FilteredFiles is complete and can be fully restored
 
#>

    [CmdletBinding()]
    Param (
        [parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [object[]]$FilteredRestoreFiles,
        [switch]$Continue,
        [switch]$EnableException
    )


    Begin{
        #Need to anchor with full backup:
        $FunctionName =(Get-PSCallstack)[0].Command
        $TestHistory = @()
    }
    Process {
        ForEach ($bh in $FilteredRestoreFiles){
            $TestHistory += $bh
        }
    }
    end {
        if ($continue)
        {
            return $true
        }
        Write-Verbose "$FunctionName - Testing LSN Chain"
        if ($null -eq $TestHistory[0].BackupTypeDescription){
            $TypeName = 'Type'
        }
        else{
            $TypeName = "BackupTypeDescription"
        } 
        write-Verbose "TypeName = $typename "
        
        $FullDBAnchor = $TestHistory | Where-Object {$_.$TypeName -in ('Database','Full') }

        if (($FullDBAnchor | Group-Object -Property FirstLSN | Measure-Object).count -ne 1)
        {
            $cnt = ($FullDBAnchor | Group-Object -Property FirstLSN | Measure-Object).count
            Foreach ($tFile in $FullDBAnchor){write-verbose "$($tfile.FirstLsn) - $($tfile.TypeName)"}
            Write-Verbose "$FunctionName - db count = $cnt"
            Write-Warning "$FunctionName - More than 1 full backup from a different LSN, or less than 1, neither supported"

            return $false
            break;
        }

        #Via LSN chain:
        [BigInt]$CheckPointLSN = ($FullDBAnchor | Select-Object -First 1).CheckPointLSN.ToString()
        [BigInt]$FullDBLastLSN = ($FullDBAnchor | Select-Object -First 1).LastLSN.ToString()
        $BackupWrongLSN = $FilteredRestoreFiles | Where-Object {$_.DatabaseBackupLSN -ne $CheckPointLSN}
        #Should be 0 in there, if not, lets check that they're from during the full backup
        if ($BackupWrongLSN.count -gt 0 ) 
        {
            if (($BackupWrongLSN | Where-Object {[BigInt]$_.LastLSN.ToString() -lt $FullDBLastLSN}).count -gt 0)
            {
                Write-Warning "$FunctionName - We have non matching LSNs - not supported"
                return $false
                break;
            }
        }
        $DiffAnchor = $TestHistory | Where-Object {$_.$TypeName -in ('Database Differential','Differential')}
        #Check for no more than a single Differential backup
        if (($DiffAnchor.FirstLSN | Select-Object -unique | Measure-Object).count -gt 1)
        {
            Write-Warning "$FunctionName - More than 1 differential backup, not supported"
            return $false
            break;        
        } 
        elseif (($DiffAnchor | Measure-Object).count -eq 1)
        {
            Write-Message -Message "Found a diff file, setting Log Anchor" -Level Verbose
            $TlogAnchor = $DiffAnchor
        } 
        else 
        {
            $TlogAnchor = $FullDBAnchor
        }


        #Check T-log LSNs form a chain.
        $TranLogBackups = $TestHistory | Where-Object {$_.$TypeName -in ('Transaction Log','Log') -and $_.DatabaseBackupLSN -eq $FullDBAnchor.CheckPointLSN} | Sort-Object -Property LastLSN, FirstLsn
        for ($i=0; $i -lt ($TranLogBackups.count))
        {
            Write-Verbose "looping t logs"
            if ($i -eq 0)
            {
                if ($TranLogBackups[$i].FirstLSN -gt $TlogAnchor.LastLSN)
                {
                    Write-Warning "$FunctionName - Break in LSN Chain between $($TlogAnchor.FullName) and $($TranLogBackups[($i)].FullName) "
                    Write-Verbose "Anchor $($TlogAnchor.LastLSN) - FirstLSN $($TranLogBackups[$i].FirstLSN)"
                    return $false
                    break
                }
            }else {
                if ($TranLogBackups[($i-1)].LastLsn -ne $TranLogBackups[($i)].FirstLSN -and ($TranLogBackups[($i)] -ne $TranLogBackups[($i-1)]))
                {
                    Write-Warning "$FunctionName - Break in transaction log between $($TranLogBackups[($i-1)].FullName) and $($TranLogBackups[($i)].FullName) "
                    return $false
                    break
                }
            }
            $i++

        }  
        Write-Verbose "$FunctionName - Passed LSN Chain checks" 
        return $true

    }
}