Рабоче-крестьянское резервное копирование под Windows

в 8:19, , рубрики: backup, powershell, shadow copy, windows, резервное копирование, Серверное администрирование

Рабоче-крестьянское резервное копирование под Windows - 1

Постановка задачи

Допустим, вы системный администратор в малой/средней (да чего уж там, иногда даже большой) компании, перед которым стоит задача организации резервного копирования файлового сервера исключительно с помощью подручных средств. Или вы продвинутый пользователь, которому небезразлична судьба хранящихся на вашем жестком диске файлов.

Пусть задача будет звучать следующим образом:

  1. Необходимо организовать автоматическое резервное копирование определенных файлов компьютера на отдельный носитель.
  2. Компьютер работает под управлением Windows версий 7 / 2008 или более поздней.
  3. Объем данных большой, поэтому копирование должно поддерживаться как полное, так и дифференциальное.
  4. Д.б. возможность копировать любые файлы, в т.ч. системные, заблокированные на чтение и т. п.
  5. Сторонним платным софтом пользоваться категорически не хочется (ну, допустим, мы стеснены в средствах, а эти ваши торренты — не наш путь! Или религия не позволяет. Или миллион других причин.), а лучше вообще обойтись без любого стороннего софта, пользуясь лишь возможностями ОС .

Немного подумав, еще расширим список хотелок:

  1. Как продолжение предыдущего пункта, формат архива также должен быть открытым и распространенным, чтобы в случае чего его без проблем открыть откуда угодно с помощью чего-угодно
  2. Более того, он должен быть таким, чтобы из любого, даже дифференциального архива, можно было бы без труда вытащить любой файл, не распаковывая для этого весь архив.
  3. Глубина архивации должна настраиваться (что называется, backup rotate).
  4. Было бы неплохо также с архивом сохранять дескрипторы безопасности NTFS.
  5. И вообще, хочется максимальной расширяемости и настраиваемости, если завтра возникнет желание нагородить дополнительный функционал.

Что ж, требования сформулированы, дело за малым – спланировать и реализовать всё остальное.

Выбор средств

Поскольку сторонние средства мы решили отметать, то как такового выбора технологий у нас почти не остается:

  • Алгоритмический язык и командлеты – Powershell (хотя тут при желании можно и VBScript)
  • Доступ к файлам через службу теневого копирования тома (VSS)
  • Формат архива – Zip
  • Механизм копирования только измененных файлов – архивный бит файловой системы
  • Автоматизация запусков – встроенный планировщик
  • ПО архивации – ?

Несморя на то, что через пользовательский интерфейс Windows создать «Сжатую ZIP-папку» проще простого, в результате поиска встроенного аналога командной строки меня постиг облом №1. Для реализации, собственно, функции архивирования для оригинальной Windows из коробки, к сожалению, так или иначе требовалась либо доустановка NET Framework, либо сторонних командлетов Powershell, либо файлов из Resource Kit, либо чего-то еще.

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

Глубоко вздохнув, приходится делать шаг в сторону от одного из вышеозначенных принципов и взять на роль архиватора готовое решение. С точки зрения опенсорсности и бесплатности опять же практически безальтернативно выбор падает на:

  • ПО архивации – 7-Zip

План алгоритма

Итак, алгоритм предельно прост: пишем сценарий на Powershell, который должен:

  1. Исходя из переданных параметров, создать теневую копию интересуемого тома.
  2. Получить к ней доступ из операционной системы.
  3. В случае дифференциальной/инкрементной резервной копии составить список измененных файлов.
  4. Заархивировать нужные файлы.
  5. По возможности заархивировать NTFS ACL
  6. Удалить теневую копию.
  7. В случае создания полной/инкрементной резервной копии – сбросить с файлов архивный бит.
  8. В случае создания полной копии удалить старые бэкапы (старше заданной глубины архивации).

Скрипт

Ходить вокруг да около тут незачем, сразу публикую готовый скрипт:

Итоговый скрипт backup-files.ps1

##################################################################################################
# Скрипт резервного копирования данных v0.9b
# 25-12-2014
# Accel
##################################################################################################
#
#Поддерживаются полные и дифференциальные копии (на основе архивного атрибута файлов)
#
#Системные требования: 
#	Windows 7+, 2008+
#	Установленный архиватор 7-Zip (тестировалось на версии 9.30b)
#
#За один запуск скрипта возможно резервное копирование лишь с одного диска
#
#NTFS-полномочия на данный момент не сохраняются (определяется возможностями архиватора)
#
#Скрипт должен запускаться от пользователя, имеющего доступ к архивируемым файлам (с правами SYSTEM, Backup Operator и т.п.)

