PowerShell LogoПериодически приходится писать крипты, автоматизирующие те или иные действия администратора. Иногда это бывает обычный сбор информации. Иногда это внесение изменений в конфигурацию/учётные записи, связанные с наступлением тех или иных событий. Тонкость возникает там, где необходимо работать с объектами Exchange. обычно, для этого достаточно в скрипте импортировать оснастку PS Microsoft.Exchange.Management.PowerShell.E2010 (Excahnge 2010):

Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010

Однако, в случае, когда нам в скрипте необходимо использовать командлет Get-MailboxStatistics получаем следующую ошибку:

PS C:> Get-Mailbox user | Get-MailboxStatistics
Get-MailboxStatistics : Failed to commit the change on object "MB-DATABASE" because access is denied.
At line:1 char:45
+ Get-Mailbox buldakov | Get-MailboxStatistics <<<<
+ CategoryInfo          : NotSpecified: (0:Int32) [Get-MailboxStatistics], MapiAccessDeniedException
+ FullyQualifiedErrorId : 46BEBAF0,Microsoft.Exchange.Management.MapiTasks.GetMailboxStatistics

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

PS C:>Get-ManagementRoleEntry "*Get-MailboxStatistics" | ? {$_.Role -like "Mail  Recipients"} | fl Name,Role
Name : Get-MailboxStatistics
Role : Mail Recipients

PS C:>Get-ManagementRoleAssignment -Role "Mail Recipients" -GetEffectiveUsers | ? {$_.EffectiveUserName -like "svc_Name"} | fl Role,EffectiveUserName
Role              : Mail Recipients
EffectiveUserName : svc_Name

Нашёлся следующий workaround. Для подключения к Exchange используем стандартный скрипт RemoteExchange.ps1:

. 'C:Program FilesMicrosoftExchange ServerV14binRemoteExchange.ps1'
Connect-ExchangeServer -auto

Точку с пробелом в начале ставить обязательно.

e2010Следующий сценарий родился в процессе миграции пользователей с одного из сторонних почтовиков. Итак, у нас имеется набор почтовых ящиков, которые созданы для миграции со стороннего почтового решения. В некоторый момент времени к стандартным smtp-адресам (например, domain.com) добавляются smtp-адреса, которые находятся у пользователей во внешней почтовой системе (например, домен domain.net). Так как изначально процесс не сильно автоматизирован, то в некоторый момент времени оказывается, что не все почтовые ящики имеют smtp-адрес из домена domain.net. А час X (когда мы добавим домен domain.net в авторизованные) приближается и необходимо выяснить какие-же почтовые ящики мы не охватили. То есть необходимо найти те почтовые ящики (предположим, что мы их пока складируем в отдельных почтовых базах), которые примут пользователей при миграции, но при этом старый smtp-адрес для них не прописан.

Первым делом давайте посмотрим в каком виде у нас хранятся smtp-адреса. Все они находятся в свойстве EmailAddresses почтового ящика. Свойство это многозначное, то есть фактически представляет собой некий массив разнородных данных (помним, что кроме smtp-адресов в этом свойстве могут записываться и другие типы адресов, например, sip или X400). При обращении к нему выведется набор объектов, каждый из которых представляет определённый адрес, прописанный в свойствах ящика:

[PS] C:>(Get-Mailbox stbul).EmailAddresses

SmtpAddress        : stbul@domain.com
AddressString      : stbul@domain.com
ProxyAddressString : SMTP:stbul@domain.com
Prefix             : SMTP
IsPrimaryAddress   : True
PrefixString       : SMTP

AddressString      : C=RU;A= ;P=DOMAIN;O=DOMAIN;S=Buldakov (Test);G=Stanislav;
ProxyAddressString : X400:C=RU;A= ;P=DOMAIN;O=DOMAIN;S=Buldakov (Test);G=Stanislav;
Prefix             : X400
IsPrimaryAddress   : True
PrefixString       : X400

Очевидно, что работать мы будем не со всеми адресами, а только с smtp. Поэтому имеет смысл фильтровать по свойству ProxyAddressString, и выбирать только те объекты-адреса, у которых это свойство начинается с SMTP. Простейший вариант поиска только smtp-адресов будет выглядеть следующим образом:

$mailbox = Get-Mailbox somemailbox
for ($i=0; $i -lt $mailbox.EmailAddresses.Count; $i++){
    if ($mailbox.EmailAddresses[$i].ProxyAddressString -like "smtp:*"){
        $mailbox.EmailAddresses[$i]}
}

В нашем случае процесс усложняется тем, что перед нами стоит обратная задача. То есть надо собрать не все почтовые ящики с адресами в определённом домене, а те, у которых адресов из нужного нам домена нет. Один из вариантов решения данной проблемы – использование специального счётчика, который будет увеличиваться при нахождении в свойствах ящика нужного нам smtp-адреса. А в итоге, выводить будем те объекты, значение счётчика для которых будет равно нулю (то есть он не будет содержать нужного нам smtp-адреса). Итоговый скрипт получается примерно следующий:

$mailboxes = Get-MailboxDatabase new-db* | Get-Mailbox
foreach ($mailbox in $mailboxes){
    $i = 0
    $count = 0
    for ($i=0; $i -lt $mailbox.EmailAddresses.Count; $i++){
    if ($mailbox.EmailAddresses[$i].ProxyAddressString -like "smtp:*domain.net"){
        $count = $count + 1}
    }
    if ($count -eq 0){
        $mailbox.Name}
}

Аналогично, можно искать, например, группы распространения. В этом случае в переменную $mailboxes помещается набор нужных нам групп.

e2010В очередной раз натолкнулся на проблему некорректной отработки RBAC в случае работы с общими папками. Стоит задача – делегировать права на заведение mail-enabled общих папок. То есть по факту, на командлет Enable-MailPublicFolder. Право на запуск этого командлета делегировано всего одной роли:

[PS] C:Windowssystem32>Get-ManagementRoleEntry '*Enable-MailPublicFolder' | fl Name, Role

Name : Enable-MailPublicFolder
Role : Mail Enabled Public Folders

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

[PS] C:Windowssystem32>Enable-MailPublicFolder -Identity 'Public Folder'
MapiExceptionNoAccess: Unable to set properties on object. (hr=0x80070005, ec=-2147024891)
...
+ CategoryInfo          : NotSpecified: (0:Int32) [Enable-MailPublicFolder], MapiExceptionNoAccess
+ FullyQualifiedErrorId : 1CA8E050,Microsoft.Exchange.Management.MapiTasks.EnableMailPublicFolder

Самая вкуснятина в конце предпоследней строки: MapiExceptionNoAccess. Оказывается, чтобы создавать mail-enabled общие папки штатного механизма RBAC не достаточно. В Exchange 2000/2003 за процедуру создания mail-enabled общих папок отвечало специальное разрешение ms-Exch-Mail-Enabled-Public-Folder. Назначалось оно на уровне конкретной организации Exchange в разделе конфигурации в AD. Что интересно, в Exchange 2010 оно там же и осталось. Более того, этим разрешением обладают две группы – Organization Management и Public Folder Management:

[PS] C:Windowssystem32>Get-ADPermission -Identity "CN=OrgName,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=o365test,DC=pro" | ? {$_.ExtendedRights -like 'ms-Exch-Mail-Enabled-Public-Folder'} | select User

User
----
Organization Management
Public Folder Management

Ради интереса привожу полный список разрешений группы Public Folder Management на контейнер почтовой организации:

[PS] C:Windowssystem32>Get-ADPermission -Identity "CN=OrgName,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=0365test,DC=pro" | ? {$_.User -like '*Public Folder Management'} | select AccessRights, ExtendedRights

AccessRights                                                ExtendedRights
------------                                                --------------
{GenericRead}
{ExtendedRight}                                             {ms-Exch-Create-Public-Folder}
{ExtendedRight}                                             {ms-Exch-Modify-Public-Folder-Deleted-Item-Retention}
{ExtendedRight}                                             {ms-Exch-Modify-Public-Folder-Replica-List}
{ExtendedRight}                                             {ms-Exch-Modify-Public-Folder-Expiry}
{ExtendedRight}                                             {ms-Exch-Modify-PF-Admin-ACL}
{ExtendedRight}                                             {ms-Exch-Modify-Public-Folder-Quotas}
{ExtendedRight}                                             {ms-Exch-Mail-Enabled-Public-Folder}
{ExtendedRight}                                             {ms-Exch-Modify-PF-ACL}
{ExtendedRight}                                             {ms-Exch-Store-Create-Named-Properties}
{ExtendedRight}                                             {ms-Exch-Store-Admin}
{ExtendedRight}                                             {ms-Exch-Store-Visible}
{ExtendedRight}                                             {ms-Exch-Create-Top-Level-Public-Folder}

