Синхронизация контактов удаленных доменов (Exchange GAL) с помощью PowerShell

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

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

Ранее уже публиковались различные решения для выполнения данной задачи, однако они либо были слишком громоздки и дОроги (например на базе ILM 2007), либо не имели требуемого функционала (ранее опубликованные скрипты на PowerShell). Слева приведена схема тестовой среды — два леса, два домена, собираем инфу о пользователях домена saturn.corp и размещаем в адресной книге домена moon.corp. Хочу сразу заметить, что для полностью автоматической синхронизации необходимо хотя бы одностороннее доверительное отношение между доменами, т.е. учетная запись домена moon.corp, под которой будет выполняться скрипт, должна иметь права на просмотр учетных записей в домене saturn.corp

Общая схема синхронизации:

  • Получение с помощью скрипта информации об учетных записях удаленного домена и, в зависимости от их текущего состояния, либо создание новых контактов, либо обновление или удаление существующих в указанном OU;
  • Применение политик Exchange к созданным контактам;
  • Обновление адресных книг и GAL Exchange

Итак, в первую очередь мы разместим на почтовом сервере в домене moon.corp приведенный ниже скрипт:

Поместим данный скрипт в планировщик и назначим его выполнение, скажем, каждые 1 раз в день в 22 часа. Еще раз обращаю внимание, что учетная запись, под которой будт запускаться задание на выполнение скрипта, должна иметь права на просмотр каталога AD в удаленном домене, а также на запись в OU moon.corp/Contacts/Saturn в целевом.

[tip]Если скрипт расположен в D:\Scripts\SyncContacts\ImportUserAsContactintoAD.ps1, то строка в планировщике (с учетом использования PowerShell 1.0) должна выглядеть так: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -command «D:\Scripts\SyncContacts\ImportUserAsContactintoAD.ps1»
[/tip]

Далее нам необходимо создать и применить на сервере Exchange 2007 политику к вновь созданным контактам, исходя из того, что у всех пользователей домена saturn.corp атрибут «Организация» содержит запись «Saturn Corp.«:

Последнее, что нам нужно  сделать, это периодически (после каждой синхронизации) применять созданную нами политику и обновлять адресные книги, GAL и OAB:

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

Успехов!

P.S. Приведенный в статье способ использовался в нашей организации ранее. В настоящее время синхронизация GAL Exchange 2007 осуществляется с помощью бесплатного компонента MIIS 2003 — IIFP. Если вам интересна настройка такой схемы, прошу оставлять свои комментарии — напишу подробную статью.