$ErrorActionPreference="SilentlyContinue"
Stop-Transcript | out-null
$ErrorActionPreference = "Continue"

##################################################################################################
#Начало блока переменных
##################################################################################################

#Название задания архивирования
#Используется в именовании архива и ссылки на теневую копию
#Должно отвечать правилам именования файлов, наличие пробелов не рекомендуется, т.к. не тестировалось
#Пример: $ArchiveTaskName="DiskE"
$ArchiveTaskName="DiskE"

#Путь до диска-источника резервного копирования 
#Перечень целевых папок этого диска определяется отдельно
#Пример: $SrcDrivePath="D:"
$SrcDrivePath="D:"

#Путь до целевого диска 
#Пример: $BackupDrivePath="E:"
$BackupDrivePath="E:"

#Полный путь до файла со списком папок для архивирования на диске-источнике
#Пример: $SubfoldersToBackupFile = "E:Backupsrc_dirs.txt"
#	* Каждая строка данного файла должна содержать одну папку, которую мы хотим включить в архив
#	* Путь д.б. относительным, т.е. не содержать буквы диска.
#	* Иными словами, если одна из папок резервного копирования у нас D:FilesFilesToBackup, то в файле должна быть строка FilesFilesToBackup
#	* Кодировка - ANSI
$SubfoldersToBackupFile = "E:Backupsrc_dirs.txt"

#Путь до временного файла-списка файлов для архивации:
#Пример: $BackupFilesList = "E:Backupbackup-filelist.txt"
$BackupFilesList = "E:Backupbackup-filelist.txt"

#Путь до целевой папки с архивами (В ней не должно быть никаких других файлов, иначе rotation их может удалить! Также лучше не использовать корень диска, а создать хоть одну подпапку.)
#Пример: $ArchiveDstPath = $BackupDrivePath+"BackupScript backup"
$ArchiveDstPath = $BackupDrivePath+"BackupScript backup"

#Полный путь до файла журнала задания
#Пример: $ScriptLogFile = "E:BackupBackupFiles.log"
$ScriptLogFile = "E:BackupBackupFiles.log"

#Путь до исполняемого файла архиватора 7-Zip
#Пример: $SevenZipExecutablePath = "C:Program files7-Zip7z.exe"
$SevenZipExecutablePath = "C:Program files7-Zip7z.exe"

#Количество дней хранения архива (отсчет ведется с последнего полного бэкапа)
#Пример: $BackupRotationIntervalDays=22
$BackupRotationIntervalDays=22

##################################################################################################
#Конец блока переменных
##################################################################################################


$BackupFilesListTmp = $BackupFilesList+".tmp"
$backuptype=$args[0]
$VSCPath = $BackupDrivePath+"VSC_"+$ArchiveTaskName+"_$(Get-Date -format "yyyyMMdd")"

Start-Transcript -path $ScriptLogFile

$LogVars=1

if ($LogVars=1) {
	echo "================================================================="
	echo "ArchiveTaskName: $ArchiveTaskName"
	echo "SrcDrivePath: $SrcDrivePath"
	echo "BackupDrivePath: $BackupDrivePath"
	echo "SubfoldersToBackupFile: $SubfoldersToBackupFile"
	echo "BackupFilesList: $BackupFilesList"
	echo "ArchiveDstPath: $ArchiveDstPath"
	echo "ScriptLogFile: $ScriptLogFile"
	echo "SevenZipExecutablePath: $SevenZipExecutablePath"
	echo "VSCPath: $VSCPath"
	echo "BackupRotationIntervalDays: $BackupRotationIntervalDays"
	echo "================================================================="
	}

echo "Backup started at: $(Get-Date)"