Дело осталось за малым – назначить соответствующие разрешения для нашего делегата:

Add-ADPermission -Identity "CN=OrgName,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=0365test,DC=pro" -User "Public Folders Delegates" -AccessRights GenericRead

Add-ADPermission -Identity "CN=OrgName,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=0365test,DC=pro" -User "Public Folders Delegates" -ExtendedRights ms-Exch-Mail-Enabled-Public-Folder 

Интересные ссылки:
Beyond RBAC: Delegating the ‘Mail-enable Public Folders’ right
Permissions Available in Exchange

e2010Как я написал в предыдущей заметке, для решения проблемы с невозможностью выдачи права Send-As необходимо менять владельца объекта общей папки в Active Directory. Эта задача скучная и нудная, особенно, если у нас имеется несколько сотен/тысяч таких объектов. Нам в её решении поможет PowerShell. Для начала, имеет смысл выгрузить существующих владельцев:

Set-Location ad:
Get-ADObject -LDAPFilter "(ObjectClass=publicFolder)" -SearchBase 'CN=Microsoft Exchange System Objects,DC=o365lab,DC=pro' |
select Name, DistinguishedName, @{Name="Owner";expression={(Get-Acl "$_").Owner}} |
Export-Csv -Delimiter ";" -Path c:tmppfOwners.txt -Encoding UTF8

Теперь приступаем к написанию скрипта, который всех этих владельцев будет исправлять. Командлет Get-Acl имеет метод SetOwner(), который может помочь поменять владельца объекта. Правда, для замены нам понадобится использовать командлет Set-Acl, чтобы это изменение применить к объекту. Подробнее процедура расписана здесь. В нашем случае процедура замены будет выглядеть примерно следующим образом:

$NewOwner = New-Object System.Security.Principal.NTAccount("O365LABNEWMAILSERVER$")
$PF = Get-ADObject -LDAPFilter "(ObjectClass=publicFolder)" -SearchBase 'CN=test6,CN=Microsoft Exchange System Objects,DC=o365lab,DC=pro'
$Acl = Get-Acl $PF
$Acl.SetOwner($NewOwner)
Set-Acl -AclObject $Acl -Path $PF.DistinguishedName

Осталось эту процедуру запустить в цикл, в котором она будет применена ко всем общим папкам в контейнере Microsoft Exchange System Objects:

Import-Module ActiveDirectory
Set-Location ad:

$PFs = Get-ADObject -LDAPFilter "(ObjectClass=publicFolder)" -SearchBase 'CN=Microsoft Exchange System Objects,DC=o365lab,DC=pro'
$NewOwner = New-Object System.Security.Principal.NTAccount("O365LABNEWMAILSERVER$")

ForEach ($PF in $PFs) {
$Acl = Get-Acl $PF
$Acl.SetOwner($NewOwner)
Set-Acl -AclObject $Acl -Path $PF.DistinguishedName   }

Полезные ссылки:
How Can I Use Windows PowerShell to Determine the Owner of a File?
Can I Determine a Folder’s Access Rights and Who Has Them?
Cannot add send as permission for public folder on Exchange 2010

apple-devilВ очередной раз (внезапно!) Apple устроил холокост для своих верных поклонников, которые используют Exchange в качестве корпоративной почтовой системы. В кратце – при обработке запросов на организацию встреч (meeting requests) с устройства Apple с новой прошивкой можно организовать зацикливание. Это может привести к резкому росту логов транзакций, и в случае, если не уследить за дисковым пространством, на котором хранятся логи, то можно легко положить серверы почтовых ящиков.

Рекомендации:

  • Не ставить пока прошивку iOS 6.1
  • Заблокировать устройства, которые уже обновились

Ну а для начала имеет смысл понять кто успел обновиться. Информацию по устройствам, с которых пользователь подключался к почтовому ящику даёт комадлет Get-ActivesyncDeviceStatistics. Имеет смысл отправлять в него только почтовые ящики, к которым уже подключались через мобильные устройства. У них параметр HasActivesyncDevicePartnership имеет значение true. Полный скрипт получается примерно следующий:

$Mbx = Get-CASMailbox -Filter {HasActivesyncDevicePartnership -eq $True}
-ResultSize unlimited;
$Mbx | %{$Name = $_.Name;
$Device = Get-ActiveSyncDeviceStatistics -Mailbox $_.Identity |
?{$_.DeviceUserAgent -like "Apple*1002*"} |
%{Write-Host $Name, $_.DeviceModel, $_.DeviceUserAgent, $_.DeviceId,
$_.FirstSyncTime, $_.LastSuccessSync}}

Исходный скрипт взят отсюда. Спасибо Олегу Крылову и Сергею Мариничеву за внесённые правки.

Оказывается, имеется даже целый скрипт в галерее скриптов. Спасибо комментатору Олегу.

Задача – выгрузить список общих (с которыми работают несколько пользователей) ящиков Exchange 2010 с:

  • почтовыми адресами,
  • списком тех, у кого есть полный доступ на ящик,
  • менеджером,
  • размером ящика

Итоговый вывод примерно следующий будет:

"MailboxName1":"Email1":"FAUser1;FAUser2;":"Manager1":"MBSize1"
"MailboxName2":"Email2":"FAUser3;FAUser4;":"Manager2":"MBSize2"
....

Первые два столбца получаются через

Get-Mailbox -Database "Database" -ResultSize unlimited | Select DisplayName,
PrimarySmtpAddress

Мы предполагаем, что общие ящики находятся в одной базе, и кроме них в этой базе других ящиков (пользовательских) нет. Список пользователей, имеющих права доступа мы можем получить, используя командлет Get-MailboxPermission. Причём, нас интересуют только пользователи, которым Full Access дан непосредственно на сам общий ящик, а не на объект, находящийся выше по иерархии (то есть отфильтровываем наследуемые записи для которых параметр IsInherited принимает значение $true). Заодним убираем объект NT AUTHORITYSELF.

$tmpUsersPermissions = Get-Mailbox $_ | Get-MailboxPermission |
?{($_.IsInherited -eq $false) -and ($_.user -notlike "NT AUTHORITYSELF")} |
Select User

Далее массив получённых учётных записей надо “склеить”:

$UserPermissionString = "";
foreach ($UserPermission in $tmpUsersPermissions) {
     $UserPermissionString = $UserPermissionString + $UserPermission.User + "; "}

В итоге, в переменной $UserPermissionString окажется требуемый список пользователей с правами Full Access на ящик.

Следующий, интересующий нас объект – менеджер ящика (на самом деле учётной записи, к которой прикреплён ящик). Так как сам почтовый ящик уже помещён в переменную $_, то можно её использовать для того, чтобы вытащить через Get-User из параметра Manager учётную запись менеджера почтового ящика

(Get-User $_).Manager.Name

Аналогично, через Get-MailboxStatistics и переменную $_ получаем размер ящика. Для уменьшения количества цифр конвертируем его в мегабайты

($_ | Get-MailboxStatistics).TotalItemSize.Value.ToMB()

Теперь осталось всё собрать в одну строку и вывести в csv-файл. В итоге получается примерно следующая конструкция

Get-Mailbox -Database "Database" -ResultSize unlimited | Select DisplayName,
PrimarySmtpAddress,
@{Name="FullAccess";expression={
$tmpUsersPermissions = Get-Mailbox $_ | Get-MailboxPermission |
?{($_.IsInherited -eq $false) -and ($_.user -notlike "NT AUTHORITYSELF")} |
Select User;
$UserPermissionString = "";
foreach ($UserPermission in $tmpUsersPermissions) {
     $UserPermissionString = $UserPermissionString + $UserPermission.User + "; "};
$UserPermissionString;}},
@{Name="Manager";expression={(Get-User $_).Manager.Name}},
@{Name="MailboxSize";expression={
($_ | Get-MailboxStatistics).TotalItemSize.Value.ToMB()}} |
Export-Csv c:Tempmailboxes.txt -Delimiter :

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

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

