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

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

Нехай завдання буде звучати наступним чином:

  1. Необхідно організувати автоматичне резервне копіювання окремих файлів комп'ютера на окремий носій.
  2. Комп'ютер працює під управлінням Windows версій 7 / 2008 або більш пізньої.
  3. великий Обсяг даних, тому копіювання повинно підтримуватися як повне, так і диференціальне.
  4. Д. б. можливість копіювати будь-які файли, в т. ч. системні, заблоковані на читання і т. п.
  5. Стороннім платним софтом користуватися категорично не хочеться (ну, припустимо, ми обмежені в коштах, а ці ваші торрент — не наш шлях! Чи релігія не дозволяє. Або мільйон інших причин.), а краще взагалі обійтися без будь-якого стороннього софту, користуючись лише можливостями ОС .
Трохи подумавши, ще розширимо список хотілок:

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


Вибір засобів
Оскільки сторонні кошти ми вирішили відмітати, як такого вибору технологій у нас майже не залишається:

  • Алгоритмічна мова і запустіть — Powershell (хоча тут при бажанні можна і VBScript)
  • Доступ до файлів через службу тіньового копіювання тому (VSS)
  • Формат архіву — Zip
  • Механізм копіювання тільки змінених файлів — архівний біт файлової системи
  • Автоматизація запусків — вбудований планувальник
  • ЗА архівації — ?
Несморя на те, що через інтерфейс Windows створити «Стиснутої папки» простіше простого, в результаті пошуку вбудованого аналога командного рядка мене спіткав облом №1. Для реалізації, власне, функції архівування для оригінальної Windows з коробки, на жаль, так чи інакше потрібна або доустановка NET Framework, або сторонніх командлетів Powershell, або файлів з Resource Kit, або чогось ще.

Випробувавши ряд перерахованих вище варіантів мене спіткав облом №2: на великих обсягах архівованих даних (починаючи від пари сотень гігабайт) одні просто вилітали, інші з'їдали всю пам'ять і починали вантажити сервер, треті ще якимось чином починали бешкетувати.

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

  • ЗА архівації — 7-Zip


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


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

Підсумковий скрипт backup-файли.ps1
##################################################################################################
# Скрипт резервного копіювання даних v0.9b
# 25-12-2014
# Accel
##################################################################################################
#
#Підтримуються повні і диференціальні копії (на основі архівного атрибута файлів)
#
#Системні вимоги: 
# Windows 7+, 2008+
# Встановлений архіватор 7-Zip (тестувалося на версії 9.30 b)
#
#За один запуск скрипта можливо резервне копіювання лише з одного диска
#
#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:\Backup\src_dirs.txt"
# * Кожен рядок цього файлу повинна містити одну папку, яку ми хочемо включити в архів
# * Шлях д. б. відносним, тобто не містити букви диска.
# * Іншими словами, якщо одна з папки резервного копіювання у нас D:\Files\FilesToBackup, то у файлі має бути рядок Files\FilesToBackup
# * Кодування - ANSI
$SubfoldersToBackupFile = "E:\Backup\src_dirs.txt"

#Шлях до тимчасового файлу-список файлів для архівації:
#Приклад: $BackupFilesList = "E:\Backup\backup-filelist.txt"
$BackupFilesList = "E:\Backup\backup-filelist.txt"

#Шлях до цільової папки з архівами (У ній не повинно бути ніяких інших файлів, інакше rotation їх може видалити! Також краще не використовувати корінь диска, а створити хоч одну папку.)
#Приклад: $ArchiveDstPath = $BackupDrivePath+"Backup\Script backup"
$ArchiveDstPath = $BackupDrivePath+"Backup\Script backup"

#Повний шлях до файлу журналу завдання
#Приклад: $ScriptLogFile = "E:\Backup\BackupFiles.log"
$ScriptLogFile = "E:\Backup\BackupFiles.log"

#Шлях до виконуваного файлу архіватора 7-Zip
#Приклад: $SevenZipExecutablePath = "C:\Program files\7-Zip\7z.exe"
$SevenZipExecutablePath = "C:\Program files\7-Zip\7z.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-файли.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>PC\Accel</Author>
</RegistrationInfo>
<Triggers />
<Principals>
<Principal id="Author">
<UserId>PC\User</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:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe</Command>
<Arguments>-Command "& {E:\Backup\backup-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 годин, що в умовах завдання є цілком прийнятним результатом.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.