Cross-forest Миграция Active Directory

moving

Миграция Active Directory – относительно редкая задача, которая чаще всего возникает в связи с реструктуризацией бизнеса.

В статье будет рассмотрена Cross-forest миграция, как более комплексная задача, разобравшись с которой миграция между доменами одного леса (более распространенный сценарий) не вызовет затруднений.

Переносить будем основные объекты Active Directory – Пользователей, Группы и Компьютеры, кроме того, рассмотрим миграцию почтовых ящиков Exchange.

В своей статье я сделаю акцент на выполнении массовых операций.

Основное условие – со стороны пользователя миграция должна пройти максимально предсказуемо и с минимальными потерями рабочего времени.

Вернувшись за рабочее место пользователь сможет ввести свой существующий логин и пароль и использовать существующей профиль со всеми данными и индивидуальными настройками.

Доступ ко всем корпоративным ресурсам (ERP, Fileshare, Email, Printers) останется без изменений вне зависимости от того «переехали» эти ресурсы в новый лес или остаются в старом.

Единственное что будет изменено – это имя домена, но можно предложить использование в качестве логина Email адреса в новом лесу – это достаточно удобно с точки зрения пользователя который привык вводить личный Email в качестве логина в социальных сетях и т.п.

 Лабораторная среда

Для написания этой статьи я развернул такую лабораторную среду:

1

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] sg2 [email protected] WS3

Перед началом миграции у вас должна корректно функционировать сеть и среда виртуализации в которой будет развернут новый лес Active Directory.

Настройка доверительных отношений между лесами

В зависимости от целей и условий миграции двухсторонние доверительные отношения между Source и Target лесами могут быть недопустимы, но в моем случае я считаю их уместными (подробнее о том на каком этапе миграции кто кому должен доверять описано в ADMT Guide).

Для корректного разрешения имен между лесами существует несколько способов, например я буду использовать Conditional Forwarders:

2

3

Не важно какой способ выберете вы, но в результате имена должны разрешаться корректно:

4

5

Когда это условие выполнено, на Source DC откроем mmc Active Directory Domains and Trusts, запустим мастер в котором создадим и подтвердим такую конфигурацию:

6

Я не описываю процесс нажимания Next => Next => OK, если у вас это вызывает затруднения – и Сети достаточное количество материалов по пошаговой настройке доверительных отношений.

После завершения работы Мастера убедимся, что в Target DC также был создан траст:

7

DNS Suffix

Предполагая, что пользователи будут (временно или постоянно) использовать ресурсы обеих лесов и зная что использование srvshare вызывает меньше проблем чем srv.dom2.locshare создадим и применим такие групповые политики в Source и Target лесах соответственно:

89

В результате применения политики суффиксы будут выглядеть так (для Source domain):

10

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

11

PKI Trust

Предположим, что в новом лесу развернут новая PKI, а в PKI из старого леса вносить изменения не будем.

Пользователи доверяют сертификатам того ЦС, с которым находятся в одном лесу, но не ЦС доверенного леса, в котором находится часть ресурсов, которые они используют.

Для решения этой ситуации создадим и применим такие групповые политики в Source и Target лесах соответственно:

12

13

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

14

Builtin объекты

Builtin объекты везде имеют одинаковые SIDы, поэтому при миграции с помощью ADMT мигрированы они не будут.

Нужно проверить членство в этих группах, и членство этих объектов в других группах (вообще, рекомендую Builtin использовать как можно меньше).

 Учетная запись для ADMT

ADMT будет установлена в Target лесу, соответственно учетную запись для нее создадим тоже там.

В ADMT Guide есть детальное описание необходимых прав для этой учетной записи на разных этапах миграции, но я не буду кривляться и включу ее в группу Domain admins в Target forest и в группу Administrators в Source Domain:

15

При миграции объектов типа Computer у учетной записи ADMT должны быть права локального администратора на этих объектах. Для этого  будем использовать групповую политику, которой добавим группу DOM2Domain admins (в которую входит учетная запись dom2ADMT) в группу Локальных администраторов на компьютерах Source леса:

16

Проверим результат на рабочей станции:

17

Правила Брандмауэра

Для работы агента ADMT создадим исключения в брандмауэре с помощью групповой политики в обеих лесах (я разрешил доступ только с сервера на котором будет работать ADMT):

18

Active Directory Audit, SID Filter & SID history

