image

Работая в течении полугода с Microsoft Exchange Server 2016 в компании, где более 500 сотрудников использует корпоративную почту, я столкнулся с проблемой полноценного удаления информации о пользователях, отключенных в Active Directory.

Задачи, которые мы хотим автоматизировать, после отключения учетки пользователя в AD:

  • Экспорт всех писем из основого и архивного ящика в .pst файл;
  • Полная блокировка почтового ящика после экспорта писем;
  • Очистка всех списков рассылки от «мертвых пользователей» (автоматически не очищается);
  • Обновление Global Address List и Offline Address Book, чтобы активные пользователи не видели отключенных.

Испытывая полнейшую нелюбовь к ручной работе, было принято решение максимально автоматизировать все эти задачи с помощью PowerShell.

Подготовка:


Подключаем библиотеку Exchange Management PowerShell:

Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn;

Получаем список всех отключенных в Active Directory пользователей и исключаем некоторые служебные записи:

$DisableUsers = get-user -Filter {(UserAccountControl -eq 'AccountDisabled, NormalAccount') -and (RecipientType -eq 'UserMailbox')} | ? {($_.SamAccountName -ne 'krbtgt') -and ($_.SamAccountName -ne 'SM_2013a5b0c2bd4ca2a') -and ($_.SamAccountName -ne 'testvc')}

Объявляем переменные:

# Объявляем переменную для объединения нескольких запросов на экспорт.
$BatchName = 'MassRequest'
# Создаем пути для экспорта
$CMounth = (Get-Date).month 
$CYear = (Get-Date).year 
$CurrentDate = "$CYear.$CMounth" # Получаем будущее имя папки вида Год.Месяц
$MainDir = "\\%Ваш путь%"
$ExportPath = $MainDir + $CurrentDate + "\"

Обработка:


Чтобы было удобнее найти .pst архив уволенного пользователя, было принято решение создавать папку вида Год.Месяц. Так, все уволенные пользователи в апреле 2017 года попадут в папку 2017.4, уволенные в мае в папку 2017.5 и тд.

# Проверяем, есть ли уже папка Год.Месяц, если нет, то создаем.
if ((Test-Path $ExportPath -PathType Container) -eq $false){
    New-Item -Path $MainDir -Name $CurrentDate -ItemType "directory"
} 

В цикле по отключенным пользователям выгружаем их почту в .pst файл из основного и архивного почтовых ящиков и сохраняем в папку Год.Месяц.

С помощью параметра -BatchName объединяем запросы под одним именем, для возможности отслеживать статус всей выгрузки сразу, а не каждый запрос отдельно.

foreach($User in $DisableUsers){
$PrimaryPath = $ExportPath + $User.SamAccountName + ".pst"
$ArhivePath = $ExportPath + $User.SamAccountName + "_Archive.pst"

New-MailboxExportRequest -Mailbox $User.SamAccountName -BatchName $BatchName -FilePath $PrimaryPath
New-MailboxExportRequest -Mailbox $User.SamAccountName -BatchName $BatchName -FilePath $ArhivePath -IsArchive     
} 

Ждем, пока скрипт закончит работу. Ждать нужно обязательно, т.к. дальше мы переводим ящики в статус Disable и хотим быть уверены, что перед этим выгрузка почты закончилась.

# Ждем, пока скрипт закончит работу
$i=1;
while ((Get-MailboxExportRequest -BatchName $BatchName | Where {($_.Status -eq “Queued”) -or ($_.Status -eq “InProgress”)})) {
    sleep 60
    Write-Host "Скрипт работает $i минут. Ожидаем завершения.."
    $i=$i+1
}

После завершения экспорта удаляем все запросы, которые получили статус Completed

# После завершения экспорта удаляем все запросы
Get-MailboxExportRequest -Status Completed | Remove-MailboxExportRequest -Confirm:$false 

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

# Начинаем чистить списки рассылок. Сначала получаем их полный список. 
$DistribList = Get-DistributionGroup

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

# В цикле удаляем отключенных пользователей из всех списков
foreach($List in $DistribList){
    foreach($User in $DisableUsers){        
        Remove-DistributionGroupMember -Identity $List -Member $User -Confirm:$false -ErrorAction Ignore  
    }
} 

Предпоследний этап: отключение почтовых ящиков. Из учетки пользователя в AD пропадает E-mail, а сам почтовый ящик удаляется. Теперь его только в течении некоторого времени можно восстановить стандартными средствами Exchange.

# Начинаем отключать почтовые ящики, после чего они пропадут из адресной книги
foreach($User in $DisableUsers){
    Disable-Mailbox -Identity $User.SamAccountName -Archive -Confirm:$false 
    Disable-Mailbox -Identity $User.SamAccountName -Confirm:$false       
}

Обновляем GAL и OAB, чтобы пользователи увидели изменения как можно быстрее.

# Обновляем Global Adress List, чтобы клиенты увидели изменения в адресной книге
Get-GlobalAddressList | Update-GlobalAddressList
Get-OfflineAddressBook | Update-OfflineAddressBook
Get-AddressList | Update-AddressList

Небольшой комментарий:


В своей фирме мы прицепили эту обработку к кастомной кнопке в 1С. Отдел кадров в профиле сотрудника выставляет ему статус «Уволен» и скрипт начинает работать.

Тем самым отключенных пользователей в адресной книге практически невозможно увидеть, а уволенный сотрудник сразу теряет доступ к почте. (Если только выключить учетку в Active Directory, то зайти в почту сотрудник все равно может, что по нашей корпоративной политике недопустимо).

Надеюсь кому-то скрипт будет полезен. Спасибо!
Поделиться с друзьями
-->

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


  1. vesper-bot
    18.05.2017 16:39

    > Если только выключить учетку в Active Directory, то зайти в почту сотрудник все равно может, что по нашей корпоративной политике недопустимо

    Вот этот кусок сильно смущает. Как сотрудник может зайти в почту, если его учетка disabled? Или проблема в репликации AD?


    1. kotorr
      18.05.2017 16:54
      -3

      Авторизацию в почту идет по логину и паролю из домена. Если мы заблокировали учетку пользователя в AD, то он не может авторизоваться на рабочей станции и запустить Outlook.
      Но если с другого утройства он зайдет на веб-морду почтовика, то под своими, уже заблокированными учетными данными, он все равно сможет в почту войти.
      Вроде как отправлять почту он не сможет, но всю переписку может прибить или скопировать что-то важное.
      С архивной почтой это тоже работает.


      1. vesper-bot
        18.05.2017 17:32

        Вот реально не верю. Чтобы проходила авторизация на веб-морде (owa) под заблокированными учетными данными, либо должна быть ситуация, при которой owa при проверке получает ответ «не заблокировано», либо есть баг в Exchange, от которого надо избавляться любой ценой (то есть запросить Микрософт и выяснить, почему это у вас такое позволительно). Одно дело если это кэшированная сессия подключенного мобильного устройства (ActiveSync) — тогда надо после блокирования пользователя делать

        Get-MobileDevice -mailbox $disableduser.mail | Remove-MobileDevice
        (можно даже сделать clear-mobiledevice, если политикой требуется), в этом случае все сессии activesync будут разорваны, и устройство, попытавшись получить доступ под сохраненными учетными данными, получит ошибку account disabled и не сможет ничего получить, тем более удалить из ящика. Но если реально после репликации AD заход с логином/паролем на owa даст пользователю доступ — это глюк.


        1. gotch
          18.05.2017 18:01
          +2

          Просто CAS кеширует учетные данные, есть несколько статей на эту тему вроде https://support.microsoft.com/en-us/help/267568/an-old-password-still-works-after-you-change-it-in-outlook-on-the-web. Кто-то, кто заморачивается, отключает таким учеткам OWA.
          Но там кэш всего на 15 минут, вроде это ни для кого не было критичным. Если время менять, то OWA может начать притормаживать.


          1. vesper-bot
            18.05.2017 18:15

            Интересно, и в принципе логично, однако это работает только если пользователь сразу перед тем, как его заблокировали, авторизовался на OWA. Тогда он, узнав, что его заблочили, может с веб-морды что-то удалить. А обойти такой косяк тоже можно, если запихать CAS-сервера в отдельный сайт, рядом к ним поставить DC/RODC и его посадить в тот же сайт. После чего уменьшить кэширование токенов на IIS на скажем три минуты (это может привести к тому, что пользователи начнут получать диалог «войдите заново», тогда лучше так все-таки не делать — но по описанию токен кэшируется, т.е. IIS будет воссоздавать токен без запроса логина/пароля, так как сессия авторизации пользователя на ПО не истекла, и хотя производительность и просядет слегка, пользователи не будут получать диалог повторной авторизации, но если их заблочили, то будут блокироваться быстрее). А если паранойя, то правильно будет именно оторвать доступ к owa при отключении пользователя, причем ещё до того, как экспортировать его почтовый ящик. Экспорт может занять довольно много времени, если ящик большой, а канал внезапно узкий.


            1. gotch
              18.05.2017 18:35

              Сложно всегда побороться с штатной работой продукта.
              А побыстрее отбивать, думаю получится через Disable-Mailbox, Update-StoreMailboxState, Connect-Mailbox к временной учетке и New-MailboxExportRequest.


          1. kotorr
            19.05.2017 08:43

            Я не хочу разводить тут спор, но мы проводили тесты в продакшене. Доступ у заблокированной учетки остается до 24 часов и кэш тут не причем. Exchange получает инфу о отключении учетки только во время maintenance mode, что может длиться довольно долго.
            Поэтому мы предпочли ручками отключать эту mailbox.


  1. Openmsk
    18.05.2017 20:38

    Отличная тема! Спасибо!
    Может быть всю вашу задумку я не буду использовать, но частично обязательно реализую!


  1. brainfair
    19.05.2017 04:13

    Можно так же использовать отрубание функционала для пользователя:

    set-casmailbox -owaenabled $false -activesyncenabled $false -popenabled $false -imapenabled $false -mapienabled $false -owafordevicesenabled $false


    Так же если используете линк, то обязательно делать disable юзеру, потому что даже если его заблочить, сменить пароль, еще с неделю он сможет использовать линк.

    get-csuser %username% | disable-csuser


  1. ildarz
    19.05.2017 18:21
    +1

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


    Чисто по самому скрипту:


    Отдел кадров в профиле сотрудника выставляет ему статус «Уволен» и скрипт начинает работать.

    … работать, и еще раз работать, перелопачивая ВСЕ (!) отключенные учетки вместо того, чтобы работать только с теми, для которых выставлена галка.


    Проверяем, есть ли уже папка Год.Месяц, если нет, то создаем.

    Зачем вы вообще делаете эту проверку, если в дальнейшем всё равно нет никакой обработки ошибок? А вот не создался у вас путь по какой-то причине, что будет? Если вы только создаете файл — это ещё не так страшно. А если бы удаляли, да по маске?


    Ждем, пока скрипт закончит работу. Ждать нужно обязательно, т.к. дальше мы переводим ящики в статус Disable и хотим быть уверены, что перед этим выгрузка почты закончилась.

    А как вы можете быть в этом уверены, если вы даже статус задачи не проверяете по её окончании? А если там не Completed, а Failed?


    Get-MailboxExportRequest -Status Completed | Remove-MailboxExportRequest -Confirm:$false

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


    В своей фирме мы прицепили эту обработку к кастомной кнопке в 1С.

    А вы как-то учитывали, что у скрипта время исполнения, мягко говоря, ненулевое? Продумывали, что будет, если несколько инстансов скрипта окажутся запущены параллельно?


    В общем, тут принцип примерно тот же, что и бэкапами — либо на чужом опыте учитесь обрабатывать ошибки и ограничивать область действия скриптов, либо в один прекрасный момент на горьком своём. :) Хотя, конечно, есть везучие люди, которым и бэкапы ни разу в жизни не понадобились, и все команды в скриптах всегда отрабатывают штатно...


  1. whiplash
    23.05.2017 17:12

    Молодцы, расширили мою идею)

    Не могу не добавить, что до 24 часов у сотрудника скорее всего будет доступ в почту через OWA/ActiveSync.
    Чтобы этого избежать — нужно делать reload APP пулов в IIS CAS Exchange.


    1. vesper-bot
      23.05.2017 17:34

      То есть вот откуда эта засада по доступности OWA после блокирования учетной записи… Надо учесть, потому что такая реализация на IIS это бред редкостный и дыра в безопасности. Но помимо сброса всех IIS'ов обойти эту дыру можно, оторвав OWA и ActiveSync:


      set-mailbox $user -activesyncenabled:$false -owaenabled:$false


      Источник. Если посмотрите, в ветке выше эта проблема была заявлена.


  1. HomeDimoN
    25.05.2017 12:41
    +1

    Может пригодиться:

    Хранение настроек скрипта в файле
    Хранение настроек скрипта в файле, после можно подписать файл скрипта, что бы избежать несанкционированного изменения скрипта…

    #region Get setting from config
    $myDir = Split-Path -Parent $MyInvocation.MyCommand.Path
    #$myDir = "$pwd\"
    If (!(Test-Path $MyDir\Settings.xml))  {write-host 'Setting file ' $MyDir\Settings.xml ' not found..' -ForegroundColor Red ; exit}
    [xml]$ConfigFile = Get-Content "$MyDir\Settings.xml"
    if ($ConfigFile -eq $null) {write-host 'Setting file ' $MyDir\Settings.xml ' has error...' -ForegroundColor Red ; exit}
    
    $global:Path2Export = $ConfigFile.Settings.Path2Export
    $global:SecondsToSleep = $ConfigFile.Settings.SecondsToSleep
    $global:ExportGroupName = $ConfigFile.Settings.ExportGroupName
    $global:ExchangeServer = $ConfigFile.Settings.ExchangeServer
    #endregion 
    

    Пример XML
    <?xml version="1.0"?>
    <Settings>
      <Path2Export>\\FileServer.contoso.com\BackupPst$\</Path2Export>
      <ExportGroupName>ExportDisableUser2PST</ExportGroupName>   
      <SecondsToSleep>30</SecondsToSleep>
      <ExchangeServer>ExchangeServer.contoso.com</ExchangeServer>
    </Settings>