Проблема: серверы Exchange 2013 с последним кумулятивом начинают отправлять массово NDR сообщения на адрес inboundproxy@contoso.com.

Известно, что адрес inboundproxy@contoso.com (начиная с Exchange 2013 CU2) используется механизмом Managed Availability для проверки хождения почты внутри почтовой организации между почтовыми ящиками типа Health Mailbox. Поэтому первым делом смотрим состояние этих ящиков:

Get-Mailbox –Monitoring

По каждому ящику получаем забавный вывод:

WARNING: The object child.domain.local/Microsoft Exchange System Objects/Monitoring Mailboxes/
HealthMailbox<guid> has been corrupted, and it's in an inconsistent state. The following 
validation errors happened:
WARNING: Database is mandatory on UserMailbox.
WARNING: Database is mandatory on UserMailbox.

Видно, что ящики поломались – не хватает аттрибута Database. Самый простой способ починить – пересоздать эти ящики. Подробнее процедура описана тут. Итак, ящики пересоздали. Get-Mailbox не возвращает ошибок по новым ящикам. Смотрим дальше – проблема не пропала. Самое время посмотреть тело отправляемого NDR. Если Exchange отправляет почту через сторонний почтовый сервер, то эти письма можно поискать там. Если отправка идёт напрямую, то такие сообщения будут копиться в очередях на самих серверах Exchange. Как их выгрузить в последнем случае можно посмотреть, например, так:

Get-Queue server\idForInboundProxy
Get-Message -Queue server\idForInboundProxy | Suspend-Message -Confirm:$false
Export-Message server\idForInboundProxy\MessageID | AssembleMessage -Path c:\temp\Health1.eml

В моем случае ошибка была следующая:

HealthMailbox<guid>@domain.local
Remote Server returned '550 5.1.1 RESOLVER.ADR.RecipNotFound; not found'

Недвусмысленный намёк, что managed Availability не может найти адрес, на который отправляет почту. Тут есть тонкий момент – все почтовые ящики находятся в дочернем домене child.domain.com. Основной принимающий домен (accepted domain) – корневой домен domain.local. Причём, этот домен не используется в дефолтной политике почтовых адресов (email address policy). И скорее всего, именно поэтому, при пересоздании почтовых ящиков Managed Availability они создались без smtp-адреса из корневого домена domain.local. Поэтому проверки Managed Availability не смогли отправить почту на адрес из корневого домена и сформировался NDR. Остался неразрешённым один вопрос – почему Managed Availability использовал не основной адрес ящика здоровья, а суррогат, сформированный по правилу alias@domain.local. Есть следующая статья в базе знаний, в которой описан обходной путь для решения этой проблемы, и даже указано, что она решена в Exchange 2016 CU4.

Ссылки:
Exchange 2013/2016 Monitoring Mailboxes
Queues Building to inboundproxy.com Domain
Messages for the health mailboxes are stuck in queue on Exchange Server 2016
Dealing with Health Proxy Probe Messages in Exchange 2013 Managed Availability
Exchange Server 2013 and the Inboundproxy.com NDR Message Problem
Managed Availability messages are journaled in Exchange Server 2013

Для управления серверами Exchange через DSC написан специальный модуль xExchange. В нынешнем виде (1.19.0.0) он работает отлично, но не всегда. Например ресурс xExchMaintenanceMode работает не совсем так как ожидается. Если вывести сервер в режим обслуживания, то с точки зрения xExchMaintenanceMode он не будет выведен в режим обслуживания.

Дело тут вот в чём. xExchMaintenanceMode использует следующую команду для вывода сервера-члена DAG в режим обслуживания (строки 207/211 файла MSFT_xExchMaintenanceMode.psm1):

$startDagServerMaintenanceScript -serverName $env:COMPUTERNAME -Verbose

В результате выполнения этой команды узел отказоустойчивого кластера не ставится на паузу. При этом, одно из условий успешного выполнения конфигурации – состояние узла кластера не принимает значение “Up” (строки 370-374)

 if ($maintenanceModeStatus.ClusterNode.State -eq "Up")
{
    Write-Verbose "Cluster node has a status of Up"
    return $false
}