function BackupFull {
	echo "Backup type: full"
	
	#Создаем теневую копию
	$s1 = (gwmi -List Win32_ShadowCopy).Create($SrcDrivePath, "ClientAccessible")
	$s2 = gwmi Win32_ShadowCopy | ? { $_.ID -eq $s1.ShadowID }
	$d  = $s2.DeviceObject + ""

	#Создаем на нее ярлык (удалим предыдущий, если остался после прерванной архивации)
	CMD /C rmdir "$VSCPath"
	cmd /c mklink /d $VSCPath "$d"

	#Составляем список папок для архивации
	"" | Set-Content $BackupFilesList
	Get-Content $SubfoldersToBackupFile | Foreach-Object {CMD /C "echo $VSCPath$_* >> $BackupFilesList" }
	
	#Создаем массив параметров для 7-Zip
	$Arg1="a"
	$Arg2=$ArchiveDstPath+""+$ArchiveTaskName+"_$(Get-Date -format "yyyy-MM-dd")_`(Full`).zip"
	$Arg3="-i@"+$BackupFilesList
	$Arg4="-w"+$ArchiveDstPath
	$Arg5="-mx=3"
	$Arg6="-mmt=on"
	$Arg7="-ssw"
	$Arg8="-scsUTF-8"
	$Arg9="-spf"

	#Зипуем
	& $SevenZipExecutablePath ($Arg1,$Arg2,$Arg3,$Arg4,$Arg5,$Arg6,$Arg7,$Arg8,$Arg9)
	
	Remove-Item $BackupFilesList

	#Если теневые копии имеют необъяснимую тенденцию копиться, лучше удалим их все
	#CMD /C "vssadmin delete shadows /All /Quiet"
	
	#Или можно удалить только конкретную созданную в рамках данного бекапа
	"vssadmin delete shadows /Shadow=""$($s2.ID.ToLower())"" /Quiet" | iex

	#Удаляем ярлык
	CMD /C rmdir $VSCPath

	#Снимаем архивный бит
	Get-Content $SubfoldersToBackupFile | Foreach-Object {CMD /C "attrib -A -H -S $SrcDrivePath$_* /S /L" }
	
	#делаем rotation
	echo "Rotating old files..."
	CMD /C "forfiles /D -$BackupRotationIntervalDays /S /P ""$ArchiveDstPath"" /C ""CMD /C del @file"""
	}
	
function BackupDiff {
	echo "Backup type: differential"
	
	#Создаем теневую копию
	$s1 = (gwmi -List Win32_ShadowCopy).Create($SrcDrivePath, "ClientAccessible")
	$s2 = gwmi Win32_ShadowCopy | ? { $_.ID -eq $s1.ShadowID }
	$d  = $s2.DeviceObject + ""

	#Создаем на нее ярлык (удалим предыдущий, если остался после прерванной архивации)
	CMD /C rmdir $VSCPath
	cmd /c mklink /d $VSCPath "$d"
	
	#Включаем UTF-8
	CMD /C "chcp 65001 > nul"
	
	#Составляем список файлов, измененных с момента предыдущей архивации
	"" | Set-Content $BackupFilesList
	Get-Content $SubfoldersToBackupFile | Foreach-Object {CMD /C "dir $VSCPath$_ /B /S /A:A >> $BackupFilesList" }
	
	CMD /C "chcp 866 > nul"
	
	$SearchPattern="^"+$BackupDrivePath.Substring(0,1)+":\"
	
	#Отрезаем букву диска, иначе 7-zip при архивации по списочному файлу глючит, находя несуществующие дубли
	#(Get-Content $BackupFilesList) -replace $SearchPattern,'' > $BackupFilesListTmp
	Get-Content $BackupFilesList | ForEach-Object { $_ -replace $SearchPattern,"" } | Set-Content ($BackupFilesListTmp)
	
	Remove-Item $BackupFilesList
	Rename-Item $BackupFilesListTmp $BackupFilesList
	
	#Поскольку имя диска в путях удалили, нужно перейти в нужную директорию
	cd $BackupDrivePath

	#Создаем массив параметров для 7-Zip
	$Arg1="a"
	$Arg2=$ArchiveDstPath+""+$ArchiveTaskName+"_$(Get-Date -format "yyyy-MM-dd")_`(Diff`).zip"
	$Arg3="-i@"+$BackupFilesList
	$Arg4="-w"+$ArchiveDstPath
	$Arg5="-mx=3"
	$Arg6="-mmt=on"
	$Arg7="-ssw"
	$Arg8="-scsUTF-8"
	$Arg9="-spf"

	#Зипуем
	& $SevenZipExecutablePath ($Arg1,$Arg2,$Arg3,$Arg4,$Arg5,$Arg6,$Arg7,$Arg8,$Arg9)
	
	Remove-Item $BackupFilesList

	#Если теневые копии имеют необъяснимую тенденцию копиться, лучше удалим их все
	#CMD /C "vssadmin delete shadows /All /Quiet"
	
	#Или можно удалить только конкретную созданную в рамках данного бекапа
	"vssadmin delete shadows /Shadow=""$($s2.ID.ToLower())"" /Quiet" | iex

	#Удаляем ярлык
	CMD /C rmdir $VSCPath
	}
	
if ($backuptype -eq "diff") {
	BackupDiff | Out-Host
	}
elseif ($backuptype -eq "full") {
	BackupFull | Out-Host
	}
else {
	echo $backuptype
	echo "None backup type parameter passed! Usage: scriptname.ps1 [ full | diff ]"
	}

echo "Backup finished at: $(Get-Date)"

Stop-Transcript

Для работы скрипта мы должны заполнить выделенный в начале файла блок переменных (пути архивации, глубина хранения архивов) и создать файл с папками-источниками, например, такой:

