Всех привествую,

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

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

Прикинув, решил, почему-бы не переложить все действия на код и заставить сервер самостоятельно с переодичностью выгружать неактивные учетки. И принялся за реализацию открыв уже родной PowerShell ISE.

В начале скрипта определяю несколько переменных,

# Vars: 
$ExchangeServer = "exch-srv01.mylocaldomain"
$PSTArchive = "\\archive-srv01\pstfiles"
$YearStamp = $(get-date -Format "yyyy")
  • $ExchangeServer - данная переменная содержит значение адреса exchange сервера, к которому мы будет подключатся в последующем.
  • $PSTArchive - здесь хранится значение пути, куда мы будем выгружать почтовые ящики.
  • $YearStamp - тут в значении подставится результат выполнение команды Get-Data, которая отдаст результат значения текущего года. Нужно это для ротации почтовых ящиков, по годам.

Далее мы создаем новую сессию и подключаемся к Exchange Server:

# Connect to Exchange server:
$LocalSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "http://$ExchangeServer/Powershell" -Authentication Kerberos
Import-PSSession -Session $LocalSession -AllowClobber -DisableNameChecking

После того, как переменные определены и мы подключились к Exchange. В архивном каталоге проверяем создан ли каталог с указанием текущего года. И если данного каталога нету, создаем новую директорию.

Реализовано это условным оператором:

# Create folder in archive server:
if (!(Test-Path -Path "$PSTArchive\$YearStamp")) {
    New-Item -Path "$PSTArchive\$YearStamp" -ItemType Directory
}

В условии оператора if подставляется результат команды Test-Path. И если возвращается false, тогда будет создан каталог.

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

Собственно переменная, в значение которой подставляется результат команды Get-Mailbox, где через пайп происходит выборка пользователей у которых в значение аттрибута OrganizationalUnit указан нужный нам контейнер AD.

# Get all mailboxes from resigned staff
$Mailboxes = get-mailbox | where {$_.OrganizationalUnit -eq 'mylocaldomain/Users/Resigned'}

Далее полученный список перебираем в цикле.

foreach ($Mailbox in $Mailboxes) {
    .....
}

В теле данного цикла, сначало выполниться условие которое проверит наличие ящика для пользователя. И если pst-файлика нету, запустится выгрузка.

if (!(Test-Path -Path "$($PSTArchive)\$YearStamp\$($Mailbox.Alias).pst")) {
        ....
}

Внутри данного условия первочередно следует команда, которая просто в консоль принтует сообщение:

Write-Host -ForegroundColor Green "Create mailbox request for user $($Mailbox.Alias)"
  • $($Mailbox.Alias) - отобразит логин пользователя.

Далее создается новый запрос на выгрузку pst-файла:

$ExportsStats = New-MailboxExportRequest -Mailbox $Mailbox.Identity -FilePath "$($PSTArchive)\$YearStamp\$($Mailbox.Alias).pst"

В качестве аргументов, передаем:

  • $Mailbox.Identity - id объекта для выгрузки, на практике это просто логин пользователя,
  • $($PSTArchive)\$YearStamp\$($Mailbox.Alias).pst - составная переменная, которая собирает путь до архивной папки и название pst-файла с расришением .pst. Будет создан файлик, по типу - IvanovE.pst.

Запрос на выгрузку почты запускается в бекграунд, и не привязывается к текущему процессу выполнения скрипта. На мой взгляд это показалось не удобным, так как после экспорта я хочу выполнять еще несколько операций. Таких как, удаление export-запроса. Да, их нужно удалить за собой =)

Ну и второй момент, это удаление уже выгруженного ящика, только ящика (не учетки в AD).

Единственное решение, пришедшее на ум. Это добавить еще один цикл while, в теле которого будет проверятся статус выполнения экспорта.

While (($ExportsStats.Status -eq "InProgress") -or ($ExportsStats.Status -eq "Queued")) {
    Start-sleep 5
    $ExportsStats = Get-MailboxExportRequest -Mailbox $Mailbox.Identity
    $ExportsStats | Get-MailboxExportRequestStatistics | ft SourceAlias, PercentComplete
}

В данном случаи цикл будет повторяться снова и снова, если статус текущего экспорта равен InProgress или Queued. Этим обходным способом, мы тормозим запуск экспорта по следующему пользователю.

Завершением, мы проверяем что экспорт статус выполнен Completed. После удаляем запрос и отключаем почтовый ящик пользователя (При этом не удаляя из AD).

if ($ExportsStats.Status -eq "Completed") {
    $ExportsStats | Remove-MailboxExportRequest -Confirm:$false
    $ExportsStats | Disable-Mailbox -Confirm:$false
}

В конец скрипта добавляем,

# Kill currently Exchange connection: 
Remove-PSSession $LocalSession

Эта команда прибивает ранее созданную сессию с Exchange сервером.

Задача почти реализованна, по итогу получился вот такой сниппет:

# Vars: 
$ExchangeServer = "exch-srv01.mylocaldomain"
$PSTArchive = "\\archive-srv01\pstfiles"
$YearStamp = $(get-date -Format "yyyy")

# Connect to Exchange server:
$LocalSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "http://$ExchangeServer/Powershell" -Authentication Kerberos
Import-PSSession -Session $LocalSession -AllowClobber -DisableNameChecking

# Create folder in archive server:
if (!(Test-Path -Path "$PSTArchive\$YearStamp")) {
    New-Item -Path "$PSTArchive\$YearStamp" -ItemType Directory
}

# Get all mailboxes from resigned staff
$Mailboxes = get-mailbox | where {$_.OrganizationalUnit -eq 'mylocaldomain/Users/Resigned'}

foreach ($Mailbox in $Mailboxes) {
    if (!(Test-Path -Path "$($PSTArchive)\$YearStamp\$($Mailbox.Alias).pst")) {
        # Print export message
        Write-Host -ForegroundColor Green "Create mailbox request for user $($Mailbox.Alias)"
        
        $ExportsStats = New-MailboxExportRequest -Mailbox $Mailbox.Identity -FilePath "$($PSTArchive)\$YearStamp\$($Mailbox.Alias).pst"

        While (($ExportsStats.Status -eq "InProgress") -or ($ExportsStats.Status -eq "Queued")) {
            Start-sleep 5
            $ExportsStats = Get-MailboxExportRequest -Mailbox $Mailbox.Identity
            $ExportsStats | Get-MailboxExportRequestStatistics | ft SourceAlias, PercentComplete
        }

        if ($ExportsStats.Status -eq "Completed") {
            $ExportsStats | Remove-MailboxExportRequest -Confirm:$false
            #$ExportsStats | Disable-Mailbox -Confirm:$false
        }
    }
}

# Kill currently Exchange connection: 
Remove-PSSession $LocalSession

Теперь остается только прикрутить скриптец к Task Scheduler, для запуска один раз в неделю или более. И можно считать, что задача реализована.

В зависимости от условий и правил в вашей конторе, можно адаптировать скриптец под реалии.

Основная проблема, с которой пришлось мне столкнуться на раннем этапе, это решить каким способом отбирать уже неактивные почтовые ящики. Были мысли это сделать через AD, но потыкав Exchange-консоль пришла идея сделать так, как показано выше.. =)