Миграция атрибута SID history является ключевым моментом, ведь именно благодаря SID history пользователь находясь в Target лесу сможет получать доступ к ресурсам которые остались в Source лесу.

Обратите внимание, у пользователя может быть несколько SID в SID history.

Первым делом создадим и применим политики аудита в Source и Target лесах:

19

Затем выключим фильтрацию SID в обеих доменах:

netdom trust TrustingDomainName /domain: TrustedDomainName /quarantine:no

… и включим SID history также в обеих доменах:

netdom trust TrustingDomainName /domain: TrustedDomainName /enableSIDhistory:yes

Вот как это выглядит на dc1.dom1.loc и dc2.dom2.loc соответственно:

21

22

Обратите внимание: у учетной записи ADMT должны быть права «Migrate SID History» в Source domain. Т.к. я включил учетную запись в группу Administrators эти права есть, но если назначать права вручную, то делать это удобно с помощью Delegation Control Wizard:

23

Если на Компьютерах которые планируется мигрировать есть ОС Windows Vista и ниже, необходимо ключу HKLMSystemCurrentControlSetServicesNetlogonParametersAllowNT4Crypto задать значение 1 в Source и Target доменах на КД, которые являются эмулятором PDC.

Миграция OU

При миграции содержимого Active Directory в новый лес часто будет уместно создать новую структуру OU, но если нужно перенести существующую, то сделать это можно так:

Сделаем экспорт:

ldifde -f c:sourceOUs.ldf -r "(objectClass=organizationalUnit)" –l objectClass,distinguishedName,ou,description

24

В полученном файле заменим DC=dom1,DC=loc на DC=dom2,DC=loc и удалим те OU, которые уже есть в Target домене:

25

Скопируем отредактированный файл на Target контроллер домена и импортируем его:

ldifde -i -f c:sourceOUs.ldf

26

Проверим результат:

Get-ADOrganizationalUnit -Filter 'Name -like "*"' | FT Name, DistinguishedName -A

27

Миграция GPO

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

Но если нужно перенести политики, делается это так:

Сделаем backup политики из Source domain и скопируем его в Target domain:

28

В Target domain создадим пустую политику и запустим Import settings (мастер предложит сделать backup существующей политики, но смысла в этом нет т.к. она пустая).

Обратите внимание, при импорте нужно указывать папку в которой находится backup (например C:shareGPimport) а не папку с backup (например C:shareGPimport{%uid%})

29

После импорта, политику можно произвольным образом переименовать, убедиться, что все опции были перенесены корректно и применить ее к соответствующим OU в Target domain:

30

Установка 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 в такой конфигурации:

31

После установки необходимо опубликовать 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).

32

33

Установим ADMT указав имя сервера БД и инстанс для ADMT:

34

Создадим ключ для PES:

 ADMT Key /Option:Create /SourceDomain:dom1.loc /KeyFile:C:key.pes /KeyPassword:P@$$w0rd

35

Скачаем PES x64 c Microsoft Connect и установим его на сервер srv3.dom1.loc который, напомню, является временным контроллером домена в Source лесу.

В процессе установки, укажем путь и пароль к созданному ранее ключу, а в качестве учётной записи для службы будем использовать DOM2admt

После перезагрузки, откроем оснастку services.msc и запустим службу Password Export Server Service

36

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

Обратите внимание, при миграции будут скопированы и установлены даже те пароли, которые не соответствуют политике Target домена (например, пустые или «123»).

Для проверки возможности миграции SID в Target domain создадим пустую Domain local группу %NETBIOSNAME%$$$.

 Опции по-умолчанию

В ходе работ я буду показывать как создать свои конфигурации процессов миграции, и если не указать какую-то опцию в явном виде она получит параметр который указан в C:WindowsADMTtemplatescript.vbs

Разумеется, этот файл можно редактировать по всякому, но лучше для каждого процесса использовать свой Option file, что я и буду делать.

Обратите внимание, синтаксис для Option File, Script и логов отличается.

Управление исключаемыми атрибутами ADMT

По-умолчанию ADMT исключает набор атрибутов во время миграции, посмотреть этот список можно создав и выполнив скрипт DisplayExclusionList.vbs :

Set o = CreateObject("ADMT.Migration")
WScript.Echo o.SystemPropertiesToExclude
 .cscript.exe C:DisplayExclusionList.vbs

Чтобы изменить список исключаемых атрибутов можно использовать скрипт ExclusionList.vbs :

