Задача раз 2 недели восстанавливать базы данных на dev сервер. Бэкап сделаны с помощью ola.hallengren
-- В ночь с субботы на воскресение
--if(datepart(weekday, getdate()) = 1)
-- Для миграции на АГ сметили создание резервных копий для того что бы использовать верфицированые фулл бэкапы в процессе работ
-- В ночь с Пятницу на субботу (В Субботу в 01.00 создать фулл бекап)
if(datepart(weekday, getdate()) = 7)
begin
EXECUTE [dbo].[DatabaseBackup]
@Databases = 'USER_DATABASES,-%_restore',
@Directory = N'\\ARC03.adminbd.ru\sql_backup$',
@BackupType = 'FULL',
@Verify = 'Y',
@Compress = 'Y',
@CleanupTime = 312,
@CleanupMode = 'AFTER_BACKUP',
@CheckSum = 'Y',
@LogToTable = 'Y',
@DatabasesInParallel= 'Y';
end
Скрипт восстановления
# ======================================================================
# Автоматическое восстановление баз данных с уведомлениями по email
# ======================================================================
# Требуется: модуль dbatools, доступ к бэкапам и целевому SQL Server
# ======================================================================
# Подключаем модуль dbatools
Import-Module dbatools -ErrorAction Stop
# === Параметры (настраиваемые) ===
$SourceBackupPath = "\\arc03.adminbd.ru\backup$"
$TargetServer = "dev4"
$DataPath = "F:\data"
$LogPath = "E:\log"
$DatabaseList = @(
"bd1",
"bd2"
)
# Email-настройки
$From = "dev4@tech.adminbd.ru"
$To = "moskvichev@adminbd.ru"
$SmtpServer = "tech.adminbd.ru"
$SmtpPort = 25
# Лог-файл
$LogFilePath = "C:\temp\RestoreLog_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
# Создаём папку для логов, если не существует
if (-not (Test-Path "C:\temp")) { New-Item -ItemType Directory -Path "C:\temp" -Force | Out-Null }
# === Вспомогательные функции ===
function Write-Log {
param([string]$Message)
$Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$LogEntry = "[$Timestamp] $Message"
Add-Content -Path $LogFilePath -Value $LogEntry -Encoding UTF8
Write-Host $LogEntry
}
function Send-Email {
param(
[string]$Subject,
[string]$Body,
[switch]$IsHtml
)
try {
$Params = @{
SmtpServer = $SmtpServer
Port = $SmtpPort
From = $From
To = $To
Subject = $Subject
Body = $Body
Encoding = [System.Text.Encoding]::UTF8
DeliveryNotificationOption = 'Never'
}
if ($IsHtml) { $Params.BodyAsHtml = $true }
Send-MailMessage @Params
Write-Log "Письмо отправлено: $Subject"
}
catch {
Write-Log "Ошибка отправки email: $($_.Exception.Message)"
}
}
# === СТАРТ ===
Write-Log "=== ЗАПУСК восстановления баз данных на $TargetServer ==="
# Отправляем уведомление о начале
$StartSubject = "🚀 Началось восстановление баз данных на $TargetServer"
$StartBody = @"
<p>Восстановление баз данных на сервере <strong>$TargetServer</strong> запущено.</p>
<p><strong>Время начала:</strong> $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")</p>
<p><strong>Список баз:</strong> $($DatabaseList -join ', ')</p>
<p>Подробный лог доступен на сервере: <code>$LogFilePath</code></p>
"@
Send-Email -Subject $StartSubject -Body $StartBody -IsHtml
# Список для результатов
$Results = [System.Collections.Generic.List[object]]::new()
# === Параллельное восстановление ===
$ParallelResults = $DatabaseList | ForEach-Object -Parallel {
$DatabaseName = $_
$SourceBackupPath = $using:SourceBackupPath
$TargetServer = $using:TargetServer
$DataPath = $using:DataPath
$LogPath = $using:LogPath
$LogFilePath = $using:LogFilePath
# Подключаем модуль в потоке
Import-Module dbatools -ErrorAction Stop
# Локальная лог-функция для потока
$Log = {
param($msg)
$ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Add-Content -Path $LogFilePath -Value "[$ts] $msg" -Encoding UTF8
Write-Host "[$ts] $msg"
}.GetNewClosure()
& $Log "→ Начало обработки базы: $DatabaseName"
# Удаление существующей базы
$DbExists = Get-DbaDatabase -SqlInstance $TargetServer -Database $DatabaseName -ErrorAction SilentlyContinue
if ($DbExists) {
try {
& $Log "Удаление существующей базы $DatabaseName..."
$DropResult = Remove-DbaDatabase -SqlInstance $TargetServer -Database $DatabaseName -Confirm:$false -EnableException
if ($DropResult.Status -ne "Dropped") {
throw "Неожиданный статус при удалении: $($DropResult.Status)"
}
& $Log "База $DatabaseName успешно удалена."
}
catch {
& $Log "❌ Ошибка удаления базы $DatabaseName : $($_.Exception.Message)"
return [PSCustomObject]@{
DatabaseName = $DatabaseName
Status = "Ошибка"
Duration = "N/A"
Details = "Ошибка удаления: $($_.Exception.Message)"
}
}
} else {
& $Log "База $DatabaseName не существует — удаление пропущено."
}
# Поиск последнего FULL-бэкапа
$FullBackupDir = Join-Path $SourceBackupPath (Join-Path $DatabaseName "FULL")
$BackupFiles = Get-ChildItem -Path $FullBackupDir -Filter "*.bak" -ErrorAction SilentlyContinue | Sort-Object CreationTime -Descending
if (-not $BackupFiles -or $BackupFiles.Count -eq 0) {
& $Log "❌ Бэкап для $DatabaseName не найден в: $FullBackupDir"
return [PSCustomObject]@{
DatabaseName = $DatabaseName
Status = "Ошибка"
Duration = "N/A"
Details = "Бэкап не найден"
}
}
$LatestBackup = $BackupFiles[0].FullName
& $Log "Используется бэкап: $LatestBackup"
# Генерация FileMapping через скрипт
try {
$ScriptText = Restore-DbaDatabase -SqlInstance $TargetServer -Path $LatestBackup -DatabaseName $DatabaseName -OutputScriptOnly -EnableException
}
catch {
& $Log "❌ Ошибка генерации скрипта восстановления: $($_.Exception.Message)"
return [PSCustomObject]@{
DatabaseName = $DatabaseName
Status = "Ошибка"
Duration = "N/A"
Details = "Ошибка генерации скрипта: $($_.Exception.Message)"
}
}
# Парсинг MOVE-инструкций
$MoveRegex = [regex]::new("MOVE N'([^']+)' TO N'([^']+)'")
$Matches = $MoveRegex.Matches($ScriptText)
if ($Matches.Count -eq 0) {
& $Log "❌ Не найдено ни одной инструкции MOVE в скрипте восстановления."
return [PSCustomObject]@{
DatabaseName = $DatabaseName
Status = "Ошибка"
Duration = "N/A"
Details = "Не удалось определить файлы из бэкапа"
}
}
$NewFileMap = @{}
$UsedPaths = @{}
foreach ($Match in $Matches) {
$LogicalName = $Match.Groups[1].Value
$OriginalPath = $Match.Groups[2].Value
$Extension = [IO.Path]::GetExtension($OriginalPath)
$BaseName = "${DatabaseName}_${LogicalName}"
# Формируем путь
$TargetDir = if ($Extension -eq '.ldf') { $LogPath } else { $DataPath }
$NewPath = Join-Path $TargetDir "$BaseName$Extension"
# Разрешаем конфликты имён
$Counter = 1
$FinalPath = $NewPath
while ($UsedPaths.ContainsKey($FinalPath)) {
$FinalPath = Join-Path $TargetDir "${BaseName}_${Counter}${Extension}"
$Counter++
}
$UsedPaths[$FinalPath] = $true
$NewFileMap[$LogicalName] = $FinalPath
}
& $Log "Создана FileMapping для $DatabaseName (файлов: $($NewFileMap.Count))"
# Восстановление
$StartTime = Get-Date
try {
& $Log "▶ Запуск восстановления $DatabaseName..."
$null = Restore-DbaDatabase -SqlInstance $TargetServer -Path $LatestBackup -DatabaseName $DatabaseName -FileMapping $NewFileMap -WithReplace -EnableException
$EndTime = Get-Date
$Duration = $EndTime - $StartTime
$DurationStr = "{0:hh\:mm\:ss}" -f $Duration
# Проверка появления базы
$DbCheck = Get-DbaDatabase -SqlInstance $TargetServer -Database $DatabaseName -ErrorAction SilentlyContinue
if ($DbCheck) {
& $Log "✅ База $DatabaseName успешно восстановлена за $DurationStr"
return [PSCustomObject]@{
DatabaseName = $DatabaseName
Status = "Успешно"
Duration = $DurationStr
Details = "Восстановлено за $DurationStr"
}
} else {
throw "База не обнаружена после восстановления"
}
}
catch {
$EndTime = Get-Date
$Duration = $EndTime - $StartTime
$DurationStr = "{0:hh\:mm\:ss}" -f $Duration
& $Log "❌ Ошибка восстановления $DatabaseName : $($_.Exception.Message)"
return [PSCustomObject]@{
DatabaseName = $DatabaseName
Status = "Ошибка"
Duration = $DurationStr
Details = "Ошибка: $($_.Exception.Message)"
}
}
} -ThrottleLimit 3
# Добавляем результаты
foreach ($res in $ParallelResults) {
$Results.Add($res)
}
Write-Log "=== ВСЕ БАЗЫ ОБРАБОТАНЫ ==="
# === Формирование и отправка итогового отчёта ===
$HtmlRows = foreach ($r in $Results) {
$BgColor = if ($r.Status -eq "Успешно") { "#d4edda" } else { "#f8d7da" }
$Color = if ($r.Status -eq "Успешно") { "#155724" } else { "#721c24" }
"<tr style='background-color: $BgColor; color: $Color;'>
<td><strong>$($r.DatabaseName)</strong></td>
<td>$($r.Status)</td>
<td>$($r.Duration)</td>
<td>$($r.Details)</td>
</tr>"
}
$TotalCount = $Results.Count
$SuccessCount = ($Results | Where-Object Status -eq "Успешно").Count
$FailCount = $TotalCount - $SuccessCount
$Summary = if ($FailCount -eq 0) {
"✅ Все базы восстановлены успешно."
} else {
"⚠️ Успешно: $SuccessCount из $TotalCount. Ошибок: $FailCount."
}
$CompletionTime = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$HtmlReport = @"
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Отчёт: восстановление БД на $TargetServer</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
table { border-collapse: collapse; width: 100%; margin-top: 10px; }
th, td { border: 1px solid #ccc; padding: 10px; text-align: left; }
th { background-color: #f1f1f1; }
.summary { font-size: 1.1em; font-weight: bold; margin-bottom: 15px; color: #333; }
</style>
</head>
<body>
<h2>✅ Восстановление баз данных на $TargetServer завершено</h2>
<p><strong>Время завершения:</strong> $CompletionTime</p>
<div class="summary">$Summary</div>
<table>
<thead>
<tr>
<th>База данных</th>
<th>Статус</th>
<th>Длительность</th>
<th>Детали</th>
</tr>
</thead>
<tbody>
$($HtmlRows -join "")
</tbody>
</table>
<p><em>С уважением,<br>Скрипт автоматического восстановления</em></p>
</body>
</html>
"@
$EndSubject = if ($FailCount -eq 0) {
"✅ Успешно: восстановление БД на $TargetServer"
} else {
"⚠️ Частичный успех: восстановление БД на $TargetServer ($SuccessCount/$TotalCount)"
}
Send-Email -Subject $EndSubject -Body $HtmlReport -IsHtml
# === Вывод в консоль ===
Write-Host "`n" + ("=" * 60) -ForegroundColor Cyan
Write-Host "РЕЗУЛЬТАТЫ ВОССТАНОВЛЕНИЯ" -ForegroundColor Cyan
Write-Host ("=" * 60) -ForegroundColor Cyan
$Results | Format-Table -AutoSize
Write-Host "`n📄 Лог: $LogFilePath" -ForegroundColor Cyan
Write-Host "📧 Отчёты отправлены на: $To" -ForegroundColor Green
Запуск через task scheduler
Cmd
"C:\Program Files\PowerShell\7\pwsh.exe" -NoLogo -ExecutionPolicy Bypass -File "%~dp0restoredb.ps1"
Similar Posts:
- Как вывести список пользователей у которых не при монтировался диск fslogix и переименовать FriendlyName
- Как сделать проверку бэкапов mssql через Powershell 7 и dbatools.
- Как через powershell проверить есть ли файл на файловых шарах или нет
- Как сделать проверку серверов на открытие портов , пинги и трасеровка хостов для технической поддержки.
- Как завершить отключение сессии на серверах RDS и вывести список где пользователи не найдены на хостах но в брокере они есть.