Чтобы это условие выполнилось необходимо в команде вывода сервера в режим обслуживания добавить ключ -PauseClusterNode в строки 207/211 файла MSFT_xExchMaintenanceMode.psm1.

Однако, самые опытные могут сразу же обратиться на страницу с проблемами ресурса xExchange и найти запись под номером 209, в которой как раз и говорится о наличии этой проблемы 🙂

Ссылки:
PowerShell/xExchange
Problem with xExchMaintenanceMode #209

Наконец-то пришло время закончить цикл по миграции на Exchange 2016. В последней заметке мы удалили все серверы с ролью Mailbox с сопутствующими данными. У нас остались серверы с ролями Client Access Server и Hub Transport. До запуска процесса их удаления необходимо удалить CAS Array, который может быть связан с выводимыми из эксплуатации серверами. Exchange 2016 этот объект не использует, так что можно его удалять безболезненно:

Get-ClientAccessArray | Remove-CLientAccessArray

Далее штатным образом удаляем сами роли:

Setup.com /mode:Uninstall /role:CA,HT

На этом миграцию можно считать завершённой.

Задача: настроить управление конфигурацией серверов Exchange через DSC.

Для решения задачи энтузиастами был написан соответствующий модуль для DSC, который называется xExchange. Модуль требует для выполнения передачи в конфигурации пароля, который используется для запуска удалённой сессии WinRM для подключения к серверу Exchange. Чтобы не стать героем интернет-баек не стоит такие данные передавать в открытом виде, благо DSC даёт механизм шифрования паролей с использованием сертификата. Схема описана в официальной документации тут. Проблема в том, что в случае если Exchange устанавливается на Windows Server 2012 R2 (не важно какой версии – 2013 или 2016) эта схема не работает.

Давайте разбираться почему.

Шифрование пароля в модуле PSDesiredStateConfiguration реализовано через функцию Get-EncryptedPassword. Полный листинг функции можно посмотреть в файле PSDesiredStateConfiguration.psm1. В кратце, базовая версия DSC в составе WMF 4.0, который идёт в комплекте с Windows Server 2012 R2 (и единственное сочетание, которое поддерживается в связке Windows Server 2012 R2/Exchange 2013/16) реализуется так:

# Cast the public key correctly
$rsaProvider = [System.Security.Cryptography.RSACryptoServiceProvider]$cert.PublicKey.Key

# Convert to a byte array
$keybytes = [System.Text.Encoding]::UNICODE.GetBytes($value)

# Add a null terminator to the byte array
$keybytes += 0
$keybytes += 0

# Encrypt using the public key
$encbytes = $rsaProvider.Encrypt($keybytes, $false)

# Reverse bytes for unmanaged decryption
[Array]::Reverse($encbytes)

# Return a string
[Convert]::ToBase64String($encbytes)

А вот как это реализуется в обновлённой версии DSC, которая идёт в комплекте с WMF 5.0/5.1:

# Encrypt using the public key
$encMsg =Protect-CmsMessage -To $CmsMessageRecipient -Content $Value

# Reverse bytes for unmanaged decryption
#[Array]::Reverse($encbytes)

#$encMsg = $encMsg -replace '-----BEGIN CMS-----',''
#$encMsg = $encMsg -replace "`n",''
#$encMsg = $encMsg -replace '-----END CMS-----',''

return $encMsg

Найдите 10 отличий. Всё это приводит к тому, что mof-файл сгенерированный на сервере Windows Server 2016 (WMF 5.1) содержит пароль, который не может расшифровать DSC который идёт с WMF 4.0 на Windows Server 2012 R2.

Вывод: все mof-файлы для серверов Exchange установленных на Windows Server 2012 R2 необходимо генерировать на сервере Windows Server 2012 R2 с идущим в комплекте фрэймворком WMF 4.0.

Багофича: после обновления до Exchange 2016 CU8 перестаёт работать аутентификация в EWS для учёток, которые не имеют почтового ящика.

Сценарий: есть ящик, который собирает почтовый трафик, есть робот, который разбирает почту в этом ящике и работает в контексте сервисной учётки. Сервисная учётка не имеет почтового ящика (логично – зачем он ей, мы же почту не планируем получать?). После обновления до Exchange 2016 CU8 этот сценарий становится неработоспособным до того момента, пока мы не создадим почтовый ящик для сервисной учётки.