Файл папок-источников src_dirs.txt

bases
files
photos
users/profiles

Использование

Складываем всё в одну папку, и всё, можно запускать в Powershell. Единственный параметр — тип запуска [full | diff], определяет полный/дифференциальный способ копирования.

PS E:Backup> .backup-files.ps1 full

Убедившись, что всё работает (или поправив параметры, если нет) создаём задание в планировщике (отдельно для полного копирования и отдельно для дифференциального).

Экспортированный пример задания: scheduled-backup-task-full.xml

<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo>
    <Date>2014-10-14T23:14:45.0256428</Date>
    <Author>PCAccel</Author>
  </RegistrationInfo>
  <Triggers />
  <Principals>
    <Principal id="Author">
      <UserId>PCUser</UserId>
      <LogonType>S4U</LogonType>
      <RunLevel>HighestAvailable</RunLevel>
    </Principal>
  </Principals>
  <Settings>
    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
    <DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
    <AllowHardTerminate>true</AllowHardTerminate>
    <StartWhenAvailable>true</StartWhenAvailable>
    <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
    <IdleSettings>
      <StopOnIdleEnd>true</StopOnIdleEnd>
      <RestartOnIdle>false</RestartOnIdle>
    </IdleSettings>
    <AllowStartOnDemand>true</AllowStartOnDemand>
    <Enabled>true</Enabled>
    <Hidden>false</Hidden>
    <RunOnlyIfIdle>false</RunOnlyIfIdle>
    <DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession>
    <UseUnifiedSchedulingEngine>false</UseUnifiedSchedulingEngine>
    <WakeToRun>true</WakeToRun>
    <ExecutionTimeLimit>P3D</ExecutionTimeLimit>
    <Priority>7</Priority>
  </Settings>
  <Actions Context="Author">
    <Exec>
      <Command>C:WindowsSystem32WindowsPowerShellv1.0powershell.exe</Command>
      <Arguments>-Command "& {E:Backupbackup-files.ps1 full}"</Arguments>
    </Exec>
  </Actions>
</Task>

Ну вот, в целом, и всё, можно настроить, представить папку с архивами системе мониторинга наличия резервных копий (если есть) и забыть.

Замечания

  • Для корректной работы (в первую очередь для создания теневой копии) powershell должен быть запущен от имени администратора (или «с наивысшими привилегиями» в терминологии планировщика заданий).
  • По идее способ также годится для горячего консистентного резервного копирования хоть баз MSSQL, хоть MS Exchange (при установке соотв. shadow copy providers, которые к подобному софту идут в комплекте), хотя конкретно в этих случаях удобнее пользоваться встроенными средствами.
  • Инкрементным резервным копированием я не пользуюсь (много хлопот найти удаленный файл среди кучи инкрементных архивов), но если возникнет потребность, то он получается буквально комбинированием нескольких срочек из полного и дифференциального блоков скрипта.
  • Здесь также не реализован механизм сохранения ACL (формат zip, да и 7zip не поддерживают хранение дескрипторов безопасности в архиве; RAR умеет, но это уже не свободное ПО, что сильно противоречит условиям задачи). В случае необходимости дескрипторы можно сохранять в файл встроенными утилитами типа icacls и добавлять полученный дамп в создаваемый архив.
  • Увы и ах, алгоритм не подходит для XP/2003. Сложность возникает на моменте создания ярлыка на теневую копию (в этих ОС нет утилиты mklink, а по-быстрому обойти эти грабли у меня не вышло).

P.S.

Перед необходимостью изобретать свой велосипед автор за несколько лет намучался перепробовал большое количество разнообразного бесплатного готового ПО со похожей функциональностью (Cobian Backup, COMODO Backup и др.). Вдоволь находившись по разнообразным встроенным в упомянутый софт граблям, было принято решение написать что-то своё. На данный момент описанное решение успешно работает на серверах (Windows Server 2008 R2) и рабочих станциях (Windows 7 и Windows 8.1).

Самая крупная создаваемая полная резервная копия на данный момент составляет 1 Тб в исходных файлах, в архиве – 350 Гб. При архивировании с зеркала SAS (7200) на такой же локальный Volume-диск (оба работают внутри vSphere, будучи подключенными как RDM Passthrough-диски) операция занимает около 6 часов, что в условиях задачи является вполне приемлемым результатом.

Автор: Aclz

Источник


  1. Алексей:

    Здорово что приведен листинг кода, но мне кажется в нем нет обратных слешей совсем, к прмеру все пути съехали? можно ли ссылку на архив? сам в любом случае буду разбираться т.к. хочу у себя внедрить

  2. Андрей:

    Спасибо за классный скрипт! Но считаю неплохо было бы доработать, добавить список исключений.

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js