0

背景:任何了解 SQL 备份的人都知道,成功的还原依赖于从最近的完整备份到最后一个事务文件的不间断的备份文件链。如果链中有任何中断,备份将失败。我正在使用 DPM 2019 管理多个 SQL 数据库的备份。我还需要每晚创建一个数据库的“异地”副本。

我正在运行一个让 SQL 服务器每晚备份到文件夹/文件的 PS 脚本。最近我需要从备份中恢复,发现这个每晚的“额外”备份破坏了文件的备份链,让我只能从这个每晚的文件中恢复,而没有任何隔夜的事务文件。简而言之,我们因此而丢失了数据。我需要做的是从 DPM 服务器获取最新的备份并将其用作我的夜间异地副本。我有一个 PS 脚本,可以从有效的 DPM 服务器获取最新的完整备份。然而,这个脚本(通过几个谷歌搜索找到,我没有写),将选定的备份恢复到“任何”SQL 服务器作为操作数据库。

不是我需要的;我需要它来恢复到可以移动/传输到场外的网络文件。我知道它可以在 DPM 服务器 UI 中完成;这是还原过程中的一个选项。我不知道如何通过 PS 脚本来做到这一点。

    #Get DataSource With Name $SQLDatabaseName Running On Instance $SourceServerName & Store As A Var
    $DataSourceSQLObj = Get-DataSource -ProtectionGroup $ProtectionGroupSQLObj | Where-Object { $_.Name -eq $SQLDatabaseName -and $_.Instance -eq $SourceServerName} 
    If ($DataSourceSQLObj -ne $null) #Only Continue If DB Exists
        { 
        #Add $DataSource As A SQLDataSource And Store As A Var
        $SQLDatabases = [Microsoft.Internal.EnterpriseStorage.Dls.UI.ObjectModel.SQL.SQLDataSource]$DataSourceSQLObj;
        #Get The Latest Disk Based (Express Full) Backup - Fails With Incremental Sync.
        $RecoveryPointsSQLObj = Get-Recoverypoint -DataSource $SQLDatabases | Where-Object { $_.HasFastRecoveryMarker -eq "Fast" -and $_.IsRecoverable -and $_.Location -eq "Disk"}  | Sort-Object BackupTime -Desc;
        
        If ($RecoveryPointsSQLObj.Count) #Check More Than 1 RP Is Returned
        { 
            $RecoveryPointToRestore = $RecoveryPointsSQLObj[0]; #Get The Latest RP (1st In List)
        } 
        Else #If Only 1 RP Is Returned
        { 
            $RecoveryPointToRestore = $RecoveryPointsSQLObj; 
        } 
        
        If ($RecoveryPointToRestore -eq $null) #If No RP's Are Returned...
        { 
            Write-EventLog -LogName $LoggingLogName -Source $LoggingSource -EventID $LoggingEventID -Message "Restore Failed. RP For DB: $SQLDatabaseName On: $SourceServerName Not Found In PG $ProtectionGroupSQLStr"
            Return 
        } 

此代码按预期工作,它从目标服务器获取最新的完整备份。我迷路的地方是如何将恢复位置/类型设置为网络文件夹。脚本已恢复到任何命名的 SQL 服务器的代码,带有数据库重命名(如果已配置)。

         $length = $RecoveryPointToRestore.PhysicalPath.Length; #Return Number Of Files (i.e. LDF And MDF Files) - 2 = 1x LDF and 1x MDF 
        
        #Setup The Alternative DB Object Ready For Restore - Create The Objects & Add As Many FileLocationMapping Placeholders As There Are Files To $AlternativeDatabaseObj
        $AlternativeDatabaseObj = New-Object -TypeName Microsoft.Internal.EnterpriseStorage.Dls.UI.ObjectModel.SQL.AlternateDatabaseDetailsType; 
        $LocationMapping = New-Object Microsoft.Internal.EnterpriseStorage.Dls.UI.ObjectModel.SQL.FileLocationMapping[] $length; 
        $AlternativeDatabaseObj.LocationMapping = $LocationMapping 
        
        $i = 0; #Resets The Count (See While Loop Below)
        While($i -lt $length) #Perform The While Loop While $i Is Less Than The Number Of Files To Restore ($length). Add The Crrent File Names And Locations For Each File To Be Restored 
        {        
            $AlternativeDatabaseObj.LocationMapping[$i] = New-Object -TypeName Microsoft.Internal.EnterpriseStorage.Dls.UI.ObjectModel.SQL.FileLocationMapping; #Create The Object
            $AlternativeDatabaseObj.LocationMapping[$i].FileName = $RecoveryPointToRestore.FileSpecifications[$i].FileSpecification; #Set File Name For Files
            $AlternativeDatabaseObj.LocationMapping[$i].SourceLocation = [Microsoft.Internal.EnterpriseStorage.Dls.UI.ObjectModel.OMCommon.PathHelper]::GetParentDirectory($RecoveryPointToRestore.PhysicalPath[$i]); #Set Source Location (Path) For Files
            If ($AlternativeDatabaseObj.LocationMapping[$i].FileName.ToLower().EndsWith(".ldf")) #If LDF File Set Destination Location As $DestinationLDFPath
            { 
                $AlternativeDatabaseObj.LocationMapping[$i].DestinationLocation = $DestinationLDFPath 
            } 
            Else #If MDF File Set Destination Location As $DestinationMDFPath
            { 
                $AlternativeDatabaseObj.LocationMapping[$i].DestinationLocation = $DestinationMDFPath 
            }        
            $i++; #Increment Counter (Move Onto Next File)
        } 
        $AlternativeDatabaseObj.InstanceName = $DestinationServerName;  #Set Destination Server Name. If Restoring To Named Instance Include The Instance Name 
        $AlternativeDatabaseObj.DatabaseName = $DestinationDatabaseName; #Set Destination DB Name
        
        #Create A Recovery Option Variable Targetted To The Destination Server, Set To Rename The DB And Use The $AlternativeDatabaseObj Details Created Earlier
        $ROP = New-RecoveryOption -TargetServer $DestinationServerName -RecoveryLocation OriginalServerWithDBRename -SQL -RecoveryType Recover -AlternateDatabaseDetails $AlternativeDatabaseObj; 
            
        #Load SQL SMO Class - Required To Check If DB Exists On $DestinationServerName
        [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO') | Out-Null 
        #Create A New Object (SMO) Pass It The $DestinationServerName Variable And Store As A Var
        $SQLServerManagement = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server ($DestinationServerName)
        #If DB Exists At Destination, Write To Event Log
        If ($SQLServerManagement.databases[$DestinationDatabaseName] -ne $null)
        {
            Write-EventLog -LogName $LoggingLogName -Source $LoggingSource -EventID $LoggingEventID -Message "Error: DB $DestinationDatabaseName Already Exists On $DestinationServerName - Restore Will Fail"
        }
        If ($PerformRestore) #Only Run Restore If $PerformRestore Is $true
        {
            write-host "Recovering database"
            $RestoreJob = Recover-RecoverableItem -RecoverableItem $RecoveryPointToRestore -RecoveryOption $ROP; #Start The Restore Operation Using The $ROP Recovery Option Var
            #The While Loop Below Effectively Pauses The Script Until The Succeeded Or Failed If Clauses Are Encountered. 
            $Wait = 2; #Initial Wait Time
            While ($RestoreJob -ne $null -and $RestoreJob.HasCompleted -eq $false) 
            { 
                Start-Sleep -Seconds $Wait; 
                $Wait = 20; 
            } 
            
            If($RestoreJob.Status -ne "Succeeded") #If Job Fails Write Appropriately To Event Log
            { 
                Write-EventLog -LogName $LoggingLogName -Source $LoggingSource -EventID $LoggingEventID -Message "Restore Status: $($RestoreJob.Status)`n Start: $($RestoreJob.StartTime)`n  End: $($RestoreJob.EndTime)"
            } 
            Else #If Job Completes Write To Event Log
            { 
                Write-EventLog -LogName $LoggingLogName -Source $LoggingSource -EventID $LoggingEventID -Message "Restore Status: $($RestoreJob.Status)`n Start: $($RestoreJob.StartTime)`n  End: $($RestoreJob.EndTime)"
            } 
            
            $td = (New-Timespan -Start $RestoreJob.StartTime -end $RestoreJob.EndTime) #Calculate Time Taken To Restore & Write To Event Log
            Write-EventLog -LogName $LoggingLogName -Source $LoggingSource -EventID $LoggingEventID -Message "Elapsed time: Hours: $($td.Hours) Minutes:$($td.Minutes) Seconds:$($td.Seconds) MSecs:$($td.Milliseconds)"
        } 

有人可以指出我正确的方向吗?我试过用谷歌搜索很多不同的东西,我不确定在哪里寻找/阅读更多信息。我还应该声明 PS 不是我最擅长的语言。

4

0 回答 0