Статья размещена автором Бетке Сергей Сергеевич

Powershell: создадим группы рассылки для Exchange по телефонным номерам

Я думаю, Вы не однократно сталкивались с необходимостью сообщить Вашему контрагенту по телефону Ваш адрес электронной почты. Диктовать вместе “betke” последовательность “Борис-Елена-Тимофей-Константин-Елена” меня уже утомило.

Необходимо найти решение и для людей, не проходивших обучения на военной кафедре Powershell: создадим группы рассылки для Exchange по телефонным номерам Натыкаюсь последнее время на любопытное решение: некоторые контрагенты в качестве lname используют свой внутренний телефонный номер. И в этом есть смысл – сообщить по телефону 3 цифры куда проще, чем кучу латинских букв.

Приступаем к реализации данного подхода на Exchange Server.

Рассмотрим суть предлагаемого решения на моём примере. Мой адрес – sergey.s.betke@novgaro.ru. Мой внутренний телефонный номер – 555. Поэтому по телефону имею желание сообщать адрес 555@novgaro.ru. Можно банально дописать адрес на страницу E-Mail Addresses в ADUC. А что делать, если номер используется несколькими сотрудниками? Чтобы избежать конфликта, решил использовать для данной цели группы распространения. И в моём случае – локальные.

Итак, приступим. P.S. Текущая версия сценария доступна в моём SVN репозитории, доступ на чтение — svn_sysadm, пароль — svn.

Powershell: создадим группы рассылки для Exchange по телефонным номерам Выбираем атрибуты

Планирую использовать для этих целей поле Номер телефона (так его нам ADUC представляет). Powershell: создадим группы рассылки для Exchange по телефонным номерамЗапускаем оснастку ADSI и видим, что на самом деле интересующий нас атрибут именуется следующим образом:  telephoneNumber.

Атрибут, который планируем задействовать под внутренний номер, выбрали. Теперь планирую следующие шаги:

  • сделаю выгрузку из AD в .csv с тем, чтобы облегчить экспорт в excel и дальнейшее согласование / утверждение телефонной книги;
  • реализую загрузку сведений из .csv с соответствующей корректировкой атрибута telephoneNumber и формированием журнала изменений;
  • и создам сценарий powershell, который создаст локальные группы распространения (distribution group), членство в которых будет определяться как раз значением атрибута telephoneNumber.

Приступаем к первому этапу.

Выгружаем телефонную книгу в .csv

Реализуем выгрузку по примеру статей “Собственно выгрузка контактных координат сотрудников из AD” и “Корректируем инициалы всех сотрудников в AD средствами powershell”. Если Вы ранее не использовали командлеты ADpowershell, рекомендую предварительно прочитать статью “Установка и первый опыт ADpowershell на Windows Server 2003”.

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

Итак, телефонную книгу в .csv мы выгрузили для проверки и корректировки.

Загрузим в AD телефонную книгу из .csv

А теперь реализуем обратную операцию. Может пригодиться, когда Вам присылают откорректированный документ, полученный первоначально в предыдущем параграфе. Сценарий по импорту телефонной книги в статье “PowerShell: загружаем (импортируем) телефонную книгу в AD из .csv”.

Создаём группы распространения

Безусловно, сегодня можно обойтись для этой задачи и без статических групп с помощью Dynamic Distribution Groups, но Exchange 2003 такое удовольствие не поддерживает, поэтому всё-таки пишем скрипт для создания групп.

С созданием групп и OU проблем не возникло. Возникли некоторые сложности с автоматизацией удаления “лишних” членов в этих абонентских группах и добавления только тех, что ещё не являются членами. Кроме того, иной проверки наличия OU и групп, кроме как test-Path, не нашёл. Если Вы сможете предложить в комментарии иные варианты – буду крайне благодарен.

Более серьёзные проблемы возникли с тем, чтобы сделать созданные группы mail-enabled. Для этих целей Exchange 2010 предоставляет замечательную командлету enable-DistributionGroup, но Exchange 2003 её не предоставляет. С помощью enable-DistributionGroup код был бы следующим:

enable-DistributionGroup -Identity $phoneDG -Alias $phone;

Но мы будем вынуждены придумывать некий “костыль” для Exchange 2003. Ранее, при доступе через ADSI можно было воспользоваться методом MailEnable. Приведу фрагмент кода JScript, который эксплуатируется по сей день:

{ // создание группы рассылки

    { // корректировка реквизита displayName для отображения в адресной книге как ГР Персонал...
        var displayName = "ГР " + group.sAMAccountName;
        if (
            changeProperty (group, "displayName", displayName)
        ) group.SetInfo();
    };

    var DLAddr = "all." + mailboxName;
    group.mailNickname = DLAddr;
    group.SetInfo();

    if (!group.mail) {
        group.MailEnable();
        WScript.StdOut.Write("!!!создаём группу рас. " + sn + " : " + DLAddr + "\n");
        group.SetInfo();
    };
};

Посему буду пробовать писать свой обрезанный enable-DistributionGroup, используя ADSI+CDOEXM. Решение выделил в отдельный модуль и отдельную статью. Возникла проблема с авторизацией, которой также посвятил отдельную статью, рекомендации статьи необходимо выполнить до попытки запуска кода.

Опубликую код, весь функционал уже проверен:

<# 
    .Synopsis 
        Создаём группы рассылки по телефонам (telephoneNumber) и включаем в них только те учётные записи,
        для которых в атрибуте telephoneNumber указан соответствующих телефонный номер. 
    .Description 
        Создаём группы рассылки по телефонам (telephoneNumber) и включаем в них только те учётные записи,
        для которых в атрибуте telephoneNumber указан соответствующих телефонный номер. 
    .Parameter searchBase 
        OU, в которой будет осуществляться поиск учётных записей для групп рассылок 
    .Parameter filter 
        Фильтр для отбора учётных записей для групп рассылок 
    .Parameter DGOU
        OU, в которой будут созданы группы рассылки 
    .Parameter rePhone
        Регулярное выражение, описывающее телефонный номер (для проверки) 
    .Example 
        Создаём / обновляем группы рассылки 
  
        .\PhonesToDistGroups.ps1
#> 
  
[CmdletBinding(
    SupportsShouldProcess = $true,
    ConfirmImpact = 'Low'
)]
param ( 
    [Parameter( 
        Mandatory=$false, 
        Position=0, 
        ValueFromPipeline=$false, 
        HelpMessage="OU, с которой будет осуществляться выгрузка телефонной книги в .csv"
    )] 
    [string] 
    $searchBase = 'OU=Группа Компаний ГАРО,OU=Персонал,OU=Предприятия,OU=iTg,DC=novgaro,DC=ru',
    [Parameter( 
        Mandatory=$false, 
        ValueFromPipeline=$false, 
        HelpMessage="Фильтр для отбора учётных записей для телефонной книги."
    )] 
    $filter = {name -notlike 'ПЯ*'}, 
    [Parameter( 
        Mandatory=$false, 
        Position=1, 
        ValueFromPipeline=$false, 
        HelpMessage="OU, в которой будут созданы группы рассылки."
    )] 
    [string] 
    $DGOU = 'OU=По телефонным номерам,OU=Контакты,OU=iTg,DC=novgaro,DC=ru',
    [Parameter( 
        Mandatory=$false, 
        ValueFromPipeline=$false, 
        HelpMessage="Регулярное выражение, описывающее телефонный номер (для проверки)."
    )] 
    [string] 
    $rePhone = '\d{3}'
) 
  
import-module ActiveDirectory
import-module .\ITG.Exchange\ITG.Exchange.psm1 -force

Write-Verbose @"
Создаём группы рассылки по телефонам (telephoneNumber) и включаем в них только
те учётные записи, для которых в атрибуте telephoneNumber указан соответствующий
телефонный номер. 
OU для выбора учётных записей, для которых будут созданы группы рассылок:
    $searchBase
Фильтр для отбора учётных записей:
    $filter
OU, в которой будут созданы группы рассылки:
    $DGOU
"@

$phones = @(Get-ADUser `
    -Filter $filter `
    -SearchBase $searchBase `
    -SearchScope Subtree `
    -Properties `
        cn, displayName, telephoneNumber `
| group-object `
    -property telephoneNumber `
| sort-object `
    -property name `
| where-object { $_.name -match $rePhone } `
)

Write-Verbose @"
Обнаружено $($phones.count) телефонных номеров.
"@

$phones | % `
-begin {
    [int]$phoneIndex = 0;
    write-progress `
        -id 1 `
        -activity 'Коррректируем / создаём группы рассылок по телефонным номера' `
        -status 'Приступаем...'
} `
-process {
    [string] $phone = $_.name;
    [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] $phoneOU = $null;
    if (test-path "AD:\OU=$phone,$DGOU") {
        $phoneOU = Get-ADOrganizationalUnit "OU=$phone,$DGOU";
    } else {
        if ($pscmdlet.ShouldProcess("AD:\OU=$phone,$DGOU", 'создание')) {
            $phoneOU = New-ADOrganizationalUnit `
                -Name $phone `
                -Path $DGOU `
                -ProtectedFromAccidentalDeletion $false;
        } else {
            return; # обработка этого телефона далее невозможна
        };
    };

    [Microsoft.ActiveDirectory.Management.ADGroup] $phoneDG = $null;
    if (test-path "AD:\CN=Подписка,OU=$phone,$DGOU") {
        $phoneDG = Get-ADGroup `
            -Identity "CN=Подписка,OU=$phone,$DGOU" `
            -property mailNickname,msExchHideFromAddressLists,reportToOriginator;
    } else {
        if ($pscmdlet.ShouldProcess("AD:\CN=Подписка,OU=$phone,$DGOU", 'создание')) {
            $phoneDG = New-ADGroup `
                -Name 'Подписка' `
                -SamAccountName "ГРТН $phone" `
                -GroupScope Global `
                -GroupCategory Distribution `
                -Path "OU=$phone,$DGOU" `
                -DisplayName "ГРТН $phone" `
                -PassThru;
        } else {
            return; # обработка этого телефонного номера далее невозможна
        };
    };
    if ( `
        ($phoneDG.mailNickname -ne $phone) -or `
        (!$phoneDG.msExchHideFromAddressLists) -or `
        (!$phoneDG.reportToOriginator) `
    ) {
        Set-ADGroup `
            -Identity $phoneDG `
            -Description "Автоматически сформированная группа рассылки по абонентам телефонного номера $phone." `
            -DisplayName "ГРТН $phone" `
            -Replace @{
                mailNickname = $phone;
                msExchHideFromAddressLists = $true;
                reportToOriginator = $true;
            };
    };
        
    $currentSubscribers = @(Get-ADGroupMember -Identity $phoneDG | ? {$_.objectClass -eq 'user'});
    $currentSubscribersGUIDs = @($currentSubscribers | % { $_.objectGUID });
    $newSubscribers = @($_.group | ? {$currentSubscribersGUIDs -notcontains $_.objectGUID});
    $newSubscribersGUIDs = @($_.group | % { $_.objectGUID });
    $obsoleteSubscribers = @($currentSubscribers | ? {$newSubscribersGUIDs -notcontains $_.objectGUID});

    if ($newSubscribers.count) {
        if ($pscmdlet.ShouldProcess($phoneDG, @"
Добавление в группу рассылки для телефона $phone следующих учётных записей:
 $($newSubscribers | % { "`t$($_.displayName)`n"})
"@
        )) {
            Add-ADGroupMember `
                -Identity $phoneDG `
                -Members $newSubscribers;
        };
    };
    if ($obsoleteSubscribers.count) {
        if ($pscmdlet.ShouldProcess($phoneDG, @"
Удаление из группы рассылки для телефона $phone следующих учётных записей:
 $($obsoleteSubscribers | % { "`t$($_.displayName)`n"})
"@
        )) {
            Remove-ADGroupMember `
                -Identity $phoneDG `
                -Members $obsoleteSubscribers;
        };
    };
    $phoneIndex++;
    write-progress `
        -id 1 `
        -activity 'Коррректируем / создаём группы рассылок по телефонным номера' `
        -status 'Создаём / корректируем группы распространения' `
        -currentOperation "$phone ($($phoneIndex) из $($phones.count))" `
        -percentcomplete ($phoneIndex/$phones.count*100);
    $phoneDG;
} `
-end {
    write-progress `
        -id 1 `
        -activity 'Переносим файлы в древовидную структуру папок' `
        -status 'Завершили...' `
        -completed
} `
| Enable-DistributionGroup

Write-Verbose @"
Создание групп рассылки по телефонам завершено.
"@

И этот код успешно работает. server-12 – это exchange server 2003 хост.

P.S. Этот код будет успешно работать и для Exchange Server 2007, но предварительно следует убрать наше импорт нашего собственного модуля .\ITG.Exchange\ITG.Exchange.psm1. Кстати, модуль описан здесь.

P.P.S. Вызов Enable-DistributionGroup в конвейере в моём случае обладает существенно более высокой производительностью по одной причине: все группы обрабатываются одной удалённой сессией. Если просто вызывать как функцию для каждой группы отдельно – потребуется дополнительное время на открытие и закрытие удалённое сессии для каждой группы.

Сценарий как командлета powershell

Как Вы уже, надеюсь, заметили, данный сценарий максимально приближен по функциональности к командлете. Он поддерживает параметры –whatIf, -verbose, -confirm. Должен сказать – самому понравилось, очень удобно при работе с AD (когда нужно проверить, что же сценарий сделает перед тем, как он это сделает). Необходимый для такой поддержки код выделен.

Полезности

Опубликовать комментарий

XHTML: Вы можете использовать следующие HTML теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Tags Связь с комментариями статьи:
RSS комментарии
Обратная ссылка