В логах IIS события подключения к почтовому ящику будут отображаться с 500 ошибками (Internal Server Error). Если копнуть глубже и посмотреть логи EWS, то в них будет более интересная запись:

ServiceDiagnostics_ReportException=System.ArgumentException: mailboxGuid
    at Microsoft.Exchange.Data.Directory.MailboxLocationInfo.ValidateMailboxInfo()

В общем будет явно намекать на то, что аутентифицирующийся имеет проблемы с аттрибутом mailboxGuid, что неудивительно, так как он этим аттрибутом не обладает.

Пока никаких официальных статей со стороны MS нет. Инсайдеры говорят, что внутри команды разработки сейчас идёт дискуссию по этому поводу. Рекомендаций по правильному процессу аутентификации при использовании сценария delegated mailbox так же пока добиться не удалось.

Во времена Exchange 2010, если было необходимо найти и удалить письмо из почтовых ящиков пользователей использовался мега-командлет швейцарский нож Search-Mailbox, который позволял найти письма в нескольких почтовых ящиках, скопировать их куда надо и затем удалить. Этот командлет отлично работал при поиске и удалении, если необходимо было просмотреть несколько сотен почтовых ящиков. Если же ящиков было больше тысячи – начинались проблемы. Например, вот такие:

Единственное решение проблемы – разбивать ящики на группы и запускать поиск по группам меньшего размера. Например, как описано тут:

Get-Mailbox -Database  databasename | Search-Mailbox -SearchQuery 
'(from:spam@spamorg.com) AND (sent:1/1/2008..11/22/2012)' -DeleteContent -Force

Или даже так:

$allmbxinyourorg = Get-Mailbox -ResultSize unlimited
Foreach ($mbx in $allmbxinyourorg) {
Search-Mailbox -identity $mbx -SearchQuery '(from:spam@spamorg.com) 
AND (sent:1/1/2008..11/22/2012)' -DeleteContent –Force
}

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

В Exchange 2013 была попытка уйти от этой проблемы с помощью командлета New-MailboxSearch. Но, к сожалению, он не позволял удалять письма, только найти и скопировать в ящик/выгрузить в pst-файл.

В Exchange 2016 сделана очередная попытка дать инструмент для быстрого поиска и удаления писем. Это командлеты New-ComplianceSearch и New-ComplianceSearchAction. Попытка получилась так себе, но местами очень даже ничего. Подробно процесс описан в следующих документах:

Use Compliance Search to search all mailboxes in Exchange 2016
Search for and delete messages in Exchange 2016

Но есть проблема – Compliance Search не умеет удалять письма. В общем эти документы стоит прочитать и забыть. Вот как нужно делать поиск и удаление в настоящей почтовой организации:

  • Запускаем Compliance Search:
New-ComplianceSearch -Name Search1 -ExchangeLocation all -ContentMatchQuery 'from:"@corp.com"'
Start-ComplianceSearch -Identity Search1
  • Получаем список почтовых ящиков, в которых есть нужные нам письма:
$search = Get-ComplianceSearch –Identity Search1
$results = $search.SuccessResults
$mailboxes = @()
$lines = $results -split '[\r\n]+'
foreach ($line in $lines)
{
    if ($line -match 'Location: (\S+),.+Item count: (\d+)' -and $matches[2] -gt 0)
    {
        $mailboxes += $matches[1]
    }
}
  • Запускаем удаление писем из ящиков п.2:
$mailboxes | Get-Mailbox| Search-Mailbox -SearchQuery 'from:"@corp.com"' -DeleteContent -Force
  • Подчищаем следы:
Remove-ComplianceSearch –Identity Search1
Remove-MailboxSearch –Identity Search1-shadow

Первые два шага позволяют значительно сузить область поиска, что сильно скажется на времени работы итогового поиска через Search-Mailbox. Compliance Search на нескольких тысячах ящиков отрабатывает за пару минут. Если итоговый список ящиков для поиска получится в результате этого сократить даже вдвое, то это будет почти двухкратный выигрыш во времени.

Есть, правда, и в этой бочке мёда ложка дёгтя:

  • Результат работы Compliance Search будет выглядеть в виде 1000 почтовых ящиков, отсортированных по количеству удовлетворяющих условию поиска писем. Если ящиков, в которых находится требуемое письмо больше 1000, то процедуру придётся повторить. До тех пор, пока не будут удалены все нужные письма.