Set o = CreateObject("ADMT.Migration")
o.SystemPropertiesToExclude = "<Attribute1>,<Attribute2>,<Attribute3>"
.cscript.exe C:ExclusionList.vbs

В примере я добавил в исключения созданный мною атрибут “aaa” :

37

Если версия схемы в Source и Target разная (у меня, например, в Source установлен Exchange 2010, а в Target Exchange 2013) в каждом логе будут присутствовать дополнительно исключенные атрибуты (на скриншете видно примерно четверть) и Unspecified error 0x80004005 :

38

Отключение требования смены пароля при следующем входе Пользователя

После миграции Пользователя атрибут pwdLastSet будет присвоено значение 0 , а это значит что при первом входе пользователю будет предложено сменить пароль.

С точки зрения безопасности — это разумный шаг, но с точки зрения процесса миграции будет лучше если пользователи сменят пароли спустя какое-то время.

В любом случае решить этот вопрос стоит на этапе подготовки, а не в процессе миграции.

Т.к. пользователей мы будем переносить массово по OU, наиболее удобный способ отключить требование смены пароля при следующем входе таков:

Get-ADUser -Filter * -SearchBase "%OU Path%" | Set-ADUser -ChangePasswordAtLogon $False

39

Включение Exchange MRS Proxy

На Source Exchange сервере включим Exchange MRS Proxy:

Set-WebServicesVirtualDirectory -Identity "EWS (Default Web Site)" -MRSProxyEnabled $true

40

Уменьшить таймаут можно указав в файе 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 их можно выгрузить так:

Get-ADGroup -Filter {ObjectClass -eq "Group"} -SearchBase "%OU path%" | select sAMAccountName | Export-csv C:%filename%.csv
Get-ADUser -Filter {ObjectClass -eq "User"} -SearchBase "%OU path%" | select sAMAccountName | Export-csv C:%filename%.csv
Get-ADComputer -Filter {ObjectClass -eq "Computer"} -SearchBase "%OU path%" | select sAMAccountName | Export-csv C:%filename%.csv

41

Результат выглядит следующим образом (обратите внимание, к объектам типа “Computer” знак $ добавляется автоматически):

42

43

44

Если Вы хотите переименовать пользователей при миграции можно создать расширенный Include file в формате .csv или .txt:

45

Перед началом миграции вы должны сделать экспорт всех объектов которые будут мигрированы, и поделить на «пачки» согласно плану миграции.

Эти объекты крайне желательно не изменять на всем протяжении миграции.

Миграция одной Группы

Я не буду подробно описывать каждый шаг мастера ADMT т.к. это достаточно подробно описано в ADMT Guide, так что перейдем к практике.

В первую очередь мигрируем группу DOM1$$$ с миграцией SID

46

…и убедимся, что все прошло штатно:

47

В Target domain был успешно создан новый объект и записан атрибут SID history:

48

49

Массовая миграция Групп

В первую очередь нужно мигрировать все Группы потому что при миграции Пользователей и Компьютеров их членство в  группах должно быть восстановлено автоматически.

Обратите внимание, после окончания миграции Пользователей и Компьютеров необходимо будет выполнить повторную миграцию Групп в режиме Merge + Fix members of Group для того чтобы полностью убедится в том, что членство в группах в Target = членству в группах Source.

Итак, запустим Мастер миграции Групп и будем использовать список групп из Include файла, а не указывать группы вручную:

50

Укажем путь к Include файлу и OU в Source domain в котором находятся группы из Include файла.

Обратите внимание, первые строчки .csv не являются группами, поэтому будут исключены.

51

Вместе с группой не будем переносить Пользователей которые в нее входят, но SID запишем в SID history обязательно т.к. это позволит членам группы получать доступ к ресурсам которые доступны одноименной группе в Source лесу.

52

Если случилось так, что группа была создана в Target лесу до начала миграции, за счет опции Merge будет выполнено слияние (фактически, в существующую группу будут дописаны атрибуты и изменено членство):

53

После окончания каждого этапа миграции рекомендую внимательно просматривать лог операции. Как видите, в моем случае имеется предупреждение, связанное с адресной книгой Exchange.

Но на данном этапе главное, что группы были созданы в target домене и атрибут SID history записан.

54

Если мигрировать большое количество «пачек» вручную с помощью Мастера имеет место риск человеческой ошибки, чтобы его минимизировать можно использовать Option file такого содержания:

[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 файлов выполним:

ADMT GROUP /O: "C:MigrationOptFlGroup.txt" /F: "C:MigrationBranchOfficeGroups.csv"

55

Убедимся что миграция прошла предсказуемо:

56

Миграция одного Пользователя

Т.к. я выполняю Cross-forest сценарий, будет выполнена не  миграция, а создание аналогичных объектов типа «Пользователь» в Target domain.

Это необходимо для того, чтобы сервисы которые будут мигрироваться, до окончательной миграции пользователей могли работать уже с новыми аккаунтами. Например, Exchange создаст этим пользователям почтовые ящики и в них будет поступать свежая почта в то время пока будет выполняться копирования содержимого ящика из Source организации.

В случае миграции Active Directory совместно с Exchange командлет PrepareMoveRequest создаст пользователей в Target domain, так что этот этап можно было бы пропустить, но я покажу как выполнить «первичную» миграцию пользователей на случай если вы мигрируете только AD .

Будем мигрировать пользователя user1 в аналогичный OU в Target domain:

57

На этом этапе будем генерировать новые пароли для Пользователей, а при повторной миграции скопируем пароли из Source домена:

58

Аккаунты в Source оставим включенными, а в Target выключенными. SIDы мигрировать не будем.

59

Возобновим членство в группах т.к. Группы у нас уже смигрированны.

60

В Target домене еще нету Пользователей из Source домена, так что опция Merge будет сугубо номинальной.

61

Результат миграции посмотрим в логе:

62

Отключим необходимость смены пароля пользователем при следующем входе:

Get-ADUser -Identity $%username%| Set-ADUser -ChangePasswordAtLogon:$false

Проверим членство в группах:

Get-ADPrincipalGroupMembership %username% | select name

Проверим SID history:

Get-ADUser %user% -Property sIDHistory

84

 

Массовая миграция Пользователей

Для массовой миграции Пользователей будем использовать Option file :

[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):

ADMT USER /O: "C:MigrationOptFlUsers.txt" /F: " C:MigrationBranchOfficeUsers.csv"

63

64

Миграция одного почтового ящика Exchange

Откроем Exchange Management Shell в Target domain и введём учётные данные Target domain:

$localCredentials = Get-Credential

.. и учетные данные Source Domain:

$RemoteCredentials = Get-Credential

Перейдём в папку со скриптами Exchange:

cd "C:Program FilesMicrosoftExchange ServerV15Scripts"

Выполним подготовку:

.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:

65

В дальнейшем, когда будем выполнить повторную миграцию пользователей в режиме Merge этот Пользователь будет дополнен атрибутами, включен в группы и ему будет установлен пароль такой же, как в Source домене.

Выполним миграцию почтового ящика для созданного пользователя:

New-MoveRequest –Identity '%EmailAddress%' –Remote –Remotehostname ‘%Source Exchange FQDN%’  -RemoteCredential $RemoteCredentials –TargetDeliverydomain '%mail domain%'

Выглядит миграция ящика Exchange как на скриншете ниже и занимает определенное время, в зависимости от размера ящика:

66

При следующем запуске Outlook запросит перезапуск:

67

.. И после этого будет работать с новым сервером:

68

Массовая миграция почтовых ящиков

Массовая миграция почтовых ящиков так же проста, как и миграция одного почтового ящика, но в файле со списком пользователей нужно заменить заголовок sAMAccountName на Identity

Затем укажем «Include файл» (у меня он состоит из единственного ящика [email protected]) в уже знакомые командлеты:

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
Import-Csv C:%file with users%.csv | New-MoveRequest –Remote –Remotehostname ‘%Source Exchange FQDN%’  -RemoteCredential $RemoteCredentials –TargetDeliverydomain '%mail domain%'

69

Миграция одного Компьютера и Профиля

Миграция Компьютеров и Профилей технически сложнее и, соответственно, вызывает больше проблем чем миграция Пользователей.

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

После миграции компьютер будет автоматически перезагружен.

Обратите внимание, после миграции в Target domain у Компьютера в группе локальных администраторов останутся только локальные учетные записи, поэтому нужно заранее применить на OU в который будет выполняться миграция, политику которая будет использовать Restricted Groups:

70

Запустим Мастер миграции Компьютеров (опции, которые я устанавливал будет видно в логе).

Больше всего проблем вызывает миграция Профилей, бывает так что даже в лабораторной среде она проходит успешно только со второго раза (компьютер придется вручную переводить в Source domain и также вручную удалять созданные профили %user%.%Target Domain NetBIOS name%)

Вот как выглядит результат успешной миграции Компьютера и Профиля:

71

Лог миграции Компьютера:

72

73

Лог агента переноса Профилей:

74

75

Еще раз заостряю ваше внимание – миграция профилей может быть большой проблемой с низкой предсказуемостью, так что предусмотрите достаточное количество времени на этот этап.

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

76

Проверим logon server

[System.Environment]::GetEnvironmentVariable("logonserver")

… и канал между Компьютером и доменом:

Test-ComputerSecureChannel

77

Но если случилось так, что после миграции Компьютера и Профиля пользователь при логине получает новый, «пустой» профиль быстро исправить ситуацию можно так:

  1. Логинимся на ПК с правами Администратора
  2. Открываем Regedit и переходим в HKEY_LOCAL_MACHINESoftwareMicrosoftWindows NTCurrentVersionProfileList
  3. Выбираем SID пользователя из Target домена и в параметре ProfileImagePath меняем C:Users%username%.%Target domain NetBIOS name% на C:Users%username%
  4. Профиль C:Users%username%.%Target domain NetBIOS name% можно удалить.
  5. Пользователю из Source домена в профиле можно ничего не менять т.к. в процессе миграции он будет заблокирован.
  6. Перезагружаем Компьютер и у Пользователя при входе в Target domain будет тот же профиль который был в Source домене.

Массовая миграция Компьютеров и Профилей

Массовая миграция Компьютеров и Профилей практически аналогична массовой миграции Групп, которая была рассмотрена ранее, но из Include file нужно убрать знак $ в именах компьютеров.

Если «пачек» с компьютерами много, можно использовать Option файл такого содержания:

[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 файлов:

ADMT COMPUTER /O: C:MigrationOptFlComputer.txt /F: C:MigrationBranchOfficeComputers.csv

78

79

Теперь массово смигрируем профили:

[Migration]
IntraForest=No
SourceDomain=dom1.loc
TargetDomain=dom2.loc
[Security]
TranslateOption=Replace
TranslateUserProfiles=Yes
TranslateUserRights=Yes
ADMT SECURITY /O: C:MigrationOptFlProfile.txt /F: C:MigrationBranchOfficeComputers.csv

8081

Повторная массовая миграция Пользователей

В процессе повторной миграции Пользователей будет перенесен пароль, отключен Source и включен Target аккаунт, записан атрибут SID history, и еще раз восстановлено членство в группах.

Обратите внимание, будут скопированы и установлены даже те пароли, которые не соответствуют политике Target домена (например, пустые или «123»).

Пример Option file:

[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

…и пример выполнения повторной миграции

ADMT USER /O: "C:MigrationOptFlUsers.txt" /F: "C:MigrationBranchOfficeUsers.csv"

82

83

Теперь, когда Пользователи полностью смигрированы и включены убедимся, что содержимое почтовых ящиков перенесено, и доставка почты работает корректно (я не описываю миграцию адресных книг Exchange с помощью FIM и настройку EDGE подписок я опишу в отдельной статье):

85

После того, как будет выполнена миграция всех «пачек» Пользователей и Компьютеров желательно выполнить повторную миграцию Групп в режиме Merge + Fix members of Group для того чтобы полностью убедится в том, что членство в группах в Target = членству в группах Source.

Вообще, во время миграции не следует производить какие-либо операции в AD, а особенно изменять членство в группах.

Но предположим Пользователя который находится в Source домене добавили в группу которая находится в Target домене а спустя некоторое время смигрировали пользователя из Source в Target domain.

В этом случае его членство в группе Target домена будет утеряно. Чтобы решить эту проблему, можно использовать

скрипт от Илгиз Мамышев

[spoiler title=’Скрипт’ collapse_link=’true’]

#
# Версия 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 есть Мастер отчетов, который позволяет создать убогие и неинформативные, но тем не менее отчеты:

86

Я рекомендую в ходе подготовки, выполнения и завершения использовать формы, которые вы разработаете исходя из конкретно вашего сценария миграции.

Последние 20 логов хранятся в C:WindowsADMTLogs , посмотреть краткую информацию по ним удобно с помощью команды ADMT TASK :

87

Если есть необходимость вытащить из базы архив логов, можно создать новую папку и выполнить в ней ADMT TASK /GETLOG:YES

88

Надеюсь озвученная информация будет полезной, а если нужна будет помощь — используйте форму на главной странице моего сайта.