Некоторое время назад получил крайне экзотическую задачу – необходимо было посчитать число писем отправленных и полученных некоторой группой сотрудников за каждый день прошедшего месяца и вывести эту информацию в виде удобном для обработки. Так как необходимо посчитать число писем за определённый день, то необходимо использовать командлет  Get-MessageTrackingLog, причём считать будем только события “RECEIVE”. Письма, в которых сотрудник будет указан в получателях, считаются им полученными, в которых отправителем – отправленными (Капитан Очевидность умер от зависти). Данные можно поместить в две переменные $Received и $Sent:

# block1
$Received = Get-TransportServer | Get-MessageTrackingLog -ResultSize unlimited
-Start $TmpDate -End $TmpDate.AddDays(1) -EventId "RECEIVE" -Recipients $SMTP |
Measure-Object;

$Sent = Get-TransportServer | Get-MessageTrackingLog -ResultSize unlimited
-Start $TmpDate -End $TmpDate.AddDays(1) -EventId "RECEIVE" -Sender $SMTP |
Measure-Object;

Здесь в переменных $TmpDate будет находиться дата, на которую мы считаем письма, а в $SMTP – адрес конкретного сотрудника. Для удобства хранения полученной информации создадим новый объект, который будет хранить адрес сотрудника, указанную дату, число полученных и отправленных писем (спасибо Вадимсу Подансу за подсказку, как это можно сделать):

# block 2
$tmpObject = New-Object psobject -Property @{
smtp = $SMTP;
date = $TmpDate.ToShortDateString();
rcount = $Received.Count;
scount = $Sent.Count;  }

Осталось указанное выше завернуть в цикл и с шагом в один день, до тех пор, пока не упрёмся в последний нужный день посчитать письма:

$StartDate = Get-Date "01.01.2012";
$EndDate = $StartDate.AddMonths(1);
# block 3
$TmpDate = $StartDate;
While ($TmpDate -ne $EndDate) {
<block 1>
<block 2>
$TmpDate = $TmpDate.AddDays(1);
$tmpObject | select smtp, date, rcount, scount; }

Здесь $StartDate – дата, с которой начинаем считать (в эту переменную можно значение ввести через Get-Date, как это сделал я, можно через Read-Host, можно любым другим способом), $EndDate – дата, на которой заканчиваем считать письма (у меня она смещена на один месяц, по сравнению со $StartDate, но можно её указать любым другим способом). В конце собранные данные выводятся на экран, но вывод можно сделать в файл (File-Out, например).

Осталось теперь данные собрать по всем сотрудникам. Для этого их почтовые адреса передаём в переменную-массив и по каждому члену массива делаем обсчёт.

$SMTPs = "user1@domain.com", "user2@domain.com";
ForEach ($SMTP in $SMTPs) {
<block 3>; }

Почтовые адреса в переменную можно передать прямым списком (как написано у меня), можно импортировать из csv-файла через Import-Csv. Итоговый скрипт выглядит следующим образом:

$StartDate = Get-Date "01.05.2012";
$EndDate = $StartDate.AddMonths(1);
$SMTPs = "user1@domain.com", "user2@domain.com";
ForEach ($SMTP in $SMTPs) {
$TmpDate = $StartDate;
While ($TmpDate -ne $EndDate) {
$Received = Get-TransportServer | Get-MessageTrackingLog -ResultSize unlimited
-Start $TmpDate -End $TmpDate.AddDays(1) -EventId "RECEIVE" -Recipients $SMTP |
Measure-Object;

$Sent = Get-TransportServer | Get-MessageTrackingLog -ResultSize unlimited
-Start $TmpDate -End $TmpDate.AddDays(1) -EventId "RECEIVE" -Sender $SMTP |
Measure-Object;

$tmpObject = New-Object psobject -Property @{
smtp = $SMTP;
date = $TmpDate.ToShortDateString();
rcount = $Received.Count;
scount = $Sent.Count;  }
$TmpDate = $TmpDate.AddDays(1);
$tmpObject | select smtp, date, rcount, scount; } }

Есть одна тонкость, которую следует помнить – данные в логах по умолчанию хранятся за последние 30 дней. Поэтому, чтобы иметь возможность посчитать письма на более ранние даты необходимо срок хранения увеличить, например до 90 дней:

Set-TransportServer -Identity ServerName -MessageTrackingLogMaxAge 90.00:00:00

Задача – выгрузить список пользователей сервера Exchange 2010 с логинами и группами рассылок в которых они состоят. На выходе должны получить что-то типа:

"User Name1", "UserLogon1", "DistrGroup1;DistrGroup2;DistrGroup3;"
"User Name2", "UserLogon2", "DistrGroup3;DistrGroup4;DistrGroup5;"
....

Первые 2 столбца получаются достаточно легко через

Get-Mailbox -Server ServerName -ResultSize unlimited | select DisplayName,
SamAccountName

Однако, в свойствах учётной записи на сервере Exchange отсутствует привязка к группам, в которых состоит учётная запись. Зато в AD у учётной записи пользователя доступно свойство MemberOf:

(Get-ADUser UserName -Properties MemberOf).MemberOf

Которое является массивом строковых переменных, в которых содержатся Distinguished Name групп, в которых пользователь состоит. Останется только эти группы отфильтровать, чтобы остались только группы рассылок. Для этого будем использовать параметр sAMAccountType, который для групп рассылок (SAM_NON_SECURITY_GROUP_OBJECT) будет равен 268435457. Для фильтрации групп получается такой небольшой скрипт:

$Groups = (Get-ADUser UserName -Properties MemberOf).MemberOf
$GroupsString = ""
foreach ($Group in $Groups){
   $GroupObject = [ADSI]"LDAP://$Group"
   if ($GroupObject.sAMAccountType -eq '268435457') {
      $GroupsString = $GroupsString + $GroupObject.name + "; "}}

Итоговый скрипт будет следующий:

Get-Mailbox -Server ServerName -ResultSize unlimited | select DisplayName,
SamAccountName, @{Name="Distribution Groups";expression={
$Groups = (Get-ADUser $_.SamAccountName -Properties MemberOf).MemberOf
$GroupsString = ""
   foreach ($Group in $Groups){
      $GroupObject = [ADSI]"LDAP://$Group"
      if ($GroupObject.sAMAccountType -eq '268435457') {
         $GroupsString = $GroupsString + $GroupObject.name + "; "}}
   $GroupsString}} | Export-Csv c:Tempusers.csv

Задача – назначить определённую политику конференц-связи (Conferencing Policy) в Lync Server для учёток сотрудников, объединённых в группу. По одному назначать политики через Grant-CsConferencingPolicy грустно и долго. Хочется сделать это быстро и с минимальными затратами времени. На помощь приходит наш любимый PowerShell. В консоли Lync Server Management Shell получается следующее:

Import-Module ActiveDirectory

foreach ($User in (Get-ADGroupMember "Group Name")) {Grant-CsConferencingPolicy
-Identity "$($User.distinguishedName)" -PolicyName "Policy Name"}

В Exchange 2007/2010 некоторые свойства объектов являются многозначными. То есть, фактически представляют из себя массив. Самый простой пример – набор smtp-адресов почтового ящика.

[PS] C:Windowssystem32>Get-Mailbox Stas | fl Name,EmailAddresses

Name: Stas
EmailAddresses: {smtp:st@buldakov.ru, SMTP:stas@buldakov.ru}

Сегодня с Олегом Крыловым пытались вывести в csv-файл похожий объект. Изначально задача стояла такая – нужно вывести разрешения AD на коннектор получения в csv-файл. Для начала через Get-ReceiveConnector получаем сам объект-коннектор, затем смотрим права на него через Get-ADPermissions. Получается примерно следующее:

Столбец Rights – то что нам необходимо. Пытаемся его перенаправить прямо в Export-Csv. Получается следующий файл:

Немного не то, что мы ожидали. Вместо самого значения свойства в csv-файл ушёл его тип. Давайте попробуем посмотреть, почему так произошло. Для этого посмотрим что это за объекты:

Сразу видно, что нужные нам свойства (AccessRights и ExtendedRights) являются многозначными. Возникает вопрос – как их корректно вывести в csv-файл? Верный ответ удалось подсмотреть здесь. Итого команда получается следующая:

Get-ReceiveConnector "EVLAB-EXDefault EVLAB-EX" | Get-ADPermission |
Select User,
@{Name='AccessRights';Expression={[string]::join(";",($_.AccessRights))}},
@{Name='ExtendedRights';Expression={[string]::join(";",($_.ExtendedRights))}} |
Export-Csv c:exp.txt

На выходе имеем следующий файл:

Ура!