Согласно рекомендациям лучших собаководов при внедрении cross-forest сценария, когда мы условно растягиваем почтовую организацию на 2 леса каждый получатель будет представлен в виде двух объектов. В одном лесе он будет обладать полноценным ящиком, в другом лесе для него будет создан объект Mail Enabled User (или Contact, если ящик мигрировать из леса в лес не планируется). В последний рекомендуется копировать пачку атрибутов из объекта, который обладает почтовым ящиком. Примерный список атрибутов для такого копирования можно подсмотреть, например тут и тут.

Example of Exchange 2010 multiple forest

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

  • mAPIRecipient = TRUE
  • msExchMasterAccountSID = objectSID from Mailbox
  • msExchOriginatingForest = Target Forest FQDN
  • msExchRecipientDisplayType = –1073741818
  • proxyAddresses = X500: + LegacyExchangeDN from Mailbox; existing addresses

Это позволит пользователю с ящиком одного леса назначать права на свой ящик (делегировать доступ) объекту MEU, который связан с почтовым ящиком из другого леса. Это позволит почтовому ящику из другого леса получить доступ к тем ресурсам, на которые ему выдал права пользователь из первого леса.

Полезные ссылки:
Deploy Exchange 2013 in a cross-forest topology
Prepare mailboxes for cross-forest move requests
Prepare mailboxes for cross-forest moves using the Exchange Management Shell
Exchange Server 2010 Cross Forest Delegation
[TUTORIEL]: Partage entre Organisations Exchange (partie 2: coexistence “riche”)
Cross Forest Exchange Impersonation – where the rubber meets the road

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

Папка /Audits при этом не пустая:

[PS] C:\Windows\system32>Get-MailboxFolderStatistics  mailbox | ? {$_.Name -eq "Audits"} | 
fl FolderPath, ItemsInFolder
...
FolderPath                        : /Audits
ItemsInFolder                     : 10

Это говорит о том, что логи аудита таки собираются, но почему-то при их запросе сервер ничего не возвращает. А не возвращает он из-за того, что локаль на сервере выставлена в Russia (то есть не в United States).

Ссылки:
Search-AdminAuditLog or Search-MailboxAuditLog with parameter returns empty results in Exchange Server
No results using the Search-MailboxAuditLog cmdlet with Exchange 2013 CU4+

Проблема: серверы 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

Относительно недавно обновился базовый пакет с основными библиотеками DSC. И вот что я хочу сказать по этому поводу. DSC отлично подходит для разработки и разработчиков. И очень плохо подходит для поддержки и системных администраторов.

Я вижу следующие стоп-факторы в использовании DSC как инструмента автоматизации:

  • DSC по своей архитектуре работает исключительно в рамках конкретного хоста. Это приводит к тому, что если мы хост перегружаем, то логика работы движка DSC сбрасывается. DSC начинает заново выполнять полученный от администратора скрипт, а не с того момента, где в скрипте возникла команда на перезагрузку сервера. Потенциально, этот стоп-фактор может снять настройка ActionAfterReboot LCM’а, которая появилась в Windows Server 2016
  • DSC не имеет штатного инструмента для постановки выполнения скрипта на паузу. Администратор должен переписывать базовые модули, чтобы этот инструмент можно было таки использовать
  • DSC по своей архитектуре постоянно проигрывает скрипт, который получил от администратора. Если у нас в скрипте будет заложена стандартная логика “вывести ресурс из под нагрузки” ⇒ “обновить” ⇒ “вернуть ресурс в работу”, то эта логика будет постоянно проигрываться движком DSC. Ни один штатный ресурс (за исключением разве что xExchange) не предусматривает проверки версионности конфигурации. Следовательно, сразу после завершения скрипта нужно будет быстро-быстро бежать и затирать всю полученную перед обновлением конфигурацию, как это описано, например, тут

Поэтому, коллеги, не верьте политруку, политрук лжёт 🙂

Ссылки:
DSC Resource Kit Release June 2018
How to remove all PowerShell DSC configuration documents (MOF files)?

Для управления серверами 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

При использовании push-метода применения конфигураций на серверы существует одна проблема – DSC на целевой системе не проверяет наличие необходимых ресурсов DSC. Это может привести к тому, что конфигурация применится не полностью. Что нас конечно же не устраивает. Поэтому необходимо обеспечить наличие всех необходимых ресурсов DSC, которые используются в конфигурации. Тут возможны два варианты:

  • Использование pull-сервера для применения конфигурации
  • Ручная установка необходимых модулей

Первый вариант мы пока опустим. А вот второй вариант рассмотрим подробнее. Любой модуль с ресурсами DSC имеет вполне определённую структуру и ставится по одному из путей указанных в переменной $env:PSModulePath. Модули обычно поставляются в виде архива, который содержит всю необходимую структуру папок со всеми необходимыми файлами. Задача сводится к следующему – необходимо на целевой сервер скопировать архив, содержащий файлы модуля и распаковать его по пути, указанному в переменной $env:PSModulePath. Задачу можно выполнить средствами DSC, используя ресурсы File (для копирования) и Archive (для распаковки архива). Файл конфигурации получается примерно следующего вида:

Configuration InstallDSCRes
{
    param ($modulePath = ($env:PSModulePath -split ';' |
    ? {$_ -match 'Program Files\\WindowsPowerShell'}),
    $Server = @('someserver'))
    Node $Server
    {
        File DSCResFile
        {
            SourcePath = "\\share.server\Distr\DscRes_1.0.0.0.zip"
            DestinationPath = "c:\Distr"
            Ensure = "Present"
            Type = "File"
            Checksum = "SHA-256"
            Force = $true
        }
        Archive UnzipModule
        {
            DependsOn = "[File]DSCResFile"
            Path = "c:\Distr\DscRes_1.0.0.0.zip"
            Destination = $modulePath
            Ensure = "Present"
        }
    }
}
InstallDSCRes -OutputPath C:\Configs\dsc

Затем запускаем применение конфигурации:

Start-DscConfiguration -Path C:\Configs\dsc -Wait -Verbose

Ссылки:
Resource authoring checklist
Use DSC to Install Windows PowerShell DSC Resource Kit Bits

Наконец-то пришло время закончить цикл по миграции на 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 так же пока добиться не удалось.