Резервное копирование Windows 2008 R2/2012 при помощи скрипта на PowerShell.

Давно уже написал скрипт для резервного копирования Windows 2008 R2/2012 штатными средствами, но руки все никак не доходили написать статью в бложек, исправляюсь. Скрипт, да и сама статья несколько вторичны. Основные идеи, которые легли в его/ее основу, были заимствованы из статей Вадимса, которые он написал еще аж  в 2009 году. Поэтому сильно рекомендую сначала перейти по ссылке и прочитать эти статьи, а потом возвращаться сюда. Зачем потребовалось вообще что-то писать, если Вадимс уже все написал? Ну, хотя бы потому, что шаблон-скрипт, который он опубликовал, содержит ошибки и немного не работает Подмигивающая рожица. Ну, и кроме того, некоторая информация, которую он опубликовал также содержит ошибки, IMHO. Поэтому,  очень рекомендую прочитать еще и вот эту статью: Backup Version and Space Management in Windows Server Backup.

И так, краткая выжимка из теоритических знаний, которая нам потребуется:

Выполнять резервное копирование можно на сетевой ресурс или на отдельный том. Копирование на сетевой ресурс имеет пару существенных недостатков: во-первых, при возникновении проблем с сетью (во время выполнения архивирования) ваша резервная копия, очевидно, не будет создана, а, во-вторых, сетевая папка может содержать только одну единственную резервную копию (выполняя резервную копию в ту же сетевую папку, что и раньше, вы уничтожаете предыдущую резервную копию).

Копирование на отдельный том выполняется следующим образом: На целевом томе создается папка WindowsImageBackup\<ComputerName>\. В этой папке в свою очередь будут созданы виртуальные диски (по одному на каждый из бэкапируемых томов), на который и будет производится резервное копирование. По окончании копирования состояние виртуальных дисков, хранящих резервные копии, будет сохранено при помощи службы теневого копирования. При следующем архивировании будут произведены те же самые действия в результате получится, что каждый конкретный архив будет доступен при обращении к конкретной теневой копии. Причем с точки зрения программы архивирования каждый такой архив будет являться полным архивом, а  с точки зрения используемого пространства – инкрементальным (теневая копия хранит информацию только об измененных блоках данных).

Информация о выполненном процессе архивирования хранится в нескольких местах ОС, которые, может так случиться, могут содержать рассогласованную информацию. Понятно, что реальное количество резервных копий, которое хранится локально на компьютере, не может быть больше, чем количество теневых копий тома, на который выполнялось резервное копирование. Посмотреть информацию о количестве теневых копий можно, например, в командной строке при помощи команды diskshadow (и ее подкоманд list shadows all). Однако, теневые копии не содержат достаточное количество информации, необходимой для создания перечня резервных копий, поэтому эта информация берется из других мест. Так, например, ОС ведет учет архивных копий в глобальном каталоге архивов, а так же в журнале событий Windows Server Backup Log. Информация из этих источников отображается оснастке “Система архивации данных Windows Server”. В результате может сложиться такая ситуация, что оснастка будет демонстрировать нам противоречивую информацию, которая не имеет ничего общего с реальностью.

 

ArchCons

Посмотрите на скриншот. Он был сделан в системе, для которой на локальном диске хранилось всего два архива (существовало только две теневые копии) и не было создано  ни одного сетевого архива. Однако, оснастка сообщает нам в разделе “Все архивы” о том, что у нас якобы имеется 6 архивов, а в окне сообщений мы видим отчет о создании только 3х архивов. Для того, чтобы заставить ОС отображать непротиворечивую информацию, соответствующую действительности,  нам придется проинициализировать все компоненты системы архивации, которые хранят информацию о резервных копиях или сами резервные копии. Для этого нам нужно будет очистить windows backup log, удалить глобальный каталог архивов (при помощи команды wbadmin delete catalog) и удалить все теневые копии (при помощи команды diskshadow delete shadows all). По большому счету информация, хранящаяся в windows backup log, носит чисто информационный характер и никак не влияет на процесс восстановления информации из архива, если таковой потребуется выполнить, чего не скажешь об информации, хранящейся в глобальном каталоге. Если глобальный каталог поврежден, то восстановить информацию при помощи штатных средств архивирования ОС windows у нас не получится. Однако, поврежденный или удаленный глобальный каталог архивов можно восстановить из резервной копии, которая создается при каждом архивировании в папке WindowsImageBackup\<ComputerName>\. Для восстановления поврежденного глобального каталога архивов необходимо его сначала удалить (при помощи команды wbadmin delete catalog), а затем восстановить из резервной копии (при помощи команды wbadmin restore catalog).

