Миграция Active Directory – относительно редкая задача, которая чаще всего возникает в связи с реструктуризацией бизнеса.
В статье будет рассмотрена Cross-forest миграция, как более комплексная задача, разобравшись с которой миграция между доменами одного леса (более распространенный сценарий) не вызовет затруднений.
Переносить будем основные объекты Active Directory – Пользователей, Группы и Компьютеры, кроме того, рассмотрим миграцию почтовых ящиков Exchange.
В своей статье я сделаю акцент на выполнении массовых операций.
Основное условие – со стороны пользователя миграция должна пройти максимально предсказуемо и с минимальными потерями рабочего времени.
Вернувшись за рабочее место пользователь сможет ввести свой существующий логин и пароль и использовать существующей профиль со всеми данными и индивидуальными настройками.
Доступ ко всем корпоративным ресурсам (ERP, Fileshare, Email, Printers) останется без изменений вне зависимости от того «переехали» эти ресурсы в новый лес или остаются в старом.
Единственное что будет изменено – это имя домена, но можно предложить использование в качестве логина Email адреса в новом лесу – это достаточно удобно с точки зрения пользователя который привык вводить личный Email в качестве логина в социальных сетях и т.п.
Лабораторная среда
Для написания этой статьи я развернул такую лабораторную среду:
Server | OS | Software / Roles | IP /28 | RAM |
EDGE | WS 2008R2 SP1 | Exchange 2010 SP3 | 192.168.0.2 | 1 |
DC1 | WS 2008R2 SP1 | AD DS, DNS, AD CS | 192.168.1.2 | 1 |
MAIL1 | WS 2008R2 SP1 | Exchange 2010 SP3 | 192.168.1.3 | 4 |
FS | WS 2008R2 SP1 | Fileserver | 192.168.1.5 | 1 |
WS1 | Windows 7 SP1 | Office 2010 SP2 | 192.168.1.6 | 1 |
WS2 | Windows 7 SP1 | Office 2010 SP2 | 192.168.1.7 | 1 |
WS3 | Windows 8.1 | Office 2013 SP1 | 192.168.1.8 | 1 |
DC2 | WS 2012R2 | AD DS, DNS | 192.168.2.2 | 1 |
MAIL2 | WS 2012R2 | Exchange 2013 SP1 | 192.168.2.3 | 12 |
SRV1 | WS 2012R2 | ADMT 3.2 | 192.168.2.4 | 1 |
SRV2 | WS 2012R2 | SQL 2014 | 192.168.2.5 | 1 |
SRV3 | WS 2008R2 SP1 | PES 3.2 | 192.168.1.4 | 1 |
Как видите, подобную инфраструктуру можно развернуть практически на любом хосте виртуализации с достаточным количеством оперативной памяти и дискового пространства (у меня было занято менее 512Gb).
Я привожу в примере 2008R2 и 2012R2, но материал может быть использован и для других версий Windows Server.
Для эмуляции «содержимого» которое будет переноситься я использовал:
User | Email /UPN | Security Group | Distribution Group | PC |
user1 | [email protected] | sg1 | [email protected] | WS1 |
user2 | [email protected] | sg1 | [email protected] | WS2 |
user3 | [email protected]kagarlickij.com | sg2 | [email protected] | WS3 |
Перед началом миграции у вас должна корректно функционировать сеть и среда виртуализации в которой будет развернут новый лес Active Directory.
Настройка доверительных отношений между лесами
В зависимости от целей и условий миграции двухсторонние доверительные отношения между Source и Target лесами могут быть недопустимы, но в моем случае я считаю их уместными (подробнее о том на каком этапе миграции кто кому должен доверять описано в ADMT Guide).
Для корректного разрешения имен между лесами существует несколько способов, например я буду использовать Conditional Forwarders:
Не важно какой способ выберете вы, но в результате имена должны разрешаться корректно:
Когда это условие выполнено, на Source DC откроем mmc Active Directory Domains and Trusts, запустим мастер в котором создадим и подтвердим такую конфигурацию:
Я не описываю процесс нажимания Next => Next => OK, если у вас это вызывает затруднения – и Сети достаточное количество материалов по пошаговой настройке доверительных отношений.
После завершения работы Мастера убедимся, что в Target DC также был создан траст:
DNS Suffix
Предполагая, что пользователи будут (временно или постоянно) использовать ресурсы обеих лесов и зная что использование srvshare вызывает меньше проблем чем srv.dom2.locshare создадим и применим такие групповые политики в Source и Target лесах соответственно:
В результате применения политики суффиксы будут выглядеть так (для Source domain):
Проверить результат достаточно просто – я запрошу резолв привычного пользователям ресурса который находится в Source из Target леса:
PKI Trust
Предположим, что в новом лесу развернут новая PKI, а в PKI из старого леса вносить изменения не будем.
Пользователи доверяют сертификатам того ЦС, с которым находятся в одном лесу, но не ЦС доверенного леса, в котором находится часть ресурсов, которые они используют.
Для решения этой ситуации создадим и применим такие групповые политики в Source и Target лесах соответственно:
В результате, ресурсы доверенных лесов будут выглядеть корректно (разумеется, точки распространения CRL должны быть доступны):
Builtin объекты
Builtin объекты везде имеют одинаковые SIDы, поэтому при миграции с помощью ADMT мигрированы они не будут.
Нужно проверить членство в этих группах, и членство этих объектов в других группах (вообще, рекомендую Builtin использовать как можно меньше).
Учетная запись для ADMT
ADMT будет установлена в Target лесу, соответственно учетную запись для нее создадим тоже там.
В ADMT Guide есть детальное описание необходимых прав для этой учетной записи на разных этапах миграции, но я не буду кривляться и включу ее в группу Domain admins в Target forest и в группу Administrators в Source Domain:
При миграции объектов типа Computer у учетной записи ADMT должны быть права локального администратора на этих объектах. Для этого будем использовать групповую политику, которой добавим группу DOM2Domain admins (в которую входит учетная запись dom2ADMT) в группу Локальных администраторов на компьютерах Source леса:
Проверим результат на рабочей станции:
Правила Брандмауэра
Для работы агента ADMT создадим исключения в брандмауэре с помощью групповой политики в обеих лесах (я разрешил доступ только с сервера на котором будет работать ADMT):
Active Directory Audit, SID Filter & SID history
Миграция атрибута SID history является ключевым моментом, ведь именно благодаря SID history пользователь находясь в Target лесу сможет получать доступ к ресурсам которые остались в Source лесу.
Обратите внимание, у пользователя может быть несколько SID в SID history.
Первым делом создадим и применим политики аудита в Source и Target лесах:
Затем выключим фильтрацию SID в обеих доменах:
1 |
netdom trust TrustingDomainName /domain: TrustedDomainName /quarantine:no |
… и включим SID history также в обеих доменах:
1 |
netdom trust TrustingDomainName /domain: TrustedDomainName /enableSIDhistory:yes |
Вот как это выглядит на dc1.dom1.loc и dc2.dom2.loc соответственно:
Обратите внимание: у учетной записи ADMT должны быть права «Migrate SID History» в Source domain. Т.к. я включил учетную запись в группу Administrators эти права есть, но если назначать права вручную, то делать это удобно с помощью Delegation Control Wizard:
Если на Компьютерах которые планируется мигрировать есть ОС Windows Vista и ниже, необходимо ключу HKLMSystemCurrentControlSetServicesNetlogonParametersAllowNT4Crypto задать значение 1 в Source и Target доменах на КД, которые являются эмулятором PDC.
Миграция OU
При миграции содержимого Active Directory в новый лес часто будет уместно создать новую структуру OU, но если нужно перенести существующую, то сделать это можно так:
Сделаем экспорт:
1 |
ldifde -f c:sourceOUs.ldf -r "(objectClass=organizationalUnit)" –l objectClass,distinguishedName,ou,description |
В полученном файле заменим DC=dom1,DC=loc на DC=dom2,DC=loc и удалим те OU, которые уже есть в Target домене:
Скопируем отредактированный файл на Target контроллер домена и импортируем его:
1 |
ldifde -i -f c:sourceOUs.ldf |
Проверим результат:
1 |
Get-ADOrganizationalUnit -Filter 'Name -like "*"' | FT Name, DistinguishedName -A |
Миграция GPO
Вопрос миграции GPO похож на вопрос миграции OU: во многих случаях будет уместно создать новые политики, а не переносить треш.
Но если нужно перенести политики, делается это так:
Сделаем backup политики из Source domain и скопируем его в Target domain:
В Target domain создадим пустую политику и запустим Import settings (мастер предложит сделать backup существующей политики, но смысла в этом нет т.к. она пустая).
Обратите внимание, при импорте нужно указывать папку в которой находится backup (например C:shareGPimport) а не папку с backup (например C:shareGPimport{%uid%})
После импорта, политику можно произвольным образом переименовать, убедиться, что все опции были перенесены корректно и применить ее к соответствующим OU в Target domain:
Установка ADMT
Т.к. я описываю сценарий, в котором буду использовать массовые операции через CLI, утилита ADMT должна быть установлена в Target domain на контроллере домена (я буду использовать временный контроллер домена srv1.dom2.loc, который будет выведен из эксплуатации после окончания миграции).
В этом случае лучше использовать выделенный SQL сервер на выделенном сервере – я буду использовать srv2.dom2.loc
Утилиту PES (ADMT password migration DLL) установим на контроллере домена (который тоже будет выведен из эксплуатации после окончания миграции) в Source domain, в моем случае это сервер srv3.dom1.loc
Установим SQL Server на srv2.dom2.loc в такой конфигурации:
После установки необходимо опубликовать SQL в локальном брандмауэре srv2.dom2.loc разрешив доступ к sqlservr.exe , открыв порты tcp 80, 135, 139, 443, 445, 1433, 63419 (порт инстанса который будет использовать ADMT) и udp 123, 137, 138, 445, 1025, 1434.
На сервере srv1.dom2.loc скачаем версию ADMT 3.2 с Microsoft Connect (версия в Download Center 2010 года и несовместима с новыми версиями Windows Server и SQL).
Установим ADMT указав имя сервера БД и инстанс для ADMT:
Создадим ключ для PES:
1 |
ADMT Key /Option:Create /SourceDomain:dom1.loc /KeyFile:C:key.pes /KeyPassword:P@$$w0rd |
Скачаем PES x64 c Microsoft Connect и установим его на сервер srv3.dom1.loc который, напомню, является временным контроллером домена в Source лесу.
В процессе установки, укажем путь и пароль к созданному ранее ключу, а в качестве учётной записи для службы будем использовать DOM2admt
После перезагрузки, откроем оснастку services.msc и запустим службу Password Export Server Service
После окончания (или во время перерыва) миграции из соображений безопасности службу рекомендуется останавливать.
Обратите внимание, при миграции будут скопированы и установлены даже те пароли, которые не соответствуют политике Target домена (например, пустые или «123»).
Для проверки возможности миграции SID в Target domain создадим пустую Domain local группу %NETBIOSNAME%$$$.
Опции по-умолчанию
В ходе работ я буду показывать как создать свои конфигурации процессов миграции, и если не указать какую-то опцию в явном виде она получит параметр который указан в C:WindowsADMTtemplatescript.vbs
Разумеется, этот файл можно редактировать по всякому, но лучше для каждого процесса использовать свой Option file, что я и буду делать.
Обратите внимание, синтаксис для Option File, Script и логов отличается.
Управление исключаемыми атрибутами ADMT
По-умолчанию ADMT исключает набор атрибутов во время миграции, посмотреть этот список можно создав и выполнив скрипт DisplayExclusionList.vbs :
1 2 |
Set o = CreateObject("ADMT.Migration") WScript.Echo o.SystemPropertiesToExclude |
1 |
.cscript.exe C:DisplayExclusionList.vbs |
Чтобы изменить список исключаемых атрибутов можно использовать скрипт ExclusionList.vbs :
1 2 |
Set o = CreateObject("ADMT.Migration") o.SystemPropertiesToExclude = "<Attribute1>,<Attribute2>,<Attribute3>" |
1 |
.cscript.exe C:ExclusionList.vbs |
В примере я добавил в исключения созданный мною атрибут “aaa” :
Если версия схемы в Source и Target разная (у меня, например, в Source установлен Exchange 2010, а в Target Exchange 2013) в каждом логе будут присутствовать дополнительно исключенные атрибуты (на скриншете видно примерно четверть) и Unspecified error 0x80004005 :
Отключение требования смены пароля при следующем входе Пользователя
После миграции Пользователя атрибут pwdLastSet будет присвоено значение 0 , а это значит что при первом входе пользователю будет предложено сменить пароль.
С точки зрения безопасности — это разумный шаг, но с точки зрения процесса миграции будет лучше если пользователи сменят пароли спустя какое-то время.
В любом случае решить этот вопрос стоит на этапе подготовки, а не в процессе миграции.
Т.к. пользователей мы будем переносить массово по OU, наиболее удобный способ отключить требование смены пароля при следующем входе таков:
1 |
Get-ADUser -Filter * -SearchBase "%OU Path%" | Set-ADUser -ChangePasswordAtLogon $False |
Включение Exchange MRS Proxy
На Source Exchange сервере включим Exchange MRS Proxy:
1 |
Set-WebServicesVirtualDirectory -Identity "EWS (Default Web Site)" -MRSProxyEnabled $true |
Уменьшить таймаут можно указав в файе C:Program FIlesMicrosoftExchange Serverv14ClientAccessexchwebewsweb.config значение DataImportTimeout 00:10:00 (что равно 10 минутам) и выполнив iisreset.
Подробнее о MRS Proxy – http://technet.microsoft.com/en-us/library/ee732395.aspx
Выгрузка Групп, Пользователей и Компьютеров в .csv
Для массовой миграции объектов Active Directory ADMT использует так называемые include files в которых указаны списки того, что будет мигрироваться – «пачки» Групп, Пользователей и Компьютеров.
Для каждого типа объектов необходим свой include file.
ADMT умеет работать с форматами .txt и .csv, и т.к. последний удобнее в обработке, использовать будем именно его.
Как я уже говорил, я буду мигрировать «пачки» по OU, из Source AD их можно выгрузить так:
1 |
Get-ADGroup -Filter {ObjectClass -eq "Group"} -SearchBase "%OU path%" | select sAMAccountName | Export-csv C:%filename%.csv |
1 |
Get-ADUser -Filter {ObjectClass -eq "User"} -SearchBase "%OU path%" | select sAMAccountName | Export-csv C:%filename%.csv |
1 |
Get-ADComputer -Filter {ObjectClass -eq "Computer"} -SearchBase "%OU path%" | select sAMAccountName | Export-csv C:%filename%.csv |
Результат выглядит следующим образом (обратите внимание, к объектам типа “Computer” знак $ добавляется автоматически):
Если Вы хотите переименовать пользователей при миграции можно создать расширенный Include file в формате .csv или .txt:
Перед началом миграции вы должны сделать экспорт всех объектов которые будут мигрированы, и поделить на «пачки» согласно плану миграции.
Эти объекты крайне желательно не изменять на всем протяжении миграции.
Миграция одной Группы
Я не буду подробно описывать каждый шаг мастера ADMT т.к. это достаточно подробно описано в ADMT Guide, так что перейдем к практике.
В первую очередь мигрируем группу DOM1$$$ с миграцией SID
…и убедимся, что все прошло штатно:
В Target domain был успешно создан новый объект и записан атрибут SID history:
Массовая миграция Групп
В первую очередь нужно мигрировать все Группы потому что при миграции Пользователей и Компьютеров их членство в группах должно быть восстановлено автоматически.
Обратите внимание, после окончания миграции Пользователей и Компьютеров необходимо будет выполнить повторную миграцию Групп в режиме Merge + Fix members of Group для того чтобы полностью убедится в том, что членство в группах в Target = членству в группах Source.
Итак, запустим Мастер миграции Групп и будем использовать список групп из Include файла, а не указывать группы вручную:
Укажем путь к Include файлу и OU в Source domain в котором находятся группы из Include файла.
Обратите внимание, первые строчки .csv не являются группами, поэтому будут исключены.
Вместе с группой не будем переносить Пользователей которые в нее входят, но SID запишем в SID history обязательно т.к. это позволит членам группы получать доступ к ресурсам которые доступны одноименной группе в Source лесу.
Если случилось так, что группа была создана в Target лесу до начала миграции, за счет опции Merge будет выполнено слияние (фактически, в существующую группу будут дописаны атрибуты и изменено членство):
После окончания каждого этапа миграции рекомендую внимательно просматривать лог операции. Как видите, в моем случае имеется предупреждение, связанное с адресной книгой Exchange.
Но на данном этапе главное, что группы были созданы в target домене и атрибут SID history записан.
Если мигрировать большое количество «пачек» вручную с помощью Мастера имеет место риск человеческой ошибки, чтобы его минимизировать можно использовать Option file такого содержания:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[Migration] IntraForest=No SourceDomain=dom1.loc SourceOu=Branch office/Groups TargetDomain=dom2.loc TargetOu=Branch office/Groups ConflictOptions=Merge [Group] MigrateSIDs=Yes UpdatePreviouslyMigratedObjects=No FixGroupMembership=No UpdateGroupRights=No MigrateMembers=No |
Обратите внимание на синтаксис параметра TargetOu – этот параметр нужно будет менять при миграции новой “пачки” если она мигрируется в другой OU.
Для запуска миграции с использованием Option и Include файлов выполним:
1 |
ADMT GROUP /O: "C:MigrationOptFlGroup.txt" /F: "C:MigrationBranchOfficeGroups.csv" |
Убедимся что миграция прошла предсказуемо:
Миграция одного Пользователя
Т.к. я выполняю Cross-forest сценарий, будет выполнена не миграция, а создание аналогичных объектов типа «Пользователь» в Target domain.
Это необходимо для того, чтобы сервисы которые будут мигрироваться, до окончательной миграции пользователей могли работать уже с новыми аккаунтами. Например, Exchange создаст этим пользователям почтовые ящики и в них будет поступать свежая почта в то время пока будет выполняться копирования содержимого ящика из Source организации.
В случае миграции Active Directory совместно с Exchange командлет Prepare–MoveRequest создаст пользователей в Target domain, так что этот этап можно было бы пропустить, но я покажу как выполнить «первичную» миграцию пользователей на случай если вы мигрируете только AD .
Будем мигрировать пользователя user1 в аналогичный OU в Target domain:
На этом этапе будем генерировать новые пароли для Пользователей, а при повторной миграции скопируем пароли из Source домена:
Аккаунты в Source оставим включенными, а в Target выключенными. SIDы мигрировать не будем.
Возобновим членство в группах т.к. Группы у нас уже смигрированны.
В Target домене еще нету Пользователей из Source домена, так что опция Merge будет сугубо номинальной.
Результат миграции посмотрим в логе:
Отключим необходимость смены пароля пользователем при следующем входе:
1 |
Get-ADUser -Identity $%username%| Set-ADUser -ChangePasswordAtLogon:$false |
Проверим членство в группах:
1 |
Get-ADPrincipalGroupMembership %username% | select name |
Проверим SID history:
1 |
Get-ADUser %user% -Property sIDHistory |
Массовая миграция Пользователей
Для массовой миграции Пользователей будем использовать Option file :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
[Migration] IntraForest=No SourceDomain=dom1.loc TargetDomain=dom2.loc TargetOu=Branch office/Users PasswordOption=Complex PasswordServer="" ConflictOptions=Merge [User] DisableOption=DisableTarget SourceExpiration=None MigrateSIDs=No TranslateRoamingProfile=No UpdateUserRights=Yes MigrateGroups=No UpdatePreviouslyMigratedObjects=No FixGroupMembership=Yes MigrateServiceAccounts=No UpdateGroupRights=No |
Пример выполнения массовой миграции (которая у меня состояла из user2 и user3):
1 |
ADMT USER /O: "C:MigrationOptFlUsers.txt" /F: " C:MigrationBranchOfficeUsers.csv" |
Миграция одного почтового ящика Exchange
Откроем Exchange Management Shell в Target domain и введём учётные данные Target domain:
1 |
$localCredentials = Get-Credential |
.. и учетные данные Source Domain:
1 |
$RemoteCredentials = Get-Credential |
Перейдём в папку со скриптами Exchange:
1 |
cd "C:Program FilesMicrosoftExchange ServerV15Scripts" |
Выполним подготовку:
1 |
.Prepare-MoveRequest.Ps1 -Identity %EmailAddress% -RemoteForestDomainController %FQDN of Source DC% -RemoteForestCredential $RemoteCredentials -LocalForestDomainController %FQDN of Target Forest DC% -LocalForestCredential $LocalCredentials -TargetMailUserOU “Distinguished name of OU in TargetForest” –UseLocalObject -Verbose |
В Target AD будет создан новый Пользователь в состоянии Disable:
В дальнейшем, когда будем выполнить повторную миграцию пользователей в режиме Merge этот Пользователь будет дополнен атрибутами, включен в группы и ему будет установлен пароль такой же, как в Source домене.
Выполним миграцию почтового ящика для созданного пользователя:
1 |
New-MoveRequest –Identity '%EmailAddress%' –Remote –Remotehostname ‘%Source Exchange FQDN%’ -RemoteCredential $RemoteCredentials –TargetDeliverydomain '%mail domain%' |
Выглядит миграция ящика Exchange как на скриншете ниже и занимает определенное время, в зависимости от размера ящика:
При следующем запуске Outlook запросит перезапуск:
.. И после этого будет работать с новым сервером:
Массовая миграция почтовых ящиков
Массовая миграция почтовых ящиков так же проста, как и миграция одного почтового ящика, но в файле со списком пользователей нужно заменить заголовок sAMAccountName на Identity
Затем укажем «Include файл» (у меня он состоит из единственного ящика [email protected]) в уже знакомые командлеты:
1 |
Import-Csv C:%file with users%.csv | .Prepare-MoveRequest.Ps1 -RemoteForestDomainController %FQDN of Source DC% -RemoteForestCredential $RemoteCredentials -LocalForestDomainController %FQDN of Target Forest DC% -LocalForestCredential $LocalCredentials -TargetMailUserOU “Distinguished name of OU in TargetForest” –UseLocalObject -Verbose |
1 |
Import-Csv C:%file with users%.csv | New-MoveRequest –Remote –Remotehostname ‘%Source Exchange FQDN%’ -RemoteCredential $RemoteCredentials –TargetDeliverydomain '%mail domain%' |
Миграция одного Компьютера и Профиля
Миграция Компьютеров и Профилей технически сложнее и, соответственно, вызывает больше проблем чем миграция Пользователей.
На момент запуска миграции компьютеры должны быть подключены в сеть, но пользователи должны сделать log off для того чтобы не было заблокированных пользователями файлов.
После миграции компьютер будет автоматически перезагружен.
Обратите внимание, после миграции в Target domain у Компьютера в группе локальных администраторов останутся только локальные учетные записи, поэтому нужно заранее применить на OU в который будет выполняться миграция, политику которая будет использовать Restricted Groups:
Запустим Мастер миграции Компьютеров (опции, которые я устанавливал будет видно в логе).
Больше всего проблем вызывает миграция Профилей, бывает так что даже в лабораторной среде она проходит успешно только со второго раза (компьютер придется вручную переводить в Source domain и также вручную удалять созданные профили %user%.%Target Domain NetBIOS name%)
Вот как выглядит результат успешной миграции Компьютера и Профиля:
Лог миграции Компьютера:
Лог агента переноса Профилей:
Еще раз заостряю ваше внимание – миграция профилей может быть большой проблемой с низкой предсказуемостью, так что предусмотрите достаточное количество времени на этот этап.
При корректном переносе профиля пользователя сохраняются файлы, папки, настройки рабочего стола, браузера, подключенные принтеры и т.п.:
Проверим logon server
1 |
[System.Environment]::GetEnvironmentVariable("logonserver") |
… и канал между Компьютером и доменом:
1 |
Test-ComputerSecureChannel |
Но если случилось так, что после миграции Компьютера и Профиля пользователь при логине получает новый, «пустой» профиль быстро исправить ситуацию можно так:
- Логинимся на ПК с правами Администратора
- Открываем Regedit и переходим в HKEY_LOCAL_MACHINESoftwareMicrosoftWindows NTCurrentVersionProfileList
- Выбираем SID пользователя из Target домена и в параметре ProfileImagePath меняем C:Users%username%.%Target domain NetBIOS name% на C:Users%username%
- Профиль C:Users%username%.%Target domain NetBIOS name% можно удалить.
- Пользователю из Source домена в профиле можно ничего не менять т.к. в процессе миграции он будет заблокирован.
- Перезагружаем Компьютер и у Пользователя при входе в Target domain будет тот же профиль который был в Source домене.
Массовая миграция Компьютеров и Профилей
Массовая миграция Компьютеров и Профилей практически аналогична массовой миграции Групп, которая была рассмотрена ранее, но из Include file нужно убрать знак $ в именах компьютеров.
Если «пачек» с компьютерами много, можно использовать Option файл такого содержания:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
[Migration] IntraForest=No SourceDomain=dom1.loc SourceOu=Branch office/Computers TargetDomain=dom2.loc TargetOu=Branch office/Computers ConflictOptions=Merge [Computer] UpdateAllManagedServiceAccounts=No RestartDelay=0 TranslateOption=Replace TranslateFilesAndFolders=Yes TranslateLocalGroups=Yes TranslatePrinters=Yes TranslateRegistry=Yes TranslateShares=Yes TranslateUserProfiles=Yes TranslateUserRights=Yes |
Еще раз обратите внимание на синтаксис параметра TargetOu – этот параметр нужно будет менять при миграции новой “пачки” если она мигрируется в другой OU.
Вот пример использования Option и Include файлов:
1 |
ADMT COMPUTER /O: C:MigrationOptFlComputer.txt /F: C:MigrationBranchOfficeComputers.csv |
Теперь массово смигрируем профили:
1 2 3 4 5 6 7 8 |
[Migration] IntraForest=No SourceDomain=dom1.loc TargetDomain=dom2.loc [Security] TranslateOption=Replace TranslateUserProfiles=Yes TranslateUserRights=Yes |
1 |
ADMT SECURITY /O: C:MigrationOptFlProfile.txt /F: C:MigrationBranchOfficeComputers.csv |
Повторная массовая миграция Пользователей
В процессе повторной миграции Пользователей будет перенесен пароль, отключен Source и включен Target аккаунт, записан атрибут SID history, и еще раз восстановлено членство в группах.
Обратите внимание, будут скопированы и установлены даже те пароли, которые не соответствуют политике Target домена (например, пустые или «123»).
Пример Option file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
[Migration] IntraForest=No SourceDomain=dom1.loc TargetDomain=dom2.loc TargetOu=Branch office/Users PasswordOption=Copy PasswordServer="srv3.dom1.loc" ConflictOptions=Merge [User] DisableOption=EnableTarget SourceExpiration=0 MigrateSIDs=Yes TranslateRoamingProfile=No UpdateUserRights=Yes MigrateGroups=No UpdatePreviouslyMigratedObjects=Yes FixGroupMembership=Yes MigrateServiceAccounts=No UpdateGroupRights=No |
…и пример выполнения повторной миграции
1 |
ADMT USER /O: "C:MigrationOptFlUsers.txt" /F: "C:MigrationBranchOfficeUsers.csv" |
Теперь, когда Пользователи полностью смигрированы и включены убедимся, что содержимое почтовых ящиков перенесено, и доставка почты работает корректно (я не описываю миграцию адресных книг Exchange с помощью FIM и настройку EDGE подписок я опишу в отдельной статье):
После того, как будет выполнена миграция всех «пачек» Пользователей и Компьютеров желательно выполнить повторную миграцию Групп в режиме Merge + Fix members of Group для того чтобы полностью убедится в том, что членство в группах в Target = членству в группах Source.
Вообще, во время миграции не следует производить какие-либо операции в AD, а особенно изменять членство в группах.
Но предположим Пользователя который находится в Source домене добавили в группу которая находится в Target домене а спустя некоторое время смигрировали пользователя из Source в Target domain.
В этом случае его членство в группе Target домена будет утеряно. Чтобы решить эту проблему, можно использовать
[spoiler title=’Скрипт’ collapse_link=’true’]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
# # Версия 12.09.2012 для публичного доступа # PowerShell Script для "Fix Group Membership" для мигрировавших пользователей, имеющих "хвост" в Foreign Security Principals. # Этот случай возникает, когда пользователь из исходного лесадомена добавляется в группу в целевом лесудомене, а затем сам мигрирует в целевой лесдомен и ему необходимо восстановить членство в группе в целевом лесу. # 2012, И.Мамышев # Использование: <scriptname> | <scriptname> YES # Параметр YES разрешает скрипту выполнять изменения в Active Directory. По-умолчанию скрипт выполняется в режиме только чтения. # # Скрипт производит поиск "пересечения" Users и Foreign Security Principals (FSP) в домене, затем определяет членство в группах для отобранных FSP и интерактивно предлагает администратору добавить пользователей (из списка "пересечения") домена в эти группы безопасности. # # Скрипт использует командлеты для работы с Active Directory из состава операционной системы Windows Server 2008 R2 (тестировалось только на Windows Server 2008 R2). # ### $ErrorActionPreference = "SilentlyContinue" ######################################################### # === !!! НИЖЕ ДАННОЙ СТРОКИ СКРИПТ НЕ ИСПРАВЛЯТЬ !!! === ######################################################### $Argument0 = $args[0] $Log1 = @(); # Migrated ADUsers $Log2 = @(); # ADUsers, которым требуется FixGroupMembership и соответствующие Security Group $Log2_ = @(); $Log3 = @(); # Users и Groups, в которые был добавлен User $Count1 = 1; $Count2 = 1; $Count2_ = 1; $Count3 = 1; $dt = Get-Date -Format "dd.MM.yyyy HH:mm:ss" $dt2 = Get-Date -Format "dd.MM.yyyy HH.mm.ss" $CurrentDir = $MyInvocation.MyCommand.Definition | split-path -parent # Каталог, в котором размещён файл исполняемого в данный момент скрипта. New-Item ($CurrentDir + "log") -type Directory -Force > $null $LogFile = $CurrentDir + "log" + $MyInvocation.MyCommand.Name + "_$dt2.html" # Имя файла журнала $Wellcome = "И.Мамышев, 2012 " + $MyInvocation.MyCommand.Name ######################################################### # Импорт необходимых модулей и добавление необходимых остасток Import-Module ActiveDirectory Clear-Host ## Write-Host $Wellcome -ForegroundColor Green $Wellcome >> $LogFile Write-Host '"Fix Group Membership" для мигрировавших пользователей, имеющих "хвост" в Foreign Security Principals' -ForegroundColor Green if ($Argument0 -ne "YES") { Write-Host 'ВНИМАНИЕ! Параметр YES не указан.' -ForegroundColor Yellow Write-Host "Скрипт выполняется в режиме только чтения." -ForegroundColor Yellow } Write-Host "" ## Определение имени контроллера домена в Целевом лесу (для будущего использования ниже) $DestinationForestPDC = (Get-ADDomain -Current LoggedOnUser -ErrorAction $ErrorActionPreference).PDCEmulator # Получаем список объектов класса "user" $ADUsers = @(Get-ADObject -Filter {objectClass -eq "user" -and objectClass -ne "computer"} -Properties * -SearchScope Subtree) # Перебираем ADUsers, чьи SIDHistory есть у ForeignSecurityPrincipals в качестве Name $ADUsers | ForEach-Object -Begin { if($ADUsers.Count -le 0) {$Cnt=1}else{$Cnt=$ADUsers.Count}; $percent=$Cnt/100; $c = 0; #"Будет обработано $Cnt записей" } -Process { $c++; [int]$a = $c/$percent; Write-Progress -Activity "Идет поиск Foreign Security Principals по SIDHistory мигрировавших ADUsers..." -PercentComplete $a -CurrentOperation "$a% завершено" -Status "Пожалуйста подождите."; #Счетчик - процент выполнения if ($_.sIDHistory.Value.Value) { # Если атрибут sIDHistory заполнен, то пытаемся найти его "хвост" в Foreign Security Principals $FSP = Get-AdObject -Filter { (objectClass -eq "foreignSecurityPrincipal") -and (Name -eq $_.sIDHistory.Value.Value) } -Properties * -ErrorAction $ErrorActionPreference if ($FSP) { $Log1 += (new-object psobject | add-member noteproperty "№" "$Count1" -passthru | add-member noteproperty "sAMAccountName" $_.sAMAccountName -passthru | add-member noteproperty "DisplayName" $_.DisplayName -passthru | add-member noteproperty "ObjectSID" $_.ObjectSID.Value -passthru | add-member noteproperty "sIDHistory" $_.sIDHistory.Value -passthru | add-member noteproperty "FSPObjectGUID" $FSP.ObjectGUID -passthru | add-member noteproperty "CreateTimeStamp" $_.CreateTimeStamp -passthru) $Count1++ } } } # Готовим текст в лог if ($Log1.Length -gt 0) { $Text += "<br>---------------------------------------------------------------------</br>" $Text += "<b><font color=black>Всего записей типа Foreign Security Principal: " + $ForeignSecurityPrincipals.Count + "</font></b></br>" $Text += "<b><font color=red>Из них пользователей леса " + (Get-ADDomain -Current LoggedOnUser).Forest + ", ВОЗМОЖНО требующих Fix Group Membership:</font></b>" $Text += ($Log1 | ConvertTo-Html) $Text >> $LogFile } "Выполнено: "+(Get-Date -Format "dd.MM.yyyy HH:mm:ss") >> $LogFile Write-Progress -Activity "Working..." -Completed -Status "All done." #Отображение индикатора выполнения завершим. Пропадет с экрана. #================================================================================== # Перебираем $Log1 и получаем список Security Group, в которых необходимо выполнить Fix Group Membership $Log1 | ForEach-Object -Begin { if($Log1.Count -le 0) {$Cnt=1}else{$Cnt=$Log1.Count}; $percent=$Cnt/100; $c = 0; #"Будет обработано $Cnt записей" } -Process { $c++; [int]$a = $c/$percent; Write-Progress -Activity "Идет получение списка Security Group for ForeignSecurityPrincipals=ADUsers..." -PercentComplete $a -CurrentOperation "$a% завершено" -Status "Пожалуйста подождите."; #Счетчик - процент выполнения # Получаем список групп, членом которых является FSP $SID = $_.sIDHistory $FSP = Get-ADObject -Filter {cn -eq $SID} -Properties * -ErrorAction $ErrorActionPreference $ADUserGroupForFixGroupMembership = @($FSP.MemberOf) if ($ADUserGroupForFixGroupMembership.Count -ne 0) { $ADUsersAMAccountName = $_.sAMAccountName $ADUserSID = $_.ObjectSID $ADUserCreateTimeStamp = $_.CreateTimeStamp $ADUserGroupForFixGroupMembership | ForEach-Object { # Перечисляем группы, т.к. FSP можеь быть членом нуля, одной или нескольких групп # Получим и сохраним SID группы (MemberOf возвращает значения в формате DN) $groupSID = (Get-ADObject -Filter {distinguishedName -eq $_} -Properties * -ErrorAction $ErrorActionPreference).ObjectSID.Value; $groupSamAccountName = (Get-ADObject -Filter {distinguishedName -eq $_} -Properties * -ErrorAction $ErrorActionPreference).SamAccountName; # Проверяем, не является ли уже ADUser членом группы $IsMember = $False; (Get-ADObject -Filter {objectSID -eq $ADUserSID} -Properties * -ErrorAction $ErrorActionPreference).MemberOf | ForEach-Object { if ($groupSID -eq (Get-ADObject -Filter {distinguishedName -eq $_} -Properties * -ErrorAction $ErrorActionPreference).ObjectSID.Value) { # Если есть совпадение, то выставляем флаг $IsMember = $True; } } if ($IsMember -eq $False) { # Включаем пользователя в список нуждающихся $Log2 += (new-object psobject | add-member noteproperty "№" "$Count2" -passthru | add-member noteproperty "ADUsersAMAccountName" $ADUsersAMAccountName -passthru | add-member noteproperty "ADUserSID" $ADUserSID -passthru | add-member noteproperty "ADUserGroupSamAccountName" $groupSamAccountName -passthru | add-member noteproperty "ADUserGroupSID" $groupSID -passthru | add-member noteproperty "ADUserCreateTimeStamp" $ADUserCreateTimeStamp -passthru) $Count2++ } } } } # Готовим текст в лог if ($Log2.Length -gt 0) { $Text2 += "<br>---------------------------------------------------------------------</br>" $Text2 += "<b><font color=green>Список пользователей лесадомена Active Directory " + (Get-ADDomain -Current LoggedOnUser).Forest + ", которым требуется Fix Group Membership в перечисленных Security Group:</font></b>" $Text2 += ($Log2 | ConvertTo-Html) $Text2 >> $LogFile } "Выполнено: "+(Get-Date -Format "dd.MM.yyyy HH:mm:ss") >> $LogFile Write-Progress -Activity "Working..." -Completed -Status "All done." #Отображение индикатора выполнения завершим. Пропадет с экрана. #================================================================================== IF ($Log2.Count -ne 0) { # Если список не пуст, то выполняем Write-Host "Список пользователей лесадомена Active Directory " (Get-ADDomain -Current LoggedOnUser).Forest ", которым требуется Fix Group Membership в перечисленных Security Group:" -ForegroundColor Yellow $Log2 = ($Log2 | Sort-Object -Property ADUserCreateTimeStamp -Descending) $Log2 | ft ADUsersAMAccountName, ADUserGroupSamAccountName, ADUserCreateTimeStamp -autosize # Перебираем $Log2 и интерактивно спрашиваем Администратора по каждому пользователю - нужно ли его добавлять в соответствующую группу Write-Host "Будет задано" $Log2.Count "вопросов." -ForegroundColor Green $Log2 | ForEach-Object { if ($PrevInput -eq "A") { # Если введено "A", то остальные все записи добавляем без вопросов $Log2_ += (new-object psobject | add-member noteproperty "№" "$Count2_" -passthru | add-member noteproperty "ADUsersAMAccountName" $_.ADUsersAMAccountName -passthru | add-member noteproperty "ADUserSID" $_.ADUserSID -passthru | add-member noteproperty "ADUserGroupSamAccountName" $_.ADUserGroupSamAccountName -passthru | add-member noteproperty "ADUserGroupSID" $_.ADUserGroupSID -passthru) $Count2_++ Return } # Если введено "R", то остальные все записи пропускаем if ($PrevInput -eq "R") {Return;} Write-Host "Добавить " -NoNewLine Write-Host $_.ADUsersAMAccountName -NoNewLine -ForegroundColor Yellow Write-Host " в группу " -NoNewLine Write-Host $_.ADUserGroupSamAccountName -NoNewLine -ForegroundColor Yellow Write-Host "?" -NoNewLine $Input = Read-Host (" [Y - Yes, A - Yes for All, N - No, R - No for All]") While (!($Input -eq "Y" -or $Input -eq "A" -or $Input -eq "N" -or $Input -eq "R")) { Write-Host "Данные введены некорректно. Повторите ввод." -ForegroundColor Red $Input = Read-Host } $PrevInput = $Input if ($Input -eq "Y" -or $Input -eq "A") { $Log2_ += (new-object psobject | add-member noteproperty "№" "$Count2_" -passthru | add-member noteproperty "ADUsersAMAccountName" $_.ADUsersAMAccountName -passthru | add-member noteproperty "ADUserSID" $_.ADUserSID -passthru | add-member noteproperty "ADUserGroupSamAccountName" $_.ADUserGroupSamAccountName -passthru | add-member noteproperty "ADUserGroupSID" $_.ADUserGroupSID -passthru) $Count2_++ } } # Готовим текст в лог if ($Log2_.Length -gt 0) { $Text2_ += "<br>---------------------------------------------------------------------</br>" $Text2_ += "<b><font color=orange>Список пользователей лесадомена Active Directory " + (Get-ADDomain -Current LoggedOnUser).Forest + ", которым Администратор разрешил выполнить Fix Group Membership в соответствующих Security Group:</font></b>" $Text2_ += ($Log2_ | ConvertTo-Html) $Text2_ >> $LogFile } "Выполнено: "+(Get-Date -Format "dd.MM.yyyy HH:mm:ss") >> $LogFile $Log2 = $Log2_ #================================================================================== # Перебираем $Log2 и добавляем ADUsers в Secirity Groups, согласно данных из таблицы $Log2 $Log2 | ForEach-Object -Begin { if($Log2.Count -le 0) {$Cnt=1}else{$Cnt=$Log2.Count}; $percent=$Cnt/100; $c = 0; #"Будет обработано $Cnt записей" } -Process { $c++; [int]$a = $c/$percent; Write-Progress -Activity "Идет добавление ADUsers в Security Group..." -PercentComplete $a -CurrentOperation "$a% завершено" -Status "Пожалуйста подождите."; #Счетчик - процент выполнения if ($Argument0 -eq "YES") { # Если в качетсве параметра в скрипт передано слово "YES", то выполняем командлет в "боевом" режиме $Result = Add-ADGroupMember -Identity $_.ADUserGroupSID -Members $_.ADUserSID } else { $Result = Add-ADGroupMember -Identity $_.ADUserGroupSID -Members $_.ADUserSID -WhatIf } if ($true) { # Если добавление произведено успешно, то добавляем запись в отчёт $Log3 += (new-object psobject | add-member noteproperty "№" "$Count3" -passthru | add-member noteproperty "ADUsersAMAccountName" $_.ADUsersAMAccountName -passthru | add-member noteproperty "ADUserSID" $_.ADUserSID -passthru | add-member noteproperty "ADUserGroupSamAccountName" $_.ADUserGroupSamAccountName -passthru | add-member noteproperty "ADUserGroupSID" $_.ADUserGroupSID -passthru) $Count3++ } } # Готовим текст в лог if ($Log3.Length -gt 0) { $Text3 += "<br>---------------------------------------------------------------------</br>" $Text3 += "<b><font color=blue>Список пользователей лесадомена Active Directory " + (Get-ADDomain -Current LoggedOnUser).Forest + " и групп, в которые они были добавлены:</font></b>" $Text3 += ($Log3 | ConvertTo-Html) $Text3 >> $LogFile } "Выполнено: "+(Get-Date -Format "dd.MM.yyyy HH:mm:ss") >> $LogFile Write-Progress -Activity "Working..." -Completed -Status "All done." #Отображение индикатора выполнения завершим. Пропадет с экрана. } ELSE { Write-Host "Нет данных для обработки!" -ForegroundColor Yellow }; #================================================================================== Write-Host "" Write-Host "Журнал работы скрипта: $LogFile" -ForegroundColor DarkGreen |
[/spoiler]
Отчеты о работе ADMT
Нужно сказать что у ADMT есть Мастер отчетов, который позволяет создать убогие и неинформативные, но тем не менее отчеты:
Я рекомендую в ходе подготовки, выполнения и завершения использовать формы, которые вы разработаете исходя из конкретно вашего сценария миграции.
Последние 20 логов хранятся в C:WindowsADMTLogs , посмотреть краткую информацию по ним удобно с помощью команды ADMT TASK :
Если есть необходимость вытащить из базы архив логов, можно создать новую папку и выполнить в ней ADMT TASK /GETLOG:YES
Надеюсь озвученная информация будет полезной, а если нужна будет помощь — используйте форму на главной странице моего сайта.