Комментарии ( 16 )

  1. ToXa
    says:

    Все хорошо… только вот есть пользователи скрытые из GAL, а они таким способом все равно будут создаваться…

  2. ToXa
    says:

    Вообщем пришел к следующему решению, если кому интересно.
    Два скрипта, оба запускаются из Exchange Managment shell
    1) на сервере- источнике:

    $Mails = get-mailbox | Where-Object -FilterScript {$_.HiddenFromAddressListsEnabled -match «false»} | Select-Object -property «alias»
    $filename = «\\Shareserver\share\mailuser_dom1.txt»
    del $filename
    foreach ($m in $mails) {Get-User $m.alias | Select-Object -property «displayname»,»WindowsEmailAddress»,»Company»,»title»,»pager»,»Phone»,»MobilePhone» >> $Filename }

    2) Второй скрипт на сервере- получателе:

    $file= gc -Path \\Shareserver\share\mailuser_dom1.txt
    foreach ($f in $file) {
    if ($f -ne «»){
    $s=$f.split(«:»)
    $s[1] = $s[1].trimstart()
    if ($s[0] -match «displayname») { $Disp=$s[1]}

    if ($s[0] -match «WindowsEmailAddress») { $Email=$s[1]}

    if ($s[0] -match «Company») {$compa=$s[1]}

    if ($s[0] -match «Title») { $Title=$s[1]}

    if ($s[0] -match «Pager») { $Pager=$s[1]}

    if ($s[0] -eq «Phone «) { $Phone=$s[1]}

    if ($s[0] -match «MobilePhone») { $Mobile=$s[1]

    $al=$Email.split(«@»)

    #Если есть такой контакт
    if ((get-mailcontact -identity $Email) -notlike $NULL) {
    set-contact -identity $al[0] -company $compa -title $title -pager $pager -phone $phone -mobilephone $mobile -WindowsEmailAddress $EMail -DomainController dc.dom2.com
    }
    #Если такого контакта еще нeту
    if ((get-mailcontact -identity $Email) -like $NULL){
    new-mailcontact -name $disp -DisplayName $disp -alias $al[0] -ExternalEmailAddress $Email -PrimarySmtpAddress $Email -OrganizationalUnit «dom2.com/контакты/dom1» -DomainController dc.dom1.com
    set-contact -identity $al[0] -company $compa -title $title -pager $pager -phone $phone -mobilephone $mobile -WindowsEmailAddress $EMail -DomainController dc.dom1.com
    }

    }
    }
    }

    Собственно, запускать на соответствующих Exchange серверах. если ставить в Шедуллер, то powershell надо вызывать примерно так:

    C:\WINDOWS\system32\windowspowershell\v1.0\powershell.exe -PSConsoleFile «C:\Program Files\Microsoft\Exchange Server\bin\exshell.psc1» -command «C:\Script\ScriptName.ps1»

    Мобыть кому поможет.

  3. Евген
    says:

    Очень было бы интересно узнать про автоматизацию этого решения с помощью MIIS 2003

  4. Евген
    says:

    Про MIIS 2003 очень интересно было бы почитать

  5. Марина
    says:

    PowerShell это вещь )

  6. Кобзарев Дмитрий
    says:

    Благодарю за интересную и полезную статью.
    Попытался применить данный скрипт — ничего не вышло.
    Выпадает ошибка:
    The term ‘Connect-QADService’ is not recognized as the name of a cmdlet, functi
    on, script file, or operable program. Check the spelling of the name, or if a p
    ath was included, verify that the path is correct and try again.
    At line:1 char:19
    + Connect-QADService <<<< -Service /?
    + CategoryInfo : ObjectNotFound: (Connect-QADService:String) [],
    CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

    Подскажите, где брать данный командлет?
    Версия PowerShell установлена последняя.

    С уважением, Кобзарев Д. В.

  7. Кобзарев Дмитрий
    says:

    Также было бы очень интересно почитать про MIIS 2003.

    С уважением, Кобзарев Д. В.

  8. Костя
    says:

    Спасибо, понравилось

  9. liska
    says:

    Cool!

  10. restless
    says:

    Добрый день!
    А если мне не нужно удалять контакты вообще, мне необходимо просто взять учетки из домена Win 2000 c MS Exchage 2000 и поместить их контакты в новый лес, где стоит Windows Server 2008 R2 и MS Exchange 2010!

  11. Domer
    says:

    Add-Type -Language CSharpVersion3 -TypeDefinition @»
    public class UserAccountControl
    {
    public UserAccountControl(string str)
    {
    foreach (string s in str.Split(‘,’))
    {
    switch (s.Trim().ToUpper())
    {
    case «NORMALACCOUNT»:{
    NormalAccount = true;
    break;
    }
    case «DONOTEXPIREPASSWORD»: {
    DoNotExpirePassword = true;
    break;
    }
    case «PASSWORDNOTREQUIRED»: {
    PasswordNotRequired = true;
    break;
    }
    case «ACCOUNTDISABLED»: {
    AccountDisabled = true;
    break;
    }
    }
    }
    }

    public bool NormalAccount { get; set; }

    public bool DoNotExpirePassword { get; set; }

    public bool PasswordNotRequired { get; set; }

    public bool AccountDisabled { get; set; }

    }
    «@

    function RemoveNonExistentContacts($csvObjects)
    {
    $contacts = Get-MailContact

    foreach($contact in $contacts)
    {
    $IsNotExistentContact = $true
    foreach($csvObject in $csvObjects)
    {
    if ($contact.Alias -eq $csvObject.SamAccountName)
    {
    $IsNotExistentContact = $false
    break
    }
    }

    if ($IsNotExistentContact -eq $true)
    {
    Remove-MailContact $contact.Alias -confirm:$false
    }
    }
    }

    function UpdateContact($UserObject)
    {
    $userAccountControl = New-Object -TypeName UserAccountControl -ArgumentList $UserObject.UserAccountControl

    if ((Get-MailContact $UserObject.SamAccountName) -ne $Null)
    {
    if ($userAccountControl.AccountDisabled -eq «true»)
    {
    Remove-MailContact $UserObject.SamAccountName -confirm:$false
    }
    else
    {
    if ($UserObject.RecipientType -eq «UserMailbox»)
    {
    Set-MailContact $UserObject.SamAccountName -ExternalEmailAddress $UserObject.WindowsEmailAddress
    -Name $UserObject.Name

    Set-Contact $UserObject.SamAccountName -DisplayName $UserObject.DisplayName
    -FirstName $UserObject.FirstName -LastName $UserObject.LastName
    -City $UserObject.City -Company $UserObject.Company -Department $UserObject.Department

    -Office $UserObject.Office -HomePhone $UserObject.HomePhone
    -MobilePhone $UserObject.MobilePhone -Phone $UserObject.Phone -Fax $UserObject.Fax

    -PostalCode $UserObject.PostalCode -Title $UserObject.Title
    }
    }
    }
    elseif (($userAccountControl.AccountDisabled -ne $true) -and ($UserObject.RecipientType -eq «UserMailbox»))
    {
    New-MailContact -Name $UserObject.Name -Alias $UserObject.SamAccountName
    -ExternalEmailAddress $UserObject.WindowsEmailAddress -OrganizationalUnit agidel.ru/EmailContacts

    Set-Contact $UserObject.SamAccountName -DisplayName $UserObject.DisplayName
    -FirstName $UserObject.FirstName -LastName $UserObject.LastName
    -City $UserObject.City -Company $UserObject.Company -Department $UserObject.Department

    -Office $UserObject.Office -HomePhone $UserObject.HomePhone
    -MobilePhone $UserObject.MobilePhone -Phone $UserObject.Phone -Fax $UserObject.Fax

    -PostalCode $UserObject.PostalCode -Title $UserObject.Title
    }

    }

    # #region Раздел выполнения скрипта

    $csv = Import-Csv -Delimiter «;» -Path C:\users.csv

    #Подключаемся к серверу
    $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://domain.com/PowerShell/ -Authentication Kerberos
    Import-PSSession $Session

    #Удаляем контакты, которых не существует в импортируемом файле
    RemoveNonExistentContacts($csv)

    #Обновляем данные
    foreach($object in $csv)
    {
    UpdateContact -UserObject $object
    }

    #Закрываем соединение с сервером
    Remove-PSSession $Session

    # #endregion

  12. damirka
    says:

    Add-Type -Language CSharpVersion3 -TypeDefinition @»
    public class UserAccountControl
    {
    public UserAccountControl(string str)
    {
    foreach (string s in str.Split(‘,’))
    {
    switch (s.Trim().ToUpper())
    {
    case «NORMALACCOUNT»:{
    NormalAccount = true;
    break;
    }
    case «DONOTEXPIREPASSWORD»: {
    DoNotExpirePassword = true;
    break;
    }
    case «PASSWORDNOTREQUIRED»: {
    PasswordNotRequired = true;
    break;
    }
    case «ACCOUNTDISABLED»: {
    AccountDisabled = true;
    break;
    }
    }
    }
    }

    public bool NormalAccount { get; set; }

    public bool DoNotExpirePassword { get; set; }

    public bool PasswordNotRequired { get; set; }

    public bool AccountDisabled { get; set; }

    }
    «@

    function RemoveNonExistentContacts($csvObjects)
    {
    $contacts = Get-MailContact

    foreach($contact in $contacts)
    {
    $IsNotExistentContact = $true
    foreach($csvObject in $csvObjects)
    {
    if ($contact.Alias -eq $csvObject.SamAccountName)
    {
    $IsNotExistentContact = $false
    break
    }
    }

    if ($IsNotExistentContact -eq $true)
    {
    Remove-MailContact $contact.Alias -confirm:$false
    }
    }
    }

    function UpdateContact($UserObject)
    {
    $userAccountControl = New-Object -TypeName UserAccountControl -ArgumentList $UserObject.UserAccountControl

    if ((Get-MailContact $UserObject.SamAccountName) -ne $Null)
    {
    if ($userAccountControl.AccountDisabled -eq «true»)
    {
    Remove-MailContact $UserObject.SamAccountName -confirm:$false
    }
    else
    {
    if ($UserObject.RecipientType -eq «UserMailbox»)
    {
    Set-MailContact $UserObject.SamAccountName -ExternalEmailAddress $UserObject.WindowsEmailAddress
    -Name $UserObject.Name

    Set-Contact $UserObject.SamAccountName -DisplayName $UserObject.DisplayName
    -FirstName $UserObject.FirstName -LastName $UserObject.LastName
    -City $UserObject.City -Company $UserObject.Company -Department $UserObject.Department

    -Office $UserObject.Office -HomePhone $UserObject.HomePhone
    -MobilePhone $UserObject.MobilePhone -Phone $UserObject.Phone -Fax $UserObject.Fax

    -PostalCode $UserObject.PostalCode -Title $UserObject.Title
    }
    }
    }
    elseif (($userAccountControl.AccountDisabled -ne $true) -and ($UserObject.RecipientType -eq «UserMailbox»))
    {
    New-MailContact -Name $UserObject.Name -Alias $UserObject.SamAccountName
    -ExternalEmailAddress $UserObject.WindowsEmailAddress -OrganizationalUnit agidel.ru/EmailContacts

    Set-Contact $UserObject.SamAccountName -DisplayName $UserObject.DisplayName
    -FirstName $UserObject.FirstName -LastName $UserObject.LastName
    -City $UserObject.City -Company $UserObject.Company -Department $UserObject.Department

    -Office $UserObject.Office -HomePhone $UserObject.HomePhone
    -MobilePhone $UserObject.MobilePhone -Phone $UserObject.Phone -Fax $UserObject.Fax

    -PostalCode $UserObject.PostalCode -Title $UserObject.Title
    }

    }

    # #region Раздел выполнения скрипта

    $csv = Import-Csv -Delimiter «;» -Path C:\users.csv

    #Подключаемся к серверу
    $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://domain.com/PowerShell/ -Authentication Kerberos
    Import-PSSession $Session

    #Удаляем контакты, которых не существует в импортируемом файле
    RemoveNonExistentContacts($csv)

    #Обновляем данные
    foreach($object in $csv)
    {
    UpdateContact -UserObject $object
    }

    #Закрываем соединение с сервером
    Remove-PSSession $Session

    # #endregion

  13. sea707
    says:

    Как скопировать атрибат пользователя proxyAddresses ??

  14. Михаил
    says:

    Добрый день.
    Подскажите пожалуйста, запустил скрип. Скрипт отработал, появилось 48 контактов вместо 400 и куча ошибок такого плана «Get-contact : Не удалось выполнить операцию, поскольку объект ‘Пупкин Василий Васильевич’ не найден в ‘домен контроллер где создаем контакты’.
    C:\Scripts\new\gal.ps1:22 знак:12″. Что не так?
    вот сам скрипт
    #Адрес контроллера домена, к которому подключаемся»;
    $Domain = ‘от куда’
    Connect-QADService -Service $Domain
    #Получаем список пользователей удаленного домена
    $userlist = (Get-QADUser -name [А-Я]* -IncludedProperties DisplayName, title, company, department,
    mailNickname, Office, PostalCode, l, streetAddress,
    PhoneNumber, Pager, Mobile, facsimileTelephoneNumber,
    Email, physicalDeliveryOfficeName, wWWHomePage,
    AccountIsDisabled )
    ForEach ($user in $userlist)
    {
    #Пользователь заблокирован?
    if ($user.AccountIsDisabled -eq $true)
    {
    #У пользователя есть почта?
    if ($user.mail -notlike $NULL)
    {
    #Пользователь заблокирован, удаляем существующий контакт
    Remove-MailContact $user.mail –Confirm:$false
    }
    }
    elseif ($(Get-contact $user.displayname))
    {
    if ($user.mail -notlike $NULL)
    {
    #Пользователь не заблокирован, контакт существует, надо обновить
    Set-Contact $user.email -Company $user.Company -Title $user.Title -Department $user.Department
    -PostalCode $user.PostalCode -StreetAddress $user.StreetAddress

    -MobilePhone $user.mobile -Fax $user.facsimileTelephoneNumber
    -Phone $user.telephoneNumber
    }
    }
    else
    {
    if ($user.email -notlike $NULL)
    {
    #Пользователь не заблокирован, контакт не существует, надо создать
    New-MailContact -Name $user.displayname -DisplayName $user.displayname -alias $user.mailnickname

    -OrganizationalUnit куда/Contacts/ -ExternalEmailAddress $user.mail
    }
    }
    }
    Вопрос довольно срочный.
    Заранее спасибо.

    • Kvazar
      says:

      У вас не создались контакты, поэтому и ошибки. OU правильно задан?

Добавить комментарий

Ваш адрес электронной почты не будет опубликован. Обязательные поля отмечены знаком *

два × 3 =

Яндекс.Метрика