Ну, а теперь, собственно, опубликую скрипт для резервного копирования:

    Write-Verbose "Начали..."
    #Сохраняем значение переменной окружения $VerbosePreference 
    $tmpVerbpref=$VerbosePreference
    $VerbosePreference="Continue"
    #Путь к сетевой папке, в которую будем копировать архив
    $NetworkBackupPath="\\SRV66\Backup$\SRV02\BMR"
    #Имя раздела, на котором будем создавать архив
    $VolumeTarget="D:"
    # Количество копий бэкапа, которые необходимо сохранить на локальном носителе
    $BackupQuantity=3
    # Количество копий бэкапа, которые необходимо сохранить на сетевом носителе
    $NetBackupQuantity=5    
    #Путь к файлу-списку бэкапов
    $csvFile="D:\Backup\ProfileBackup.csv"
    #Путь к папке, в котой будем создавать архив 7zip
    $Path2Arc="D:\Backup"
    # подключаем оснастку Server Backup
    Add-PSSnapin Windows.Serverbackup -ErrorAction SilentlyContinue
    # создаём задание бэкапа
    $policy = New-WBPolicy
    <#
    # создаём и добавляем в задание бэкапа о бэкапируемых файлах
    $source = New-WBFileSpec -FileSpec "C:\Users"
    Add-WBFileSpec -Policy $policy -FileSpec $source
    #>
    #
    #Get list of critical volumes
    $VolSources = Get-WBVolume -CriticalVolumes
    #Add volumes to be backed up
    Add-WBVolume -Policy $policy -Volume $VolSources
    #Define VSS Backup Otions
    Set-WBVssBackupOptions -policy $policy -VssCopyBackup
    #Enable SystemState backup
    Add-WBSystemState -policy $policy
    #Enable Bare Metal Recovery
    Add-WBBareMetalRecovery -Policy $policy
    #
    # указываем локальный том, на который будет копироваться архив
    $target = New-WBBackupTarget -VolumePath $VolumeTarget
    Add-WBBackupTarget -Policy $policy -Target $target
    Write-Verbose "Начинаем процесс создания backup'а"
    # выполняем бэкап
    Start-WBBackup -Policy $policy
    # проверяем код возврата с результатом выполнения бэкапа
    if ((Get-WBSummary).LastBackupResultHR -eq 0) {
        # переименовываем архив в более понятное имя
        $newname = "_Backup_$(Get-Date -f yyyyMMddHHmm)"
        Write-Verbose "Переименовываем папку с только что созданным архивом в $newname ..."
        Ren $VolumeTarget\WindowsImageBackup -NewName $newname
        # Запаковываем архив при помощи 7zip
        $arc="C:\Program Files\7-Zip\7z.exe"
        $arc_params="a -t7z -m0=LZMA2 -mmt -mx9"
        $arc_source="$VolumeTarget\$newname"
        $arc_dest="$Path2Arc\$newname.7z"
        Write-Verbose "Запаковываем папку $newname при помощи 7zip в $newname.7z"
        Start-Process $arc -ArgumentList "$arc_params $arc_dest $arc_source" -Wait
        # копируем архив в сетевую папку
        #copy $VolumeTarget\$newname $NetworkBackupPath -Recurse
        Write-Verbose "Копируем файл $arc_dest в сетевую папку..."
        copy "$arc_dest" $NetworkBackupPath
        if ($?) {
            #если копирование прошло без ошибок, удаляем файл-архива и папку, которая была запакована в этот архив
            del "$arc_dest" -Force -Verbose
            del $VolumeTarget\$newname -Recurse -Force #-Verbose
        }
        # удаляем старые архивы из сетевой папки, за исключением последних $BackupQuantity архивов
        $NetBackups=dir $NetworkBackupPath | ?{$_.Name -match "_.+(\d)+\.7z$"}
        $NetBackupsCount=$NetBackups.count
        if (($NetBackupsCount - $NetBackupQuantity) -gt 0) {
            $NetBackups | sort lastwritetime | select -First ($NetBackupsCount - $NetBackupQuantity) | del -Force -Verbose #-Recurse -WhatIf
        }
        # читаем наш собственный каталог бэкапов
        $csv=@()
        if (Test-Path $csvFile) {$csv = @(Import-Csv $csvFile)}
        # считываем данные о последнем бэкапе
        $current = Get-WBBackupSet | select -Last 1  | select VersionID, SnapshotId
        # и добавляем его в массив объектов действующих бэкапов
        $csv += $current
        # чтобы не было путаницы, снова сортируем объекты и пишем обратно в CSV файл
        $csv | sort @{Expression={[datetime]($_.VersionId)}}| select -Last $BackupQuantity | Export-Csv $csvFile -NoTypeInformation
        # и считаем сколько там записей
        $count = $csv.count
        # если записей больше BackupQuantity, то считаем сколько лишних архивов нужно удалить.
        # если меньше BackupQuantity записей, то ничего удалять не надо и просто добавляем новую запись
        if ($count -gt $BackupQuantity) {
            $old = $count - $BackupQuantity
            # генерируем случайное имя для скрипта, который будет использоваться в diskshadow
            $file = [System.IO.Path]::GetRandomFileName()
            # выбираем все лишние архивы и пропускаем их по конвейеру на удаление
            $csv | sort @{Expression={[datetime]($_.VersionId)}}| select -First $old | %{
                #Read-Host 'Press Enter to continue...' | Out-Null
                #Write-Verbose $file
                ##Read-Host 'Press Enter to continue...' | Out-Null
                # записываем команду во временный файл
                "delete shadows ID {$($_.SnapshotID)}"|Out-File -FilePath $Env:TEMP\$file -Encoding OEM
                #gc $Env:TEMP\$file
                #Read-Host 'Press Enter to continue...' | Out-Null
                # и запускаем diskshadow в режиме скрипта
                diskshadow /s $Env:TEMP\$file | Out-Default
            }
            del $Env:TEMP\$file
        }
    } else {
        # ругаемся, что бэкап не был завершён успешно
        Write-Verbose "Ошибка выполнения бэкапа"
    }
    Write-Verbose "Скрипт закончил работу"
    #Восстанавливаем значение переменной окружения $VerbosePreference 
    $VerbosePreference=$tmpVerbpref

3 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

Notify me of followup comments via e-mail. You can also subscribe without commenting.