Articles in the "Mailbox Delegate Management" series
- Super duper delegate retrieval script
- PowerShell module for managing Exchange mailbox delegates [This article]
- Small update to delegate management module
- Delegate management module updated to v1.3.5
- Delegate management module updated to support Exchange Online
- Delegate management module updated to v1.4.5
- Delegate management module updated to 1.4.6
- Delegate management module updated to v1.5.0
- Delegate management module updated
I have converted my script that displays a mailbox’s delegates into a module that adds the ability to add, modify, and remove a mailbox’s delegates. The benefit of the module is that it negates the need to create an Outlook profile for the owner/manager in order to manipulate his or her delegates. The module requires any version of the EWS Managed API and the Exchange cmdlets via remoting, as detailed below.
Upon importing the module, it will check for any version of the EWS Managed API, starting with version 2.0 and working its way back. This allows for the freedom to have any version installed instead of requiring everyone who uses it to ensure they have the specific one I used when writing it. After the API is loaded, it will check for the Exchange cmdlets. If not loaded into the shell, it will retrieve a list of Exchange servers from Active Directory and attempt to connect to one until successful. If neither the API nor remoting to Exchange is successful, the module will fail to load, telling you why. (The module doesn’t distinguish between the cmdlets being available because they were locally loaded from the snap-in or from remoting. However, since certain cmdlets will fail when not executed remotely because they bypass RBAC, you need to be sure that you have not locally loaded the snap-in.)
Access to a mailbox is done using full mailbox access. If you want to use impersonation, you will want to uncomment line 96. Granting impersonation rights is explained here. The URL used for EWS is determined by autodiscover of the owner/manager mailbox.
These are the cmdlets the module exposes:
Get-MailboxDelegate
The alias for Get-MailboxDelegate is gmd. It is basically the same as my Get-Delegates.ps1 script, but it has gotten a makeover to support pipelining into and out of. The -Identity parameter (aliased to -Owner and -Manager) is any standard Exchange identity (display name, email address, alias, dn, etc.) and it supports pipelining by property name. If the objects you are pipelining into Get-MailboxDelegate don’t have a property name of Identity, then you will need to use a ForEach loop and use $_.PropertyName to designate which property should be used.
Without any other parameters, all delegates will be retrieved. If you want to get only a specific delegate, you can use the -Delegate parameter. The default output will be to list the properties, but since it is now a collection of objects, you can choose to output it to a table, to a grid view, or export to a file, using the appropriate cmdlets. You can also use these output cmdlets to select only the properties you want. For example, if you only care about the private items property you could use ft owner,delegate,viewprivate. Or, if you only want those who actually can view private items, you could run something like this:
1 |
(Get-DistributionGroupMember <DLIdentity>) | gmd | where {$_.ViewPrivate -eq $true} | ft owner,delegate |
Note that I encapsulated in parentheses the command that I pipeline into Get-MailboxDelegate. This is necessary to avoid the concurrent pipeline limitation in remote PowerShell. It is only necessary if the command prior to the pipeline will be running a cmdlet that leverages remoting. Another option is to store the results of the prior command in a variable and then pipeline that into Get-MailboxDelegate.
All of the module’s cmdlets have built-in help, so you can use PowerShell’s help cmdlet to learn the details of all of them, such as the parameters and their descriptions, usage examples, etc.
Add-MailboxDelegate
The alias for Add-MailboxDelegate is amd. To use this cmdlet, provide an owner and a delegate. You can optionally specify folder permission for Inbox, Calendar, Tasks, Contacts, Sent Items, and Deleted Items; if private items are viewable; if meeting requests are to be received; and the owner’s global handling of meeting requests. I didn’t include the option of setting permission for Journal or Notes because, well, who uses them? The ability to set the permission for Sent Items and Deleted Items is to accommodate those who use GPO to have Outlook store messages sent from another user in that person’s Sent Items folder, and likewise for messages deleted from another mailbox. The option to set the meeting request delivery scope applies to the owner, not the delegate being added, so it is only necessary to include it if you are adding a delegate and you want to change the current setting.
Set-MailboxDelegate
The alias for Set-MailboxDelegate is smd. Use this cmdlet to change any settings for an existing delegate (or to change the meeting request delivery scope for the owner). Provide the owner and the existing delegate to modify and, optionally, which setting you want to change. All other settings will remain as is. If you want to change just the meeting request delivery scope for the owner, specify any existing delegate, but not any other settings (except the delivery scope). Unlike the valid roles for folder permission with Add-MailboxDelegate, you can use None if you want to remove a folder permission. If you want to remove the ability to view private items or to not receive meeting requests, use -ViewPrivateItems:$false or -ReceiveMeetingRequests:$false, respectively. The colon is necessary because both parameters are switches, so their inclusion alone in the command means true, whereas to explicitly set them to false means using the syntax above. (The cmdlet checks if either switch is actually present in the command, so don’t be concerned that not including a switch implies that it should be false.)
Remove-MailboxDelegate
The alias for Remove-MailboxDelegate is rmd. Provide the owner and the delegate to remove. That’s it.
All of the cmdlets perform the expected error checking: the owner is a valid mailbox; any delegate to add, modify, or remove is a valid mailbox; adding a delegate when the delegate already exists; modifying or removing a delegate that is not an existing delegate; using a valid (albeit the limited subset exposed in the API) role for a folder permission; and using a valid meeting request delivery scope.
Download the module or view/copy the code below:
DelegateManagement.zip (9.2 KiB)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 |
#Exchange 2010 mailbox delegate management module #v1.3.5 9/9/13 #1.3.5 Changed Send-As function to work in EMS #1.3.4 Changed default access to use FMA instead of impersonation #Paths to EWS Managed API DLL $ewsAPIPaths = "C:\Program Files\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll", "C:\Program Files\Microsoft\Exchange\Web Services\1.2\Microsoft.Exchange.WebServices.dll", "C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll", "C:\Program Files\Microsoft\Exchange\Web Services\1.0\Microsoft.Exchange.WebServices.dll" #Test if any version of API is installed before loading module functions foreach ($path in $ewsAPIPaths) { if (Test-Path $path) { Add-Type -Path $path $apiFound = $true break } } if (-not($apiFound)) { Write-Error "The Exchange Web Services Managed API is required to use this module." -Category NotInstalled break } function Get-ExchangeServerName { $configNC = ([ADSI]"LDAP://RootDse").configurationNamingContext $search = New-Object DirectoryServices.DirectorySearcher([ADSI]"LDAP://$configNC") $search.Filter = "(&(objectClass=msExchExchangeServer)(versionNumber>=1937801568))" $search.PageSize=1000 $search.PropertiesToLoad.Clear() [void] $search.PropertiesToLoad.Add("networkaddress") $search.FindAll() } function Get-ServerFqdnFromNetworkAddress($server) { $server.properties["networkaddress"] | Where-Object {$_.ToString().StartsWith("ncacn_ip_tcp")} | ForEach-Object {$_.ToString().SubString(13)} } function Connect-ExchangeServer($server) { try { Import-PSSession (New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "http://$server/powershell" -Name 'RemoteExchange') -AllowClobber -DisableNameChecking -ErrorAction Stop | Out-Null $true } catch { $false } } function Connect-RemoteExchangeServer { $exchangeServers = Get-ExchangeServerName $serverFQDN = $exchangeServers | ForEach-Object {Get-ServerFqdnFromNetworkAddress $_} #Loop through servers until connection is successful for ($i = 0; $i -lt $serverFQDN.Length; $i++) { $attemptResult = Connect-ExchangeServer $serverFQDN[$i] if ($attemptResult) {break} } if ($attemptResult) {$true} else {$false} } #Test for Exchange cmdlets before loading any module functions $testcmd = Get-Command Get-Mailbox -ErrorAction SilentlyContinue if (-not($testcmd)) { $connectResult = Connect-RemoteExchangeServer if (-not($connectResult)) { Write-Error "Unable to connect to any Exchange server." -Category ConnectionError break } } #Region Helper Functions function Connect-WebServices ($smtpAddress) { $exchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2 $global:exchangeService = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($exchangeVersion) #Use autodiscover instead of hard-coded URL $exchangeService.AutodiscoverUrl($smtpAddress) #Impersonate mailbox #$exchangeService.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $smtpAddress) New-Object Microsoft.Exchange.WebServices.Data.Mailbox($smtpAddress) } function Find-Mailbox ($identity) { try { Get-Mailbox $identity -ErrorAction Stop } catch { Write-Progress 'Done' -Completed -Status " " Write-Error "A mailbox cannot be found that matches the input string $identity." -ErrorAction Stop -Category ObjectNotFound } } function Get-SID($acl) { $aSID = @() $acl | ForEach-Object { $adUser = [System.Security.Principal.NTAccount]($_.User.ToString()) $aSID += $adUser.Translate([System.Security.Principal.SecurityIdentifier]).Value } $aSID } function Get-FMA($identity) { Get-MailboxPermission $identity | Where-Object {$_.IsInherited -eq $false -and $_.User -notlike 'S-1-5-21*'} } function Get-SendAs($identity) { Get-ADPermission $identity | Where-Object {$_.IsInherited -eq $false -and $_.ExtendedRights -like '*Send-As*'} } function Get-FolderPermission($mailbox,$folder) { Get-MailboxFolderPermission "$mailbox`:\$folder" } function Set-FolderPermission($owner,$delegate,$folder,$role) { $folderPerm = Get-FolderPermission $owner.Identity $folder [array]$delegateFolderPerm = $folderPerm | Where-Object {$_.User -eq $delegate.DisplayName} #Run appropriate cmdlet based on delegate presence in ACL if ($delegateFolderPerm.Count -eq 1) { try { Set-MailboxFolderPermission -Identity "$($owner.Identity):\$folder" -User $delegate.Identity -AccessRights $role -ErrorAction Stop | Out-Null } catch { $false } } else { try { Add-MailboxFolderPermission -Identity "$($owner.Identity):\$folder" -User $delegate.Identity -AccessRights $role -ErrorAction Stop | Out-Null } catch { $false } } } function Convert-StringPermissionToEnum($role) { switch ($role) { 'Reviewer' {[Microsoft.Exchange.WebServices.Data.DelegateFolderPermissionLevel]::Reviewer} 'Author' {[Microsoft.Exchange.WebServices.Data.DelegateFolderPermissionLevel]::Author} 'Editor' {[Microsoft.Exchange.WebServices.Data.DelegateFolderPermissionLevel]::Editor} 'None' {[Microsoft.Exchange.WebServices.Data.DelegateFolderPermissionLevel]::None} } } function Convert-StringDeliveryScopeToEnum($scope) { switch ($scope) { 'DelegatesOnly' {[Microsoft.Exchange.WebServices.Data.MeetingRequestsDeliveryScope]::DelegatesOnly} 'DelegatesAndOwner' {[Microsoft.Exchange.WebServices.Data.MeetingRequestsDeliveryScope]::DelegatesAndMe} 'DelegatesAndInfoToOwner' {[Microsoft.Exchange.WebServices.Data.MeetingRequestsDeliveryScope]::DelegatesAndSendInformationToMe} } } #EndRegion #Region Main Functions function Add-MailboxDelegate { <# .Synopsis Add a mailbox as a delegate of an owner's mailbox. .Description Add a mailbox delegate, optionally specifying permission to various folders, whether private items are viewable by the delegate, and if the delegate should receive meeting requests for the owner. .Parameter Owner Identity string of the user whose mailbox is to have the delegate. .Parameter Delegate Identity string of the user who is to be added to owner's mailbox. .Parameter InboxPermission Role to assign to the Inbox folder. Valid roles are Reviewer, Author, and Editor. .Parameter CalendarPermission Role to assign to the Calendar folder. Valid roles are Reviewer, Author, and Editor. .Parameter TasksPermission Role to assign to the Tasks folder. Valid roles are Reviewer, Author, and Editor. .Parameter ContactsPermission Role to assign to the Contacts folder. Valid roles are Reviewer, Author, and Editor. .Parameter SentItemsPermission Role to assign to the Sent Items folder. Valid roles are Reviewer, Author, and Editor. .Parameter DeletedItemsPermission Role to assign to the Deleted Items folder. Valid roles are Reviewer, Author, and Editor. .Parameter ViewPrivateItems Enable the delegate to view items marked as private. .Parameter ReceiveMeetingRequests Enable the delegate to receive meeting requests for the owner. .Parameter MeetingRequestDeliveryScope Specify how meeting requests should be handled for the owner. Valid scopes are DelegatesOnly, DelegatesAndOwner, and DelegatesAndInfoToOwner. Note that this parameter applies to all delegates. .Example Add-MailboxDelegate Username DelegateUsername -InboxPermission Editor -CalendarPermission Editor -ViewPrivateItems .Example Add-MailboxDelegate -Owner domain\username -Delegate -CalendarPermission Editor -ReceiveMeetingRequests .Notes Version: 1.01 Date: 3/5/13 #> [CmdletBinding()] param ( [Parameter(Position=0,Mandatory=$true)][Alias("Manager")][string]$Owner, [Parameter(Position=1,Mandatory=$true)][string]$Delegate, [ValidateSet('Reviewer','Author','Editor')][string]$InboxPermission, [ValidateSet('Reviewer','Author','Editor')][string]$CalendarPermission, [ValidateSet('Reviewer','Author','Editor')][string]$TasksPermission, [ValidateSet('Reviewer','Author','Editor')][string]$ContactsPermission, [ValidateSet('Reviewer','Author','Editor')][string]$SentItemsPermission, [ValidateSet('Reviewer','Author','Editor')][string]$DeletedItemsPermission, [Alias("PI")][switch]$ViewPrivateItems, [Alias("MR")][switch]$ReceiveMeetingRequests, [ValidateSet('DelegatesOnly','DelegatesAndOwner','DelegatesAndInfoToOwner')][string]$MeetingRequestDeliveryScope ) #Validate mailboxes Write-Progress -Activity "Adding Mailbox Delegate" -Status "Validating owner and delegate mailboxes" -PercentComplete 0 $mbOwner = Find-Mailbox $Owner $mbDelegate = Find-Mailbox $Delegate $ownerFirstName = (Get-User $mbOwner.Identity).FirstName $ownerLastName = (Get-User $mbOwner.Identity).LastName $delegateFirstName = (Get-User $mbDelegate.Identity).FirstName $delegateLastName = (Get-User $mbDelegate.Identity).LastName #Get EWS mailbox reference Write-Progress -Activity "Adding Mailbox Delegate" -Status "Connecting to EWS" -PercentComplete 25 $EWSMailbox = Connect-WebServices $mbOwner.PrimarySMTPAddress.ToString() #Get collection of delegates, without folder permissions Write-Progress -Activity "Adding Mailbox Delegate" -Status "Retrieving existing delegates" -PercentComplete 50 $currentDelegates = $exchangeService.GetDelegates($EWSMailbox,$false) #Check if user is already a delegate if ($currentDelegates.DelegateUserResponses.Count -gt 0) { foreach ($currentDelegate in $currentDelegates.DelegateUserResponses) { if ($currentDelegate.DelegateUser.UserId.PrimarySMTPAddress -eq $mbDelegate.PrimarySMTPAddress.ToString()) { Write-Progress -Activity "Adding Mailbox Delegate" -Completed -Status " " Write-Host "$delegateFirstName $delegateLastName is already a delegate of $ownerFirstName $ownerLastName. Use Set-MailboxDelegate to update an existing delegate." return } } } Write-Progress -Activity "Adding Mailbox Delegate" -Status "Adding delegate" -PercentComplete 75 #Create EWS delegate object $delegateUser = New-Object Microsoft.Exchange.WebServices.Data.DelegateUser($mbDelegate.PrimarySMTPAddress.ToString()) #Set private items if ($ViewPrivateItems) { $delegateUser.ViewPrivateItems = $true } #Set meeting request receipt if ($ReceiveMeetingRequests) { $delegateUser.ReceiveCopiesOfMeetingMessages = $true } #Set permissions on folders if ($InboxPermission) { $delegateUser.Permissions.InboxFolderPermissionLevel = Convert-StringPermissionToEnum $InboxPermission } if ($CalendarPermission) { $delegateUser.Permissions.CalendarFolderPermissionLevel = Convert-StringPermissionToEnum $CalendarPermission } if ($TasksPermission) { $delegateUser.Permissions.TasksFolderPermissionLevel = Convert-StringPermissionToEnum $TasksPermission } if ($ContactsPermission) { $delegateUser.Permissions.ContactsFolderPermissionLevel = Convert-StringPermissionToEnum $ContactsPermission } if ($SentItemsPermission) { $SIPermResponse = Set-FolderPermission $mbOwner $mbDelegate 'Sent Items' $SentItemsPermission } if ($DeletedItemsPermission) { $DIPermResponse = Set-FolderPermission $mbOwner $mbDelegate 'Deleted Items' $DeletedItemsPermission } #Build delegate collection object to use in EWS method $delegateArray = New-Object Microsoft.Exchange.WebServices.Data.DelegateUser[] 1 $delegateArray[0] = $delegateUser #Set new meeting request delivery scope if specified if ($MeetingRequestDeliveryScope) { $addResponse = $exchangeService.AddDelegates($EWSMailbox, (Convert-StringDeliveryScopeToEnum $MeetingRequestDeliveryScope), $delegateArray) } else { $addResponse = $exchangeService.AddDelegates($EWSMailbox, $null, $delegateArray) } Write-Progress -Activity "Adding Mailbox Delegate" -Completed -Status " " if ($addResponse[0].Result -eq [Microsoft.Exchange.WebServices.Data.ServiceResult]::Success) { Write-Host "$delegateFirstName $delegateLastName has been added as a delegate of $ownerFirstName $ownerLastName." -ForegroundColor Green if ($SIPermResponse -eq $false) { Write-Host "An error occurred adding the delegate permission to the Sent Items folder." -ForegroundColor Yellow } if ($DIPermResponse -eq $false) { Write-Host "An error occurred adding the delegate permission to the Deleted Items folder." -ForegroundColor Yellow } } else { Write-Host "An error occurred adding $delegateFirstName $delegateLastName as a delegate of $ownerFirstName $ownerLastName." -ForegroundColor Red } } function Set-MailboxDelegate { <# .Synopsis Update the settings for an existing delegate of an owner's mailbox. .Description Update an existing mailbox delegate, specifying any changes to folder permissions, whether private items are viewable by the delegate, or if the delegate should receive meeting requests for the owner. .Parameter Owner Identity string of the user whose mailbox has the delegate. .Parameter Delegate Identity string of the user whose delegate settings are to be updated. .Parameter InboxPermission Role to assign to the Inbox folder. Valid roles are Reviewer, Author, Editor, and None. .Parameter CalendarPermission Role to assign to the Calendar folder. Valid roles are Reviewer, Author, Editor, and None. .Parameter TasksPermission Role to assign to the Tasks folder. Valid roles are Reviewer, Author, Editor, and None. .Parameter ContactsPermission Role to assign to the Contacts folder. Valid roles are Reviewer, Author, Editor, and None. .Parameter SentItemsPermission Role to assign to the Sent Items folder. Valid roles are Reviewer, Author, Editor, and None. .Parameter DeletedItemsPermission Role to assign to the Deleted Items folder. Valid roles are Reviewer, Author, Editor, and None. .Parameter ViewPrivateItems Enable the delegate to view items marked as private. .Parameter ReceiveMeetingRequests Enable the delegate to receive meeting requests for the owner. .Parameter MeetingRequestDeliveryScope Specify how meeting requests should be handled for the owner. Valid scopes are DelegatesOnly, DelegatesAndOwner, and DelegatesAndInfoToOwner. Note that this parameter applies to all delegates. .Example Set-MailboxDelegate Username DelegateUsername -InboxPermission Editor -CalendarPermission Editor -ViewPrivateItems .Example Set-MailboxDelegate -Owner domain\username -Delegate -CalendarPermission Editor -ReceiveMeetingRequests .Notes Version: 1.01 Date: 3/5/13 #> [CmdletBinding()] param ( [Parameter(Position=0,Mandatory=$true)][Alias("Manager")][string]$Owner, [Parameter(Position=1,Mandatory=$true)][string]$Delegate, [ValidateSet('Reviewer','Author','Editor','None')][string]$InboxPermission, [ValidateSet('Reviewer','Author','Editor','None')][string]$CalendarPermission, [ValidateSet('Reviewer','Author','Editor','None')][string]$TasksPermission, [ValidateSet('Reviewer','Author','Editor','None')][string]$ContactsPermission, [ValidateSet('Reviewer','Author','Editor','None')][string]$SentItemsPermission, [ValidateSet('Reviewer','Author','Editor','None')][string]$DeletedItemsPermission, [Alias("PI")][switch]$ViewPrivateItems, [Alias("MR")][switch]$ReceiveMeetingRequests, [ValidateSet('DelegatesOnly','DelegatesAndOwner','DelegatesAndInfoToOwner')][string]$MeetingRequestDeliveryScope ) #Validate mailboxes Write-Progress -Activity "Updating Mailbox Delegate" -Status "Validating owner and delegate mailboxes" -PercentComplete 0 $mbOwner = Find-Mailbox $Owner $mbDelegate = Find-Mailbox $Delegate $ownerFirstName = (Get-User $mbOwner.Identity).FirstName $ownerLastName = (Get-User $mbOwner.Identity).LastName $delegateFirstName = (Get-User $mbDelegate.Identity).FirstName $delegateLastName = (Get-User $mbDelegate.Identity).LastName #Get EWS mailbox reference Write-Progress -Activity "Updating Mailbox Delegate" -Status "Connecting to EWS" -PercentComplete 25 $EWSMailbox = Connect-WebServices $mbOwner.PrimarySMTPAddress.ToString() #Get collection of delegates, with folder permissions Write-Progress -Activity "Updating Mailbox Delegate" -Status "Retrieving existing delegates" -PercentComplete 50 $currentDelegates = $exchangeService.GetDelegates($EWSMailbox,$true) #Confirm user is already a delegate $delegateMatch = $false if ($currentDelegates.DelegateUserResponses.Count -eq 0) { Write-Progress -Activity "Updating Mailbox Delegate" -Completed -Status " " Write-Host "$ownerFirstName $ownerLastName does not have any delegates. ` Use Add-MailboxDelegate to add a new delegate." return } elseif ($currentDelegates.DelegateUserResponses.Count -gt 0) { foreach ($currentDelegate in $currentDelegates.DelegateUserResponses) { if ($currentDelegate.DelegateUser.UserId.PrimarySMTPAddress -eq $mbDelegate.PrimarySMTPAddress.ToString()) { #Modify existing delegate object instead of creating new one so existing settings #can be preserved $delegateUser = $currentDelegate.DelegateUser $delegateMatch = $true } } if (-not($delegateMatch)) { Write-Progress -Activity "Updating Mailbox Delegate" -Completed -Status " " Write-Host "$delegateFirstName $delegateLastName is not a delegate of $ownerFirstName $ownerLastName. Use Add-MailboxDelegate to add a new delegate." return } } Write-Progress -Activity "Updating Mailbox Delegate" -Status "Updating delegate" -PercentComplete 75 #Set private items if included if ($MyInvocation.BoundParameters.ContainsKey('ViewPrivateItems')) { $delegateUser.ViewPrivateItems = $ViewPrivateItems } #Set meeting request receipt if included if ($MyInvocation.BoundParameters.ContainsKey('ReceiveMeetingRequests')) { $delegateUser.ReceiveCopiesOfMeetingMessages = $ReceiveMeetingRequests } #Set permissions on folders if ($InboxPermission) { $delegateUser.Permissions.InboxFolderPermissionLevel = Convert-StringPermissionToEnum $InboxPermission } if ($CalendarPermission) { $delegateUser.Permissions.CalendarFolderPermissionLevel = Convert-StringPermissionToEnum $CalendarPermission } if ($TasksPermission) { $delegateUser.Permissions.TasksFolderPermissionLevel = Convert-StringPermissionToEnum $TasksPermission } if ($ContactsPermission) { $delegateUser.Permissions.ContactsFolderPermissionLevel = Convert-StringPermissionToEnum $ContactsPermission } if ($SentItemsPermission) { $SIPermResponse = Set-FolderPermission $mbOwner $mbDelegate 'Sent Items' $SentItemsPermission } if ($DeletedItemsPermission) { $DIPermResponse = Set-FolderPermission $mbOwner $mbDelegate 'Deleted Items' $DeletedItemsPermission } #Build delegate collection object to use in EWS method $delegateArray = New-Object Microsoft.Exchange.WebServices.Data.DelegateUser[] 1 $delegateArray[0] = $delegateUser #Set new meeting request delivery scope if specified if ($MeetingRequestDeliveryScope) { $updateResponse = $exchangeService.UpdateDelegates($EWSMailbox, (Convert-StringDeliveryScopeToEnum $MeetingRequestDeliveryScope), $delegateArray) } else { $updateResponse = $exchangeService.UpdateDelegates($EWSMailbox, $null, $delegateArray) } Write-Progress -Activity "Updating Mailbox Delegate" -Completed -Status " " if ($updateResponse[0].Result -eq [Microsoft.Exchange.WebServices.Data.ServiceResult]::Success) { Write-Host "$delegateFirstName $delegateLastName has been updated as a delegate of $ownerFirstName $ownerLastName." -ForegroundColor Green if ($SIPermResponse -eq $false) { Write-Host "An error occurred adding the delegate permission to the Sent Items folder." -ForegroundColor Yellow } if ($DIPermResponse -eq $false) { Write-Host "An error occurred adding the delegate permission to the Deleted Items folder." -ForegroundColor Yellow } } else { Write-Host "An error occurred updating $delegateFirstName $delegateLastName as a delegate of $ownerFirstName $ownerLastName." -ForegroundColor Red } } function Remove-MailboxDelegate { <# .Synopsis Remove a delegate from an owner's mailbox. .Description Remove a supplied mailbox delegate from a supplied owner's mailbox. .Parameter Owner Identity string of the user whose mailbox has the delegate. .Parameter Delegate Identity string of the user who is to be removed from the owner's mailbox. .Example Remove-MailboxDelegate user@domain.com delegate@domain.com .Example Remove-MailboxDelegate -Owner domain\username -Delegate .Notes Version: 1.01 Date: 3/5/13 #> [CmdletBinding()] param ( [Parameter(Position=0,Mandatory=$true)][Alias("Manager")][string]$Owner, [Parameter(Position=1,Mandatory=$true)][string]$Delegate ) #Validate mailboxes Write-Progress -Activity "Removing Mailbox Delegate" -Status "Validating owner and delegate mailboxes" -PercentComplete 0 $mbOwner = Find-Mailbox $Owner $mbDelegate = Find-Mailbox $Delegate $ownerFirstName = (Get-User $mbOwner.Identity).FirstName $ownerLastName = (Get-User $mbOwner.Identity).LastName $delegateFirstName = (Get-User $mbDelegate.Identity).FirstName $delegateLastName = (Get-User $mbDelegate.Identity).LastName #Get EWS mailbox reference Write-Progress -Activity "Removing Mailbox Delegate" -Status "Connecting to EWS" -PercentComplete 25 $EWSMailbox = Connect-WebServices $mbOwner.PrimarySMTPAddress.ToString() #Get collection of delegates, without folder permissions Write-Progress -Activity "Removing Mailbox Delegate" -Status "Retrieving delegates" -PercentComplete 50 $currentDelegates = $exchangeService.GetDelegates($EWSMailbox,$false) if ($currentDelegates.DelegateUserResponses.Count -eq 0) { Write-Progress -Activity "Removing Mailbox Delegate" -Completed -Status " " Write-Host $ownerFirstName $ownerLastName "does not have any delegates." } else { $delegateToRemove = @() $delegateMatch = $false Write-Progress -Activity "Removing Mailbox Delegate" -Status "Removing delegate" -PercentComplete 75 foreach ($currentDelegate in $currentDelegates.DelegateUserResponses) { if ($currentDelegate.DelegateUser.UserId.PrimarySMTPAddress -eq $mbDelegate.PrimarySMTPAddress.ToString()) { #Add userid object to collection of delegates to remove $delegateToRemove += $currentDelegate.DelegateUser.UserId $delegateMatch = $true } } if (-not($delegateMatch)) { Write-Progress -Activity "Removing Mailbox Delegate" -Completed -Status " " Write-Host $mbDelegate.PrimarySMTPAddress "is not a delegate of" $mbOwner.PrimarySMTPAddress "." } else { #Remove delegate from owner's mailbox $removeResponse = $exchangeService.RemoveDelegates($EWSMailbox, $delegateToRemove) Write-Progress -Activity "Removing Mailbox Delegate" -Completed -Status " " if ($removeResponse[0].Result -eq [Microsoft.Exchange.WebServices.Data.ServiceResult]::Success) { Write-Host "$delegateFirstName $delegateLastName has been removed as a delegate of $ownerFirstName $ownerLastName." -ForegroundColor Green } else { Write-Host "An error occurred removing $delegateFirstName $delegateLastName as a delegate of $ownerFirstName $ownerLastName." -ForegroundColor Red } } } } function Get-MailboxDelegate { <# .Synopsis Display a mailbox's delegates and permissions. .Description Retrieve the list of delegates for a mailbox and display the mailbox permission, folder permissions, meeting invite settings, and (optionally) whether the delegate has Send As permission. .Parameter Identity Identity string of the user whose mailbox has the delegates. Owner and Manager are valid aliases for this parameter. .Parameter Delegate Identity string of the delegate you want to retrieve. If omitted, all delegates are retrieved. .Parameter IncludeSendAs Switch to indicate that you want Send As permission to be included. .Example Get-MailboxDelegate user@domain.com -includesendas .Example Get-MailboxDelegate domain\username .Notes Version: 1.7 Date: 3/12/13 #> param ( [Parameter(Position=0,Mandatory=$true,HelpMessage="Identity of mailbox owner",ValueFromPipelineByPropertyName=$true)] [Alias("Owner")][Alias("Manager")][string]$Identity, [Parameter(Position=1)][string]$Delegate, [Alias("SA")][switch]$includeSendAs #Perform Send As lookup (takes longer) ) process { #Validate owner mailbox Write-Progress -Activity "Getting Mailbox Delegate" -Status "Validating owner mailbox" -PercentComplete 0 $mbOwner = Find-Mailbox $Identity #Validate delegate mailbox if ($Delegate) { Write-Progress -Activity "Getting Mailbox Delegate" -Status "Validating delegate mailbox" -PercentComplete 5 $mbDelegate = Find-Mailbox $Delegate $delegateFirstName = (Get-User $mbDelegate.Identity).FirstName $delegateLastName = (Get-User $mbDelegate.Identity).LastName } $ownerFirstName = (Get-User $mbOwner.Identity).FirstName $ownerLastName = (Get-User $mbOwner.Identity).LastName #Get EWS mailbox reference Write-Progress -Activity "Getting Mailbox Delegate" -Status "Connecting to EWS" -PercentComplete 10 $EWSMailbox = Connect-WebServices $mbOwner.PrimarySMTPAddress.ToString() #Get list of delegates and permissions from EWS Write-Progress -Activity "Getting Mailbox Delegate" -Status "Retrieving delegates" -PercentComplete 20 #Get collection of delegates, with folder permissions if ($mbDelegate) { #Retrieve only the specified delegate $delegateUser = New-Object Microsoft.Exchange.WebServices.Data.UserId($mbDelegate.PrimarySMTPAddress.ToString()) $delegateArray = New-Object Microsoft.Exchange.WebServices.Data.UserId[] 1 $delegateArray[0] = $delegateUser $currentDelegates = $exchangeService.GetDelegates($EWSMailbox,$true,$delegateArray) } else { $currentDelegates = $exchangeService.GetDelegates($EWSMailbox,$true) } #Get list of users with full mailbox access Write-Progress -Activity "Getting Mailbox Delegate" -Status "Retrieving FMA list" -PercentComplete 40 $fullMailboxAccess = Get-FMA $mbOwner.Identity $fmaSID = Get-SID $fullMailboxAccess #Convert username to SID #Get list of users with Send As permission from AD if ($includeSendAs) { Write-Progress -Activity "Getting Mailbox Delegate" -Status "Retrieving Send As List" -CurrentOperation "(This part takes the longest.)" -PercentComplete 70 $sendAs = Get-SendAs $mbOwner.Identity $saSID = Get-SID $sendAs #Convert username to SID } #Get permissions for additional folders Write-Progress -Activity "Getting Mailbox Delegate" -Status "Retrieving additional folder permissions" -PercentComplete 90 $deletedItemsPerm = Get-FolderPermission $mbOwner.Identity 'Deleted Items' $sentItemsPerm = Get-FolderPermission $mbOwner.Identity 'Sent Items' Write-Progress -Activity "Getting Mailbox Delegate" -Completed -Status " " #Build output $outputArr = New-Object System.Collections.ArrayList #Loop through list of delegates if ($currentDelegates.DelegateUserResponses.Count -gt 0) { if ($mbDelegate -and $currentDelegates.DelegateUserResponses[0].ErrorCode -eq 'ErrorNotDelegate') { Write-Host "$delegateFirstName $delegateLastName is not a delegate of $ownerFirstName $ownerLastName." } else { $currentDelegates.DelegateUserResponses | ForEach-Object { #Create custom object with property names $outputItem = "" | Select-Object Owner,Delegate,MeetingHandling,FMA,SendAs,Calendar,Inbox,Contacts,Tasks,DeletedItems,SentItems,ReceiveMeetings,ViewPrivate,Error,ErrorNote $outputItem.Owner = $mbOwner.DisplayName $outputItem.MeetingHandling = $currentDelegates.MeetingRequestsDeliveryScope if ($_.ErrorMessage -eq 'The delegate does not map to a user in the Active Directory.') {#Delegate account deleted in AD but still listed in list $outputItem.Error = "Orphan" $outputItem.ErrorNote = "Check NON_IPM_SUBTREE\Freebusy Data\LocalFreebusy.eml property 0x684A101E to determine orphan entry." } elseif ($_.ErrorMessage -eq 'Delegate is not configured properly.') { $outputItem.Error = "Misconfigured" $outputItem.ErrorNote = "Missing from Freebusy Data folder or publicDelegates attribute." } elseif ($_.Result -eq 'Error') { $outputItem.Error = "UnknownError" $outputItem.ErrorNote = $_.ErrorMessage } else { $delegateDisplayName = $_.delegateuser.userid.displayname $outputItem.Delegate = $delegateDisplayName if ($fmaSID -match $_.delegateuser.UserId.SID) { $outputItem.FMA = $true } else { $outputItem.FMA = $false } if ($includeSendAs) { if ($saSID -match $_.delegateuser.UserId.SID) { $outputItem.SendAs = $true } else { $outputItem.SendAs = $false } } $outputItem.Calendar = $_.DelegateUser.Permissions.CalendarFolderPermissionLevel.ToString() $outputItem.Inbox = $_.DelegateUser.Permissions.InboxFolderPermissionLevel.ToString() $outputItem.Contacts = $_.DelegateUser.Permissions.ContactsFolderPermissionLevel.ToString() $outputItem.Tasks = $_.DelegateUser.Permissions.TasksFolderPermissionLevel.ToString() #Construct Deleted Items permission output [array]$delegateDIPerm = $deletedItemsPerm | Where-Object {$_.User -eq $delegateDisplayName} if ($delegateDIPerm.Count -eq 1) { $delegateDIPermValue = $delegateDIPerm[0].AccessRights[0].ToString() } else { $delegateDIPermValue = 'None' } $outputItem.DeletedItems = $delegateDIPermValue #Construct Sent Items permission output [array]$delegateSIPerm = $sentItemsPerm | Where-Object {$_.User -eq $delegateDisplayName} if ($delegateSIPerm.Count -eq 1) { $delegateSIPermValue = $delegateSIPerm[0].AccessRights[0].ToString() } else { $delegateSIPermValue = 'None' } $outputItem.SentItems = $delegateSIPermValue $outputItem.ReceiveMeetings = $_.delegateuser.receivecopiesofmeetingmessages $outputItem.ViewPrivate = $_.delegateuser.viewprivateitems $outputArr.Add($outputItem) | Out-Null } } } } else { Write-Host "$ownerFirstName $ownerLastName has no delegates." } $outputArr } } #EndRegion Set-Alias amd Add-MailboxDelegate Set-Alias smd Set-MailboxDelegate Set-Alias rmd Remove-MailboxDelegate Set-Alias gmd Get-MailboxDelegate Export-ModuleMember -Function *-MailboxDelegate Export-ModuleMember -Alias * |
I can’t tell you how much I love this. I had to give a user the ability to view private items on nearly 30 room calendars, and the prospect of creating an Outlook profile for each room just so I could add the user as a delegate and check the ‘delegate can see my private items’ box was driving me to drink. Your handy module did the job quickly and easily!
You, sir, are the MAN! this is a must for office 365 migrations
I keep getting the error “”The account does not have permission to impersonate the requested user” when trying to run any of the cmdLets.
The account I am loggedin as is member of org.management but still getting the error.
Any suggestions??
Peter, being an organization administrator isn’t enough when impersonation is involved. There are two options. One is to add the accounts to an RBAC role that is for impersonation, as described here. The other option is to not use impersonation and rely on full mailbox access. To do this, comment out line 94 in the module. Either method works fine. Impersonation is generally used for application or programmatic access to mailboxes. If using FMA, then you have to be sure that you have access to the mailbox in question, which could be implicit depending on how you grant FMA. Anyway, the default setting in the module is to use impersonation, which isn’t a default right in Exchange.
The post explains that impersonation is used and how to bypass it, but I will add a link to the MSDN page for granting impersonation rights.
Hi,
This Get-MailboxDelegate do not work proper when the user has folder names in different language than English. Can you fix this?
Thank you good Sir! Working like a charm and part of my “Exchange Toolbox” now.
Can you please update this fabulous script to work with Office 365 Exchange online?
Hey Author, its great what you did!
I still have issues adding a group as delegate with error “A Mailbox cannot be found…”.
Everything else works great! Cheers, Thomas
A group cannot be a delegate. It has to be an individual mailbox.
hi!
this script is awesome! is there any explaination for the commands? e.g. the “view private items” ?! how can i add this??
There is help included in the cmdlets. You can run
Get-Help Add-MailboxDelegate -Parameter ViewPrivateItems
to see details about it. But in this case, it is a switch parameter, so just include-ViewPrivateItems
anywhere in the command to add that option.Can this script be modified so that it can be run directly on the Exchange server without having to install the web services api on the server?
The API needs to be installed on whichever system is running the cmdlets. It can be a workstation, server, or Exchange server. Nothing has to be installed on Exchange. The API is just a wrapper for using EWS without having to use SOAP and manually constructing requests. Even if you run it on Exchange, though, the API still has to be installed since the client (which just happens to be the Exchange server) is what is leveraging the API, not the server.
If you are uncomfortable installing the API on the server, you can just copy the DLL to any location on it and then modify the module to load the DLL directly from that location. Exchange won’t even know the API is installed.
for some reason the -ReceiveMeetingRequests parameter is not working for me. I’m not getting any errors but when I check the Outlook client the checkbox for that option is not checked.
Also, is there any way to set this exchange parameter?
MeetingHandling = DelegatesAndMe
The parameter to control who receives meeting requests is MeetingRequestDeliveryScope. What is called DelegatesAndMe in Exchange is set using DelegatesAndOwner in the module. I use the latter in the module because the perspective of the setting is from you as an admin, whereas Exchange delegates are set from the perspective of the owner. For the ReceiveMeetingRequests parameter issue, if you set it for another delegate with the same owner or another owner with the same or different delegate, does it work?
I love this script but how do I change it so that it runs across all users and exports to a file. I am new to powershell/EWS scripting. Thanks for any help.
You can pipeline from Get-Mailbox into Get-MailboxDelegate and then pipeline the results to any export cmdlet:
Get-Mailbox | gmd | Export-csv c:\temp\delegateresults.csv -notypeinformation
Will this work for exchange 2013 that is is hosted in our Datacenter?
Excellent WORK! this has saved me countless hours of time dealing with a problem with a Shared Mailbox and Private Items.
Cheers
Yes, the module works with Exchange 2013, as well as Exchange 2016 and Exchange Online.
Great script dude. As was previously said, ‘You Sir are officially the man’. Cheers
Hey Skippy,
Way to take the partially documented world of EWS and make it useful to others! I have to dig into this a bit…I discovered it a bit too late, as I had just finished my efforts to do a lot of what this module does…I blame Bing for not bringing me here sooner, causing me to invent the wheel you had already invented (it was Google that brought me here 🙂 )…Just kidding, I didn’t use Bing!
What if the root folders of a mailbox are in a different language? At the moment you have “Sent Items” and “Deleted Items” hardcoded, but this always errors out if the mailbox has a different language set.
What are these hardcoded attributes used for?
Get-MailboxDelegate, Add-MailboxDelegate, … they all give error that the 2 folders cannot be found if any other language is used.
One of the posts in the series explains why the folders are used (for when delegates are configured to move deleted or sent items to the manager’s respective folder). My next task (now that I have completed the updates for the AutoDL module), is to account for non-English folders. At first I couldn’t figure out a reliable or easy enough way to determine the folder’s display name, but a few months ago it occurred to me. So I will be adding that, as well as a configuration file for the settings, including the option to not set permission on those folders if you don’t want to.
Scott, awesome news! I would like to say that your script is really awesome. It helps us admins in so many ways. Cannot believe I did not stumble upon it earlier.
I will keep an eye on this thread.
Thx!
Hi,
I have a question regarding the Remove-MailboxDelegate. Does it also delete the Meeting Delivery scope? Because the built-in cmdlet doesn’t do it (Remove-MailboxFolderPermission)
Meeting request delivery scope applies to the owner/manager, not individual delegates, so when you remove a delegate nothing needs to happen to the scope setting. If you are referring to the setting for a delegate to receive meeting requests if the owner’s setting is to send to the delegates, yes, it is removed when the delegate is removed.
Localization support has been added in v1.5.1. It will automatically use whatever the display name is of the folder.
Whilst your script is excellent we are using an alternative which is to convert the message to normal using a transport rule:
——————————————————————————————————
Convert Private eMails to Normal for selected mailboxes
If the message…Is sent to ‘sharedmailbox@domain.co.uk’
and ‘Sensitivity’ header contains ”private”
Do the following…set message header ‘Sensitivity’ with the value ‘normal’
——————————————————————————————————
You can add as many mailboxes to the rule as you want.
We add a note tot he AD object to state it is in the rule.
Thanks for your great script.
I have a question, does it support the option: “Automatically send a message to delegate summarizing these permissions”?
Can’t seem to give delegates for same owner different -MeetingRequestDeliveryScope . Once I change it on one delegate, it changes it on the other.
John
The meeting request delivery scope does not apply to individual delegates, but to all. That is why when you change it for one, it affects all. This is because the setting isn’t actually an individual delegate setting (even though the module output makes it look like it is), but a property of the mailbox owner.
No, the module doesn’t send a summary email, as that is a feature only provided by Outlook. However, if it is something you would like it to do, I can update the module to have it do that.