System Center 2012 R2 DPM - PowerShell скрипт для автоматического восстановления базы данных в именованном экземпляре SQL Server

imageДанная запись не является неким решением, а скорее является результатом моего исследования по данной теме. Далее приведённый скрипт весьма не совершенен и требует доработки под конкретные условия. Однако он вполне может являться отличной отправной точкой для создания собственного решения.

Возникла задача по автоматизации восстановления баз данных (БД) SQL Server на тестовый сервер для нужд разработчиков. Так как мы работаем с разными версиями SQL Server, только одна из них установлена как экземпляр по умолчанию, остальные используют именованные экземпляры (Named Instance).

Восстановление БД из System Center 2012 R2 DPM посредством PowerShell, как оказалось, вообще задача не тривиальная и мало описанная. К примеру, практически нигде в документации (или, по крайней мере, я не нашел) не говорится, что операции, которые делаются через GUI консоль DPM, не соответствуют операциям, которые можно проделать через PowerShell. DPM это тот продукт, который выбивается из линейки продуктов “2012 R2”, так как для этих продуктов (Windows Server 2012 R2, SCCM 2012 R2, SCVMM 2012 R2) постоянно декларируется то, что любую операцию, которую можно сделать через GUI, можно сделать и через PowerShell, и даже более того, порой PowerShell дает более расширенные возможности по сравнению с GUI. Как выяснилось, в DPM это совсем не так.

Операция восстановления БД из точки восстановления в DPM через GUI выглядит примерно так:

  1. Выбираем группу защиты
  2. Выбираем точку восстановления нужной БД
  3. Выбираем тип восстановления (в нашем случае это “Recover to any instance of SQL Server”)
  4. Выбираем сервер и экземпляр SQL Server, а так же пути для файлов в точке назначения
  5. Указываем режим восстановленной базы
  6. Указываем специфические опции
  7. Жмем “Recover”

Какой результат получаем в итоге:

  1. Файлы базы копируется на сервер назначения
  2. База монтируется на выбранном экземпляре SQL Server

В случае с PowerShell мы не получаем функционала для пункта 2. Так как в структуре командлета New-RecoveryOption значение обязательного параметра -RecoveryLocation можно указать только OriginalServer или OriginalServerWithDBRename (остальные типы значений не относятся к восстановлению SQL DB). “Порыскав” на различных TechNet форумах, я нашел комментарий человека о том, что в случае восстановления на “any instance of SQL Server” консоль DPM просто копирует БД в указанные пути назначения, а потом через SQL Management дает SQL Server’у указание присоединить БД.

В результате моих изысканий и множества различных мучений родился PowerShell скрипт, за основу которого был взят скрипт из галереи TechNet. Работа скрипта была проверена в связке с SQL Server 2008 R2 и SQL Server 2012.

ВНИМАНИЕ!

Данный скрипт удаляет существующую БД на сервере назначения! Будьте предельно осторожными при тестировании этого скрипта в своей среде.

Param(
[string]$DPMServer ="DPMServer",
[string]$ProtectionGroupName = "Your Protection Group",
[string]$SourceServerName = "SourceServer",
[string]$DestinationServerName = "DestinationServer",
[string]$DestinationInstanceName = "DestinationServer\InstanceName",
[string]$SQLDatabaseName = "OriginalDatabaseName",
[string]$DestinationDatabaseName = "DestinationDatabaseName",
[string]$DestinationPath = "E:\Data\"
)

Import-Module DataProtectionManager
$conn = Connect-DPMServer $DPMServer

if ($conn) {

    #Получаем объект с группой защиты
    $ProtectionGroupSQLObj = Get-ProtectionGroup $DPMServer | Where-Object { $_.FriendlyName -eq $ProtectionGroupName} 

    #Объект источника
    $DataSourceSQLObj = Get-DataSource -ProtectionGroup $ProtectionGroupSQLObj | Where-Object { $_.Name -eq $SQLDatabaseName -and $_.Instance -eq $SourceServerName}

    if ($DataSourceSQLObj -ne $null) {

        #Приведем объект источника к типу SQLDatasource
        $SQLDatabases = [Microsoft.Internal.EnterpriseStorage.Dls.UI.ObjectModel.SQL.SQLDataSource]$DataSourceSQLObj
        
        #Получаем список точек восстановления для базы
        $RecoveryPointsSQLObj = Get-Recoverypoint -DataSource $SQLDatabases | Where-Object { $_.HasFastRecoveryMarker -eq "Fast" -and $_.IsRecoverable -and $_.Location -eq "Disk"}  | Sort-Object BackupTime -Desc

        #Если есть множество точек восстановления
        #(обработку отказа при отсутствии точек оставляю на усмотрение пользователя скрипта)
        If ($RecoveryPointsSQLObj.Count)
        { 
            $RecoveryPointToRestore = $RecoveryPointsSQLObj[0] #Если точек много - берем первую (она же самая свежая)
        } 
        Else
        { 
            $RecoveryPointToRestore = $RecoveryPointsSQLObj #Если точка одна - ее и берем
        }

        $length = $RecoveryPointToRestore.PhysicalPath.Length #Получаем количество файлов (их может быть больше двух, если база разбита на файловые группы)

        #Создаем объект альтернативного месторасположения БД
        $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

        While($i -lt $length) #Заполняем массив данными о файлах
        {        
            $AlternativeDatabaseObj.LocationMapping[$i] = New-Object -TypeName Microsoft.Internal.EnterpriseStorage.Dls.UI.ObjectModel.SQL.FileLocationMapping #Создаем обект
            $AlternativeDatabaseObj.LocationMapping[$i].FileName = $RecoveryPointToRestore.FileSpecifications[$i].FileSpecification #Указываем имя файла
            $AlternativeDatabaseObj.LocationMapping[$i].SourceLocation = [Microsoft.Internal.EnterpriseStorage.Dls.UI.ObjectModel.OMCommon.PathHelper]::GetParentDirectory($RecoveryPointToRestore.PhysicalPath[$i]) #Указываем путь источника
            $AlternativeDatabaseObj.LocationMapping[$i].DestinationLocation = $DestinationPath #Указываем путь назначения (так как скрипт предназначен для тестовых баз, я сложил все файлы в одну папку, если нужно разделять файлы по файловым группам или отделять лог от mdf, то нужно доработать эту часть скрипта)
                    
            $i++
        }

        $AlternativeDatabaseObj.InstanceName = $DestinationInstanceName #Указываем инстанс
        
        $AlternativeDatabaseObj.DatabaseName = $DestinationDatabaseName #Указываем новое имя БД

        #Создаем объект опций восстановления (значение RecoveryLocation может быть OriginalServer, OriginalServerWithDBRename или CopyToFolder)
        $ROP = New-RecoveryOption -TargetServer $DestinationServerName -SQL -RecoveryLocation OriginalServer -RecoveryType Recover -AlternateDatabaseDetails $AlternativeDatabaseObj -RestoreSecurity

        #Перед началом процесса восстановления БД проверяем наличие базы с таким именем на инстансе
        [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO') | Out-Null
        $smo = new-object Microsoft.SqlServer.Management.Smo.Server $DestinationInstanceName

        #Если база есть
        if($smo.Databases[$DestinationDatabaseName] -ne $null){

            #Удаляем коннекты к ней
            $smo.KillAllProcesses($DestinationDatabaseName)
            #Удаляем старую базу данных
            $smo.Databases[$DestinationDatabaseName].Drop()
        }

        #Запускаем процесс восстановления БД
        $RestoreJob = Restore-DPMRecoverableItem -RecoverableItem $RecoveryPointToRestore -RecoveryOption $ROP -Verbose

        $Wait = 2 #начальное время ожидание в секундах
        #Ждем пока задача не завершится
        While ($RestoreJob -ne $null -and $RestoreJob.HasCompleted -eq $false) 
        { 
            Start-Sleep -Seconds $Wait
            $Wait = 20
        }

        #Если статус задачи "Suceeded", то аттачим БД к инстансу
        if ($RestoreJob.Status -eq "Succeeded") {
            
            #Создаем объект коллекции строк
            $sc = new-object System.Collections.Specialized.StringCollection
            #Добавляем в него полные пути к файлам
            foreach ($lm in $AlternativeDatabaseObj.LocationMapping){
                $filename  = [System.IO.Path]::Combine($lm.DestinationLocation,$lm.FileName)
                $sc.Add($filename) | Out-Null
            }
            #Присоединяем БД
            $smo.AttachDatabase($DestinationDatabaseName,$sc)          

        }
    }
}

Всего комментариев: 6 Комментировать

  1. Eugene /

    Александр, спасибо!
    Получается, что скрипт восстанавливает ОДНУ базу SQL из DPM?!

    Вы используете данный скрипт, чтобы восстановить по очереди последовательно несколько баз SQL из DPM сервера?

    1. Александр Никитин / Автор записи

      Да, скрипт для восстановления одной базы из DPM. Через Jobs несложно автоматизировать восстановление нескольких баз одновременно.

      1. Eugene /

        Jobs в SQL или еще есть какие-нибудь :)?

        Есть готовые варианты?

        P.S. я пока автоматизировал через циклы PoSH и командлеты DPM.

        1. Александр Никитин / Автор записи

          Через Start-Job, я имел ввиду. Или через workflows (но там вроде ограничение на количество потоков). Просто Powershell Jobs будут удобнее имхо.

  2. Вячеслав /

    DPM не может восстанавливать несколько баз из одного Data Source.
    В этом случае Recovery выпадет в ошибку:
    Job failure on was caused by other ongoing conflicting operations on that computer. Note that some applications do not allow parallel recovery and backup operations on the same data source.

    Так что Start-Job бесполезен, все равно базы придется восстанавливать последовательно.

    1. Евгений Лейтан /

      Вячеслав, Вы правы.
      Сделал через скрипт PoSH в цикле.

Добавить комментарий