Posts on this page:
Слава сиськамПротоколу Сноверу!
Примечание: данный материал публикуется на правах ТЗ (ТЗ — Тайное Знание) и обязателен для знания администраторам PKI.
В предыдущей части (Certificate chaining engine в PowerShell) мы рассмотрели реализацию certificate chaining engine в .NET в виде класса X509Chain. Но этот рассказ был бы неполным, если мы не поговорили про CAPICOM.Chain. Хоть Microsoft рекомендует использовать .NET, а не CAPICOM для решения данной задачи, я считаю необходимым осветить этот вопрос. Для начала начнём с реализации CAPICOM в PowerShell. По непонятным мне пока причинам мне не удалось завести его в PowerShell на системах Windows Vista и выше, поэтому примеры буду приводить на Windows Server 2003.
PS G:\> $chain = New-Object -ComObject "CAPICOM.Chain" PS G:\> $chain Certificates ------------ PS G:\> $chain | gm -MemberType methods TypeName: System.__ComObject#{ca65d842-2110-4073-aee3-d0aa5f56c421} Name MemberType Definition ---- ---------- ---------- ApplicationPolicies Method IOIDs ApplicationPolicies () Build Method bool Build (ICertificate) CertificatePolicies Method IOIDs CertificatePolicies () ExtendedErrorInfo Method string ExtendedErrorInfo (int) PS G:\>
Так же, как и в .NET у нас есть метод Build(), который запускает certificate chaining engine для указанного сертификата. Но это COM объект и X509Certificate2 объекты не принимает. Поэтому мы средствами этого же CAPICOM получим нужный объект сертификата:
PS G:\> $store = New-Object -ComObject "CAPICOM.Store" PS G:\> $store.Open(2,"my",128) PS G:\> $cert = @() PS G:\> $store.Certificates | %{$cert += $_} PS G:\> $cert = $cert[1] PS G:\> $cert Version : 3 SerialNumber : 167D6D32000000000011 SubjectName : CN=Administrator, CN=Users, DC=sysadmins, DC=lv IssuerName : CN=sysadmins-LV-CA, DC=sysadmins, DC=lv ValidFromDate : 2009.08.07. 16:09:56 ValidToDate : 2010.08.07. 16:09:56 Thumbprint : 1FA71B51BCE461BEE24B11AA9EF855ED00DFDFD4 Archived : False PrivateKey : PS G:\>
Вот такой объект сертификата мы получили. Вот его и пропустим через метод Build():
PS G:\> $chain.Build($cert) False PS G:\> $chain.Status() 32 PS G:\> $chain.Certificates Version : 3 SerialNumber : 167D6D32000000000011 SubjectName : CN=Administrator, CN=Users, DC=sysadmins, DC=lv IssuerName : CN=sysadmins-LV-CA, DC=sysadmins, DC=lv ValidFromDate : 2009.08.07. 16:09:56 ValidToDate : 2010.08.07. 16:09:56 Thumbprint : 1FA71B51BCE461BEE24B11AA9EF855ED00DFDFD4 Archived : False PrivateKey : Version : 3 SerialNumber : 3DFFAF914D613282427BD591C1FB586D SubjectName : CN=sysadmins-LV-CA, DC=sysadmins, DC=lv IssuerName : CN=sysadmins-LV-CA, DC=sysadmins, DC=lv ValidFromDate : 2009.08.06. 10:21:01 ValidToDate : 2014.08.06. 10:30:54 Thumbprint : E82ACC45841280DDEAB9F7847418FA26354457A7 Archived : False PrivateKey : PS G:\> $chain.ApplicationPolicies() Name FriendlyName Value ---- ------------ ----- 123 Smart Card Logon 1.3.6.1.4.1.311.20.2.2 101 Client Authentication 1.3.6.1.5.5.7.3.2 PS G:\>
Метод вернул False, т.е. наш сертификат не прошёл проверку. Свойство Certificates содержит все сертификаты в текущей цепочке. А метод ApplicationPolicies() показал Application Policies (в прошлом EKU). Ну и метод Status() вернул код ошибки. Все коды ошибок расписаны в описании самого метода. Т.е. ошибка 32 по табличке означает:
CAPICOM_TRUST_IS_UNTRUSTED_ROOT (&H00000020) — The certificate or certificate chain is based on an untrusted root.
Особо углубляться дальше я смысла не вижу, поскольку используя эти примеры можно поупражняться в других задачах с использованием CAPICOM. А вместо этого, мы разберём кое-какие неочевидные, но очень важные вещи.
Когда вышел Remote Desktop Connection 7.0 (который поставляется вместе с Windows 7), Александр Станкевич (MVP: Enterprise Security) заметил очень плохую особенность с ним, а именно — рандомно пропал доступ к терминальным серверам, которые используют SSL для терминальных подключений. В ходе множества проверок было констатировано:
RDP клиент мотивировал отказ в подключении следюущим: «RDP клиент не смог проверить сертификат на отзыв». Причём подключение с предыдущих версий RDC, а так же подключение к TS Web Access через IE было успешным. Как выяснилось на днях, причина была в том, что в новом RDC был изменён алгоритм certificate chaining engine и новая версия RDC больше не доверяла корневым сертификатам в пользовательском хранилище. Переустановка корневого сертификата в компьютерное хранилище решала проблему RDC 7. Но, как я говорил, в Windows 7/2008 R2 было изменено поведение certificate chaining engine в реализации X509Chain. Продемонстрирую небольшую табличку, которая покажет доверие пользовательским корневым сертификатам в CAPICOM и .NET реализациях certificate chaining engine для различных операционных систем:
X509Chain (.NET) | CAPICOM.Chain (CryptoAPI) | |
Windows XP | :yes: | :no: |
Windows Server 2003 | :yes: | :no: |
Windows Vista | :yes: | :no: |
Windows Server 2008 | :yes: | :no: |
Windows 7 | :no: | :no: |
Windows Server 2008 R2 | :no: | :no: |
Таблица доверия пользовательскким корневым сертификатам различными движками построения цепочки сертификатов
Следует учитывать, что различные приложения могут использовать свой движок для построения цепочек сертификатов, как RDC 7.0 и PKIView.msc использует CAPICOM, а PowerShell — X509Chain. Однако, у меня есть уверенность, что существует ещё одна реализация этого движка, которую использует Internet Explorer. Это только мои догадки, но они основаны на том, что Internet Explorer (кроме Windows 7 и выше) использует пользовательский контейнер для проверки SSL веб-сайтов, т.е. по симптомам похоже на X509Chain. Однако, этот класс появился только в .Net Framework 2.0, которого, к примеру в оригинальной инсталляции Windows XP/2003 не было. Либо, как вариант, есть возможность настраивать этот момент в каждой реализации движка построения цепочек, но я пока не нашёл как. Это пока всё, что у меня есть по этому поводу.
Я предлагаю снова поговорить о certificate chaining engine, о котором мы уже говорили в посте Certificate Chaining Engine — как это работает но в рамках его реализации в .NET и PowerShell. В Windows есть несколько реализаций этого chaining engine. Самые популярные:
CAPICOM — вещь несколько стрёмная и её лучше избегать. Тем более мне не удалось завести его в Vista/Windows 7. Реализация в .NET в виде X509Chain ничуть не хуже, но, в то же время, проще. Итак, предлагаю начать с создания этого объекта с помощью New-Object:
[↓] [vPodans] $chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain [↓] [vPodans] $chain ChainContext ChainPolicy ChainStatus ChainElements ------------ ----------- ----------- ------------- 0 System.Security.Cryptograp... {} {} [↓] [vPodans] $chain | gm -membertype methods TypeName: System.Security.Cryptography.X509Certificates.X509Chain Name MemberType Definition ---- ---------- ---------- Build Method bool Build(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) Equals Method bool Equals(System.Object obj) GetHashCode Method int GetHashCode() GetType Method type GetType() Reset Method System.Void Reset() ToString Method string ToString() [↓] [vPodans]
Здесь видно, что метод, который будет строить и проверять цепочку будет называться Build() и в качестве аргумента этот метод принимает только объекты x509Certificate2, а возвращать объект типа Boolean — True/False. Объекты такого типа можно найти в провайдере Certififcates (просто выполнить dir cert:\store\container) или импортировать из файла — Импорт сертификатов в PowerShell. Но сначала мы посмотрим, что здесь можнол настроить. А настроить можно ChainPolicy, представляющий собой объект X509ChainPolicy. Эта политика позволяет задавать порядок проверки отзыва сертификатов, соответствие сертификата каким-то certificate policy, наличие определённых application policy (в прошлом известен нам как EKU — Extended Key Usage) и некритичность каких-то ошибок. Мы всё разбирать не будем, а только основное:
[↓] [vPodans] $chain.ChainPolicy ApplicationPolicy : {} CertificatePolicy : {} RevocationMode : Online RevocationFlag : ExcludeRoot VerificationFlags : NoFlag VerificationTime : 17.10.2009 20:35:17 UrlRetrievalTimeout : 00:00:00 ExtraStore : {} [↓] [vPodans]
Первое, что может быть интересным — Revocation Mode, который задаёт режим проверки отзыва и может иметь 3 вполне понятных значения, которые перечислены в X509RevocationMode:
[↓] [vPodans] [System.Enum]::GetNames([system.security.cryptography.x509certificates.x509revocationmode])
NoCheck
Online
Offline
Тут очень просто:
Если проверяем сертификаты на отзыв, то мы можем выбрать что именно будем проверять и этот выбор перечислен в X509RevocationFlag:
[↓] [vPodans] [System.Enum]::GetNames([system.security.cryptography.x509certificates.x509revocationflag])
EndCertificateOnly
EntireChain
ExcludeRoot
и здесь тоже всё достаточно понятно:
Следующий момент определяет флаги проверки, т.е. по каким именно критериям будет проверяться цепочка сертификатов:
[↓] [vPodans] [System.Enum]::GetNames([system.security.cryptography.x509certificates.x509verificationflags])
NoFlag
IgnoreNotTimeValid
IgnoreCtlNotTimeValid
IgnoreNotTimeNested
IgnoreInvalidBasicConstraints
AllowUnknownCertificateAuthority
IgnoreWrongUsage
IgnoreInvalidName
IgnoreInvalidPolicy
IgnoreEndRevocationUnknown
IgnoreCtlSignerRevocationUnknown
IgnoreCertificateAuthorityRevocationUnknown
IgnoreRootRevocationUnknown
AllFlags
Эти флаги так же достаточно понятны и подробно разжёваны в перечислении X509VerificationFlags.
Примечание: эти флаги показывают что будет исключено из проверки, а не наоборот. По умолчанию проверяется всё (установлен NoFlag).
Как можно задавать эти флаги и режимы, если есть такая потребность? Можно пойти двумя способами — правильным и неправильным, хотя оба рабочие:
[↓] [vPodans] # правильный метод: [↓] [vPodans] $revflag = [System.Security.Cryptography.X509Certificates.X509RevocationFlag]::EndCertificateOnly [↓] [vPodans] $chain.ChainPolicy.RevocationFlag = $revflag [↓] [vPodans] $chain.ChainPolicy ApplicationPolicy : {} CertificatePolicy : {} RevocationMode : Online RevocationFlag : EndCertificateOnly VerificationFlags : NoFlag VerificationTime : 17.10.2009 20:35:17 UrlRetrievalTimeout : 00:00:00 ExtraStore : {} [↓] [vPodans] # неправильный метод, но мне он нравится [↓] [vPodans] $chain.ChainPolicy.RevocationFlag = "excluderoot" [↓] [vPodans] $chain.ChainPolicy ApplicationPolicy : {} CertificatePolicy : {} RevocationMode : Online RevocationFlag : ExcludeRoot VerificationFlags : NoFlag VerificationTime : 17.10.2009 20:35:17 UrlRetrievalTimeout : 00:00:00 ExtraStore : {} [↓] [vPodans]
В принципе, все эти enumeration можно просто указывать в виде строки, а PowerShell уже сам подобъёт его под нужный тип.
Примечание: флаги проверки на отзыв можно указывать только по одному, а флаги исключений можно перечислять через запятую. Это видно по названию класса: если класс указан в единственном числе (X509RevocationFlag), то и значение можно указать только одно, а если во множественном числе (X509VerificationFlags), то их можно указать несколько через запятую.
В принципе, можно делать проверку:
[↓] [vPodans] $valid = (dir cert:\currentuser\my)[1] [↓] [vPodans] $invalid = (dir cert:\currentuser\my)[0] [↓] [vPodans] $chain.Build($valid) True [↓] [vPodans] $chain.ChainElements | select -expand certificate Thumbprint Subject ---------- ------- 986D375362652FE9E39BA4D042A6B8BA75745998 CN=Administrator, CN=Users, DC=sysadmins, DC=lv E82ACC45841280DDEAB9F7847418FA26354457A7 CN=sysadmins-LV-CA, DC=sysadmins, DC=lv [↓] [vPodans]
Я из локального хранилища взял заведомо хороший сертификат и плохой (разумеется, это сертификат с сайта БиЛайн :-)). Метод Build() вернул True, что означает успешную проверку всей цепочки моего сертификата до доверенного корня. А вот что случилось с сертификатом билайна:
[↓] [vPodans] $chain.reset() [↓] [vPodans] $chain.Build($invalid) False [↓] [vPodans] $chain.ChainElements | select -expand certificate Thumbprint Subject ---------- ------- EB74DA32E865C78FCB853DDA5FE45962098E1B3B CN=trust.beeline.ru, OU=DIT, O=Vimpelcom, L=Moscow, S=Moscow, C=RU [↓] [vPodans] $chain.ChainStatus Status StatusInformation ------ ----------------- PartialChain Sertificesanas kedi nevareja veidot pie uzticamas saknes... RevocationStatusUnknown Atsauksanas funkcija nevareja parbaudit sertifikata atsa... OfflineRevocation Atsauksanas funkcija nevareja parbaudit atsauksanu, jo a... [↓] [vPodans] $chain.ChainStatus | %{$_.statusinformation.trim()} Sertificesanas kedi nevareja veidot pie uzticamas saknes iestades. Atsauksanas funkcija nevareja parbaudit sertifikata atsauksanu. Atsauksanas funkcija nevareja parbaudit atsauksanu, jo atsauksanas serveris bija bezsaiste. [↓] [vPodans]
Поскольку при каждой проверке сертификаты в ChainElements накапливаются, то после каждой проверки следует очищать объект методом Reset(). Как и следовало ожидать, сертификат билайна вернул False, показывая, что цепочка у этого сертификата имеет проблемы. В случае неуспешной проверки, будет заполняться свойство ChainStatus. Данное свойство содержит все ошибки, которые были костатированы при проверке цепочки. Для меня пока непонятным стал факт, что ошибки он написал на латышском языке, хотя я его об этом не просил. Откуда он это взял — непонятно, но он сказал, что не смог построить цепочку до доверенного корня и функция проверки отзыва провалилась по всем статьям, поскольку пути в CDP сертификата нерабочие.
Примечание: обязательно следует учитывать тот факт, что при построении цепочки сертификатов и проверке доверия класс X509Chain в Windows 7 и Windows Server 2008 R2 работает в контексте LocalSystem и всегда игнорирует пользовательские контейнеры Trusted Root Certification Authorities. Поэтому если корень цепочки не заканчивается на одном из сертификатов Trusted Root CAs хранилища LocalMachine, то цепочка будет считаться недоверенной. Для предыдущих ОС цепочка может заканчиваться на пользовательском хранилище Current User.
В принципе это всё, что вам следует знать про реализацию certificate chaining engine в .NET и его использование в PowerShell. В качестве бонуса прилагаю скрипт для проверки цепочки сертификатов:
##################################################################### # Test-Certificate.ps1 # Version 1.0 # # Passes certificate through certificate chaining engine # # Vadims Podans (c) 2009 # http://www.sysadmins.lv/ ##################################################################### #requires –Version 2.0 function Test-Certificate { [CmdletBinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)] $Certificate, [System.Security.SecureString]$Password, [ValidateSet("NoCheck", "Online", "Offline")] [string]$CRLMode = "Online", [ValidateSet("EndCertificateOnly", "EntireChain", "ExcludeRoot")] [string]$CRLFlag = "ExcludeRoot", [ValidateSet("AllFlags", "AllowUnknownCertificateAuthority", "NoFlag", "IgnoreNotTimeValid", "IgnoreCtlNotTimeValid", "IgnoreNotTimeNested", "IgnoreInvalidBasicConstraints", "IgnoreWrongUsage", "IgnoreInvalidName", "IgnoreInvalidPolicy", "IgnoreEndRevocationUnknown", "IgnoreCtlSignerRevocationUnknown", "IgnoreCertificateAuthorityRevocationUnknown", "IgnoreRootRevocationUnknown")] [string[]]$VerificationFlags = "NoFlag" ) begin { # создаём объекты X509Certificate2 и X509chain ещё до поступлениея первого объекта сертификата $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain # в X509Chain записываем параметры проверки сертификатов. Как критерии # отзыва, так и исключения из проверки $chain.ChainPolicy.RevocationFlag = $CRLFlag $chain.ChainPolicy.RevocationMode = $CRLMode $chain.ChainPolicy.VerificationFlags = $VerificationFlags # мини-функция для предоставления понятного вывода на экран о статусе проверки # и ошибках в цепочке, если они есть. function _getstatus_ ($status, $chain, $cert) { if ($status) { Write-Host Current certificate $cert.SerialNumber chain and revocation status is valid -ForegroundColor Green } else { Write-Warning "Current certificate $($cert.SerialNumber) chain is invalid due of the following errors:" $chain.ChainStatus | %{Write-Host $_.StatusInformation.trim() -ForegroundColor Red} } } } process { # если аргумент сертификата уже является объектом X509Certificate2, то # сразу строим цепочку и получаем статус цепочки вызывая мини-функцию if ($_ -is [System.Security.Cryptography.X509Certificates.X509Certificate2]) { $status = $chain.Build($_) _getstatus_ $status $chain $_ } else { # если аргумент не является объектом X509Certificate2, то это будет путь к файлу # если указанный путь не существует, то ругаемся и выходим if (!(Test-Path $Certificate)) {Write-Warning "Specified path is invalid"; return} else { # если путь существует, то проверяем, что это путь файловой системы. # если это не так, то ругаемся и выходим if ((Resolve-Path $Certificate).Provider.Name -ne "FileSystem") { Write-Warning "Spicifed path is not recognized as filesystem path. Try again"; return } else { # если предварительные проверки прошли успешно, то выбираем объект файла $Certificate = gi $(Resolve-Path $Certificate) # и на основании расширения файла через Switch преобразуем файл в # X509Certificate2 объект или объекты switch -regex ($Certificate.Extension) { "\.CER|\.DER|\.CRT" {$cert.Import($Certificate.FullName)} "\.PFX" { if (!$Password) {$Password = Read-Host "Enter password for PFX file $certificate" -AsSecureString} # тут пришлось указать контекст хранилища, куда предполагается сохранить сертификат. # на самом деле мы никуда не будем сохранять объект сертификата, это было сделано # по причине отсутствия подходящего конструктора метода Import(), чтобы # можно было указать только путь к файлу и пароль от PFX $cert.Import($Certificate.FullName, $password, "UserKeySet") } "\.P7B|\.SST" { $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection $cert.Import([system.IO.File]::ReadAllBytes($file.FullName)) } default {Write-Warning "Looks like your specified file is not a certificate file"; return} } # полученный объект или объекты сертификатов пропускаем через метод Build() и вызываем # мини-функцию расшифровки статуса проверки $cert | %{ $status = $chain.Build($_) _getstatus_ $status $chain $_ } # после каждой итерации очищаем объекты сертификата и цепочки $cert.Reset() $chain.Reset() } } } } }
Данный скрипт может принимать аргументы в виде уже готовых объектов X509Certificate2 или с указанием пути к файлу сертификата, например:
dir cert:\currentuser\my | Test-Certificate Test-Certificate .\mycert.cer
Ну и при желании можно поуказывать там разные параметры и флаги проверки.
Ещё небольшое дополнение к Certificate Management Pack, который я сейчас пишу. Данный код позволяет запускать и останавливать службу Certification Authority на сервере. Поскольку я для решения этой задачи использую WMI, то ремотинг будет обеспечен. Следует учесть, что все команды по управлению службой CA будут расположены за конвейером после команды Get-CertificationAuthority. Это было сделано в рамках удобства и стандартизации. Когда мы хотим что-то настроить в CA, то сначала должны указать его имя/объект. Поскольку у нас уже есть готовое решение для этого, то почему бы его и не использовать? Код на самом деле очень простой и в нём разберётся даже новичок:
##################################################################### # Start-Stop CA.ps1 # Version 1.0 # # Starts and stops Certification Authority service on specified CA # Requires Get-CertificationAuthority cmdlet: # http://www.sysadmins.lv/PermaLink,guid,947401b2-312a-4129-860b-4296dfe46cb2.aspx # # Usage: # Get-CertificationAuthority "CAName" | Stop-CertificationAuthority # stops CA service on CA named CAName # Get-CertificationAuthority | Start-CertificationAuthority # starts all Enterprise CAs in current forest # # Vadims Podans (c) 2009 # http://www.sysadmins.lv/ ##################################################################### function Start-CertificationAuthority ($CA) { process { $status = (gwmi Win32_Service -ComputerName $CA.Computer -Filter "Name = 'CertSvc'").StartService() if ($status.ReturnValue -eq 0) { Write-Host $CA.CAName Certificate Services successfully started on $CA.Computer -ForegroundColor Green } else { Write-Warning "Unable to start $($CA.CAName) certificate services" } } } function Stop-CertificationAuthority ($CA) { process { $status = (gwmi Win32_Service -ComputerName $CA.Computer -Filter "Name = 'CertSvc'").StopService() if ($status.ReturnValue -eq 0) { Write-Host $CA.CAName Certificate Services successfully stopped on $CA.Computer -ForegroundColor Green } else { Write-Warning "Unable to stop $($CA.CAName) certificate services" } } }
Можно сказать, что выносить данные команды в отдельные функции излишне, ведь это можно и самому дописать в свой скрипт. Но можно и не сказать так, ведь представьте, насколько удобно, когда нужная функция у вас уже есть под рукой, и её не надо писать с нуля.
Update: хочу ещё раз отметить на одну из частых ошибок, которые допускают при написании скриптов в PowerShell. Вы можете просто использовать переменные в двойных кавычках и при использовании этой строки в неё будет подставляться содержимое переменной. Но этого не будет, если вы используете свойство объекта, который хранится в этой переменной, поскольку переменные экспандятся только 1 раз. Этого достаточно для простой переменной, но недостаточно для извлечения содержимого свойста объекта в этой переменной. Ведь сначала нужно извлечь содержимое переменной, а потом прочитать содержимое указанного свойства. Чтобы экспандить такие вещи, переменные в двойных кавычках следует заключать в подвыражение $().
В предыдущей части бэкапа в Windows Server 2008 R2 (Windows Server 2008 R2 и Windows Backup (часть 1)) мы рассмотрели основные моменты создания бэкапа средствами PowerShell. Сегодня мы рассмотрим вопросы самого процесса бэкапа и о его хранении.
Когда процесс бэкапа закончится, можно посмотреть его статус:
[↑] [Administrator] Get-WBSummary NextBackupTime : 0001.01.01. 0:00:00 NumberOfVersions : 5 LastSuccessfulBackupTime : 2009.10.07. 14:39:42 LastSuccessfulBackupTargetPath : \\?\Volume{ca6dbf07-14ad-11de-937f-806e6f6e6963} LastSuccessfulBackupTargetLabel : Camelot Share-1 LastBackupTime : 2009.10.07. 14:39:42 LastBackupTarget : E: DetailedMessage : LastBackupResultHR : 0 LastBackupResultDetailedHR : 0 CurrentOperationStatus : NoOperationInProgress
Здесь вы увидите основные сведения о результатах бэкапа. Свойство LastBackupResultHR содержит код возврата. Если это 0, то всё хорошо. Если это не 0, то бэкап не был выполнен удачно. А вот свойство NumberOfVersions показывает сколько уже копий бэкапа содержится в текущем архиве. Более подробно этот момент будет рассмотрен ниже.
При выполнении бэкапа происходит несколько вещей:
Если это сетевая папка, то в пути \\Server\BackupShare\WindowsImageBackup создаст папку для каждого компьютера и в ней будет хранить бэкап соответствующего компьютера. При этом последующие операции бэкапа будут копировать архив в эту же папку. Во времена ntbackup.exe мы могли выбирать метод выполнения бэкапа — с использованием VSS или без (это не относилось к SystemState бэкапам), а теперь этот вопрос решён однозначно — VSS используется всегда. Это обусловлено ещё тем, что Server Backup использует VSS для ведения истории бэкапов, что исключает путаницу в архивных копиях. Внимательные читатели могут заметить, что внутри папки бэкапа есть VHD файл (по одному VHD на каждый архивируемый том), который содержит актуальное состояние бэкапа. И тут появляется интересная вещь: каждый новый бэкап копируется в один VHD файл — а куда же деваются предыдущие копии? На самом деле все они хранятся в этом VHD файле, но скрыты за теневыми копиями, которые создаются при каждой операции бэкапа и закрепляются за архивом:
[↑] [Administrator] Get-WBBackupSet
VersionId : 10/07/2009-10:49
BackupTime : 2009.10.07. 13:49:41
BackupTarget : E:
RecoverableItems : Volumes, Files
Volume : {System (C:)}
Application : {}
VssBackupOption : VssCopyBackup
SnapshotId : 8d6aa8ef-bb24-4ffc-93da-08831bc4ae88
VersionId : 10/07/2009-11:15
BackupTime : 2009.10.07. 14:15:36
BackupTarget : E:
RecoverableItems : Volumes, Files
Volume : {System (C:)}
Application : {}
VssBackupOption : VssCopyBackup
SnapshotId : ab2c6d39-3447-4c9b-b072-f03d746045c4
<...>
Команда Get-WBBackupSet показывает историю бэкапов системы и ID номер теневой копии, которая содержит файлы архива на момет выполнения конкретного задания бэкапа. При восстановлении из бэкапа консоль MMC считывает эти копии и позволяет восстановить файлы на любой момент времени выполнения бэкапа. Чтобы дать более понятное представление об этом, покажу простой пример:
Несмотря на то, что при непосредственном просмотре VHD файла мы видим только данные сохранённые после последней операции бэкапа, в нём по прежнему хранятся и все предыдущие копии, которые система различает по теневым копиям, которые закреплены за каждым бэкапом. Именно здесь теневые копии играют огромную роль в хранении истории бэкапа. И пока эти теневые копии живы, мы имеем доступ к предыдущим версиям файлов внутри VHD архива. Это даёт следующие преимущества:
В большинстве случаев это решение будет являться достаточным для любых операций восстановления. Единственное критичное место здесь будет наличие этих теневых копий. Это может вызвать трудности только при повреждении теневых копий на архивном томе. Но обычно это уже будет означать потерю всех бэкапов. Такие дела.
Хранятся они там сколь угодно долго, пока есть свободное место. Когда свободное место заканчивается, то Server Backup автоматически пытается отыскать себе место. Если у нас выполняются только полные бэкапы, то наиболее старые версии архивов просто удаляются. Если у нас комбинируются полные бэкапы с инкрементальными/дифференциальными, то берётся наиболее старый архив и в него вписываются инкрементальные/дифференциальные архивы, которые были выполнены в промежутках между полными бэкапами до тех пор, пока не освободится достаточно для нового бэкапа места. Таким образом обеспечивается сохранность наиболее новых архивов с удалением более старых. Такая схема автоматической ротации так же будет востребована в большинстве случаев. Для экономии места Server Backup для запланированного задания автоматически делает комбинирование полных и инкрементальных бэкапов. Каждые 2 недели выполняется полный бэкап и ежедневно в промежутках между полными будет выполняться только инкрементальное архивирование.
Такая автонастройка режимов для запланированных бэкапов и авторотация будет достаточно эффективна и проста в сегменте SOHO/SMB, не отвлекая на себя слишком много внимания администратора. От администратора потребуется только создание задания и организация отказоустойчивости тома с архивами.
Разработчики Server Backup сделали всё, чтобы упростить процесс выполнения бэкапа в стандартных случаях SOHO/SMB. Но когда появляются особые условия, то тут начинаются свои сложности, хотя это всё относительно преодолимо. Например, вы создали несколько заданий бэкапов, которые отдельно что-то архивируют в одну и ту же точку. Но к каждому заданию предъявляются свои требования по сроку хранения бэкапа.
Пример: это файл-сервер и вы архивируете папку с документами пользователей ежедневно и следует хранить только 7 последние копии. Другое задание архивирует инсталляционные файлы вашей сети раз в неделю и требуется наличие только 4 последних копий. Так же все копии должны копироваться в сетевую папку или на съёмный диск на случай катастрофы и/или ада и Израиля. В такой ситуации мы потеряем возможность использования авторотации архивов и прочих плюшек. Давайте посмотрим, как будет выглядеть примерный скрипт:
# подключаем оснастку Server Backup Add-PSSnapin Windows.Serverbackup # создаём задание бэкапа $profiles = New-WBPolicy # создаём и добавляем в задание бэкапа архивируемую папку $source = New-WBFileSpec -FileSpec "D:\Users" Add-WBFileSpec -Policy $profiles -FileSpec $source # указываем локальный том, на который будет копироваться архив $target = New-WBBackupTarget -VolumePath "E:" Add-WBBackupTarget -Policy $profiles -Target $target # выполняем бэкап Start-WBBackup -Policy $profiles # проверяем код возврата с результатом выполнения бэкапа if ((Get-WBSummary).LastBackupResultHR -eq 0) { # переименовываем архив в более понятное имя $newname = "Profiles_$(Get-Date -f dd.MM.yyyy)" Ren E:\WindowsImageBackup -NewName $newname # копируем архив в сетевую папку copy e:\$newname \\server\backups\profiles # удаляем все архивы из сетевой папки, которые старше 7 дней dir \\server\backups\profiles | ?{$_.lastwritetime -lt (Get-Date).AddDays(-7)} | del -Force } else { # ругаемся, что бэкап не был завершён успешно }
И уже этот файл отдельно зашедулить в Task Scheduler. В такой ситуации дополнительных шагов не требуется, т.к. пока живы теневые копии, вы можете восстанавливать файлы из них (наличие самого архива не требуется). А если теневых копий уже не осталось (например, том с архивами был отформатирован), то для восстановления данных просто копируете папку с архивом в корень любого тома с именем WindowsImageBackup и тогда этот архив будет определён системой как пригодный для восстановления. Так вы можете делать несколько раздельных заданий с индивидуальным расписанием бэкапа и ротацией.
Если ротация архивов в сетевой папке достаточно проста и укладывается в одну строчку, то с локальными архивами придётся подключать утилиты CMD, а именно — diskshadow.exe! Вам нужно внутри diskshadow выполнить Delete Shadows ID {GUID}, где GUID — ID теневой копии, которая закреплена за конкретным бэкапом и его можно получить из вывода Get-WBBackupSet (свойство SnapshotID)
[↑] [Administrator] diskshadow
Microsoft DiskShadow version 1.0
Copyright (C) 2007 Microsoft Corporation
On computer: CAMELOT, 2009.10.13. 22:18:05
DISKSHADOW> delete shadows ID {8d6aa8ef-bb24-4ffc-93da-08831bc4ae88}
Deleting shadow copy {8d6aa8ef-bb24-4ffc-93da-08831bc4ae88}...
1 shadow copy deleted.
DISKSHADOW>
Вот таким образом можно удалять старые теневые копии архивов по одиночке. При удалении теневой копии при следующей операции бэкапа будет обновлён каталог бэкапов. Чтобы удалить все предыдущие архивы кроме текущего внутри diskshadow нужно выполнить:
Delete Shadows Oldest E:
где E: — путь к тому с архивами.
Сами данные из VHD файла будут удалены только при следующей операции бэкапа. Однако, это не относится к архивам, которые содержат SystemState. Для ротации архивов SystemState придётся воспользоваться уже другой утилитой — wbadmin.exe:
wbadmin delete systemstatebackup –version: datetime
где datetime — дата и время выполнения бэкапа. Эту дату можно получить так же из вывода командлета Get-WBBackupSet (свойство VersionID). Чтобы удалить все бэкапы SystemState, кроме текущего следует выполнить:
wbadmin delete systemstatebackup –backuptarget:E: –deleteoldest
и для удаления всех наиболее старых архивов SystemState с сохранением N копий выполнить:
wbadmin delete systemstatebackup –keepversions:N
где N — количество копий SystemState, которые должны быть сохранены.
Исходя из изученного нами материала можно сделать такую вещь: локально хранить стандартный архив с несколькими заданиями бэкапа, а в сетевой папке каждый тип архива отдельно и применять к ним раздельную ротацию. Единственное, что мне пришло на ум — использовать CSV файл для каталогизации теневых копий. Вот как это примерно выглядит:
# подключаем оснастку Server Backup Add-PSSnapin Windows.Serverbackup # создаём задание бэкапа $profiles = New-WBPolicy # создаём и добавляем в задание бэкапа архивируемую папку $source = New-WBFileSpec -FileSpec "D:\Users" Add-WBFileSpec -Policy $profiles -FileSpec $source # указываем локальный том, на который будет копироваться архив $target = New-WBBackupTarget -VolumePath "E:" Add-WBBackupTarget -Policy $profiles -Target $target # выполняем бэкап Start-WBBackup -Policy $profiles # проверяем код возврата с результатом выполнения бэкапа if ((Get-WBSummary).LastBackupResultHR -eq 0) { # переименовываем архив в более понятное имя $newname = "Profiles_$(Get-Date -f dd.MM.yyyy)" Ren E:\WindowsImageBackup -NewName $newname # копируем архив в сетевую папку copy e:\$newname \\server\backups\profiles # удаляем все архивы из сетевой папки, которые старше 7 дней dir \\server\backups\profiles | ?{$_.lastwritetime -lt (Get-Date).AddDays(-7)} | del -Force # читаем наш собственный каталог бэкапов $csv = Import-Csv E:\ProfileBackup.csv # и считаем сколько там записей $count = $csv.count # если записей больше 7, то считаем сколько лишних архивов нужно удалить. # если меньше 7 записей, то ничего удалять не надо и просто добавляем новую запись if ($count -gt 7) { $old = $count - 7 # генерируем случайное имя для скрипта, который будет использоваться в diskshadow $file = [System.IO.Path]::GetRandomFileName() # выбираем все лишние архивы и пропускаем их по конвейеру на удаление $csv | sort | select -First $old | %{ # записываем команду во временный файл "delete shadows ID {$($_.SnapshotID)}" > $Env:TEMP\$file # и запускаем diskshadow в режиме скрипта diskshadow -s $Env:TEMP\$file } del $Env:TEMP\$file } # считываем данные о последнем бэкапе $current = Get-WBBackupSet | select -Last 1 | select VersionID, SnapshotId # и добавляем его в массив объектов действующих бэкапов $csv += $current # чтобы не было путаницы, снова сортируем объекты и пишем обратно в CSV файл $csv | sort | select -Last 7 | Export-Csv E:\ProfileBackup.csv -NoTypeInformation } else { # ругаемся, что бэкап не был завершён успешно }
В принципе, это только один вариант реализации подобной задачи и не обвешена никакими проверками. Однако, учитывая, что данный код публикуется на правах ТЗ (ТЗ — Тайное Знание), поэтому может использоваться как шаблон алгоритма такой кастомной ротации. Данный скрипт только демонстрирует логику, которой вы можете воспользоваться и подпилить под свои условия самостоятельно.
Вот и всё, наверное, что я хотел рассказать про бэкап в Windows Server 2008 R2. В Windows 7 нет командлетов для бэкапа, поэтому свои хотелки придётся реализовывать только средствами CMD (wbadmin, vssadmin, diskshadow). И это будет значительно сложнее, чем вариант с командлетами повершела.