Полезные ссылки:
Exchange Massive Search and Destroy & Quota control
Use Compliance Search to search all mailboxes in Exchange 2016
Search for and delete messages in Exchange 2016
Advanced Query Syntax
QueryString (QueryStringType)

С момента своего появления в Exchange 2007 служба автообнаружения используется в Outlook по известному заранее сценарию:

  1. Outlook пытается подключиться к AD и получить список SCP для своего сайта
  2. Если п.1 не выполнен, то идёт обращение по адресу https://<SMTP-address-domain>/autodiscover/autodiscover.xml
  3. Если п.2 не выполнен, то идёт обращение по адресу https://autodiscover.<SMTP-address-domain>/autodiscover/autodiscover.xml
  4. Если п.3 не выполнен, то идёт попытка редиректа, настроенного для http://autodiscover.<SMTP-address-domain>/autodiscover/autodiscover.xml
  5. Если п.4 не выполнен, то идёт обращение к SRV-записи _autodiscover._tcp.<SMTP-address-domain>

Этот порядок поиска службы автообнаружения на клиенте можно немного подправить. Для каждого из этих шагов доступна настройка в реестре, которая позволяет этот шаг пропустить. Подробнее тут.

Все настройки выполняются в ветке реестра HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\Outlook\AutoDiscover. Доступны следующие ключи:

"ExcludeScpLookup"=dword:00000001
"ExcludeHttpsRootDomain"=dword:00000001
"ExcludeHttpsAutoDiscoverDomain"=dword:00000001
"ExcludeHttpRedirect"=dword:00000001
"ExcludeSrvRecord"=dword:00000001

Полезные ссылки:
Autodiscover for Exchange
Autodiscover service
Outlook 2016 Implementation of Autodiscover
Plan to automatically configure user accounts in Outlook 2010

Active Directory предоставляет штатный механизм изменения типа группы (Distribution ↔ Security).

В случае, если у нас имеется Exchange, этого недостаточно. Необходимо так же на стороне Exchange запустить процесс обновления типа группы. В общем то это совсем не новость, так как механизм автоматической конвертации группы распространения в группу безопасности пропал в Exchange 2007 и с тех пор назад не возвращался. Поэтому, если изменение Distribution ↔ Security было выполнено на стороне Active Directory необходимо после этого запустить следующий командлет:

Set-Distributiongroup –identity <DistributionGroupId> –MemberDepartRestriction Closed

Если этого не сделать, то все операции назначения прав доступа для новоиспечённой группы безопасности на стороне Exchange будут давать сбои: нельзя будет назначать на группу права на ящики, на группы рассылок, на календари итд. итп.

Полезные ссылки:
Modify Group Type or Group Scope
How to change a UDG to a USG in Exchange 2010
Automatic converison of UDG in USG in Exchange 2007
EXCHANGE 2010 – ADD OUTLOOK CALENDAR PERMISSIONS FOR ALL MEMBERS OF A GROUP

Достаточно давно, почти сразу после появления, я делал обзор функции онлайн-архива, которая позволяла перемещать в архивный ящик письма, старше определённого возраста. С тех пор прошло достаточно много времени. Саму функциональность переименовали в In-Place Archive. Научились работать со стандартными папками Tasks и Calendar.

Недавно столкнулся с одним странным случаем. Имеется политика хранения, которая содержит DPT для архивирования, RPT для папки Tasks и несколько пользовательских тэгов. RPT для папки Tasks настроен на бессрочное хранение задач (Never Delete), DPT переносит в архив объекты старше некоторого срока (предположим, старше одного года). Согласно документации DPT применяется только к объектам, которые не помечены другими тэгами. Поэтому, вроде бы, под действие этого тэга не должны попадать задачи (помечены RPT для папки Tasks) и все папки и письма, которые помечены пользователем с помощью пользовательских тэгов.

На самом деле это не так. Задачи из папки Tasks будут попадать под действие DPT и уезжать в архив, согласно настройкам дефолтного тэга.

Кто виноват?

Предполагаю, что дело в следующем. Известно, что весь процесс работы политик хранения построен на MAPI-аттрибутах RetentionPeriod/PR_RETENTION_PERIOD, RetentionDate/PR_RETENTION_DATE, PolicyTag/PR_POLICY_TAG, ArchivePeriod/PR_ARCHIVE_PERIOD, ArchiveDate/PR_ARCHIVE_DATE и ArchiveTag/PR_ARCHIVE_TAG. Первые три связаны с настройками политик хранения (период хранения, дата истечения хранения и применённый тэг хранения), последние три с настройками архивирования (период хранения до запуска процесса архивирования, дата истечения хранения перед перемещением в архив и применённый тэг архивирования). На основании этих аттрибутов соответствующий ассистент (робот) на почтовом сервере обрабатывает объекты в почтовом ящике. Для новых объектов согласно применённым тэгам проставляются сроки хранения и время истечения сроков хранения. Для уже помеченых объектов принимается решение нужно ли с ними что-то делать, если срок хранения истёк.

С нашим странным случаем получается забавная ситуация. Ассистент видит, что существует DPT с действием MoveToArchive. Для задач из папки Tasks архивные MAPI-аттрибуты не проставлены, так как архивирование для папок Tasks и Calendar недоступно. На основании этого ассистент принимает(моё предположение) решение, что задачи попадают под действие DPT и начинает их перемещать в архив согласно настройкам DPT.

Что делать?

MS предлагает в данной ситуации один вариант – отключить для ассистента возможность обрабатывать объекты из папок Tasks/Calendar. Делается это нашим любимым способом – через пятую точкуреестр. Нужен следующий ключ на всех почтовых серверах, где хранятся базы с ящиками пользователей:

Path: HKLM\SYSTEM\CurrentControlSet\Services\MSExchangeMailboxAssistants\Parameters
Name: ELCAssistantCalendarTaskRetentionEnabled
Type: DWORD
Value: 0 (Do not process Calendar and Task folders)

Полезные ссылки:
Retention tags and retention policies in Exchange 2016
Prevent archiving of items in a default folder in Exchange 2010
Calendar and Tasks Retention Tag Support in Exchange 2010 SP2 RU4
Retention policy on Calendar and Task folders. Confused ?
DPT with move to archive action takes precedence over RPT.
Default folders that support Retention Policy Tags
Архивирование в Exchange 2010 SP1

Обновление 2: разработчики обещают, что баг будет исправлен в мартовском Exchange 2016 CU9.

Обновление: как доносит народная молва это поведение появилось в Exchange 2016 CU4. При отключении ящика не очищается аттрибут legacyExchangeDN. Заведён баг.

Ситуация: сотрудник переходит из одного подразделения в другое. На предыдущем месте работы он использовал линкованый ящик. На новом месте будет использовать обычный. Вроде бы штатная операция – необходимо отключить линкованый ящик (Disconnect-Mailbox) и подключить его к новой учётной записи (Connect-Mailbox). После очистки базы (Update-StoreMailboxState) получаем рабочий OWA и нерабочий Outlook. Профиль настраивается, но при запуске Outlook возвращается ошибка, что нет доступа к дереву папок. Картинка будет примерно такая:

В логе MAPI/HTTP на бэкэнде нашлась вот такая вот интересная запись:

MoMTException:2147746065 (rpc LoginFailure) -> [LoginFailureException] Unable to access AD 
(StoreError=LoginFailure) -> [StoragePermanentException] There was a problem accessing Active 
Directory. Check your network connections and try again. -> [ValueNotPresentException] 
Property 'IsSoftDeletedByRemove' is not present on object 'Some User Mailbox'.

В общем понятно, что ничего не понятно. Однако, беглый обзор аттрибутов двух учёток (новой, и старой от линкованого ящика) позволил обнаружить, что обе учётки имеют один и тот же аттрибут legacyExchangeDN. После очистки этого аттрибута на старой учётке Outlook сразу же запустился.

Аттрибут этот очень хитрый. Внутри Exchange использует именно его для маршрутизации почты. Поэтому его задвоение как минимум приводит к невозможности нормальной работы пострадавшего почтового ящика. Как оказывается он так же участвует и в процессе аутентификации.

Полезные ссылки:
Clean-MailboxDatabase in Exchange 2013
The Attribute, the Myth, the legacyExchangeDN