PowerShell: Report Mailbox Delegates Script (Office 365 Migration Tool) Part 3

This is the third part (part 1 here and part two here) describing my script that enumerates delegates on mailboxes from a set of users.  This part looks at the second of the two main functions that do all the work as well as the code that ties it all together

Here’s the second main function;

    function Return-MailboxDelegationList
    {
        <# .SYNOPSIS This function returns all the users and groups delegated permissions to a mailbox. Group memberships areenumerated recursively. .DESCRIPTION This function returns all the users and groups delegated permissions to a mailbox. Group memberships areenumerated recursively. .PARAMETER $MailboxToProcess The mailbox object to list delegates of. .PARAMETER $NonDelegated If this switch is specified only mailboxes with no delegates are returned. .PARAMETER $Count This is added to the Verbose string listing the mailbox being processed. #>    

        param
        (
        [Parameter(
            Mandatory=$true)]
            [Microsoft.Exchange.Data.Directory.Management.Mailbox]$MailboxToProcess,
            [switch]$NonDelegated, 
            [int]$Count      
        )
        
        $SamAccountNameSearchString=$DomainNetbiosName+"\"+$MailboxToProcess.SamAccountName
        $Permissions=$MailboxToProcess | Get-MailboxPermission | ?{($_.IsInherited -eq $False) -and -not ($_.User -like "NT AUTHORITY\SELF") -and -not ($_.User -like $SamAccountNameSearchString)}
        #Only process if there are some permissions explictly defined on the mailbox(non-inherited) and are not SELF or the owner.
        Write-Verbose "$Count to go : Processing Mailbox : $MailboxToProcess.DisplayName"
        if(((@($Permissions).Count -gt 0) -and ($Permissions -ne $Null) -and -not $NonDelegated))
        {
            foreach($SinglePermission in $Permissions)
            {
                $objOutput=New-Object -typename PSObject
                
                $objOutput | Add-Member NoteProperty "DisplayName" $MailboxToProcess.DisplayName
                $objOutput | Add-Member NoteProperty "EmailAddress" ([string]($MailboxToProcess.PrimarySMTPAddress))
                $objOutput | Add-Member NoteProperty "DelegatedUser" ""
                $objOutput | Add-Member NoteProperty "AccessRights" ([string]($SinglePermission.AccessRights))
                $objOutput | Add-Member NoteProperty "ExtendedRights" ([string]($SinglePermission.ExtendedRights))
                $objOutput | Add-Member NoteProperty "Group" $false
                $objOutput | Add-Member NoteProperty "Memberof" ""
                $objOutput | Add-Member NoteProperty "MailEnabled" $false
                $objOutput | Add-Member NoteProperty "Message" ""
 
                #Permissions are 
                $FullAccessUserSIDString=$SinglePermission.User.SecurityIdentifier.Value                               
                $objSid=New-Object System.Security.Principal.SecurityIdentifier $FullAccessUserSIDString
                $FullAccessUserName=$null                                    
                $ErrorActionPreference="SilentlyContinue"                    
                $FullAccessUserName=$objSid.Translate([System.Security.Principal.NTAccount])
                $ErrorActionPreference="Stop"
                if ($FullAccessUserName -ne $null)
                { 
                    $DelegatedObject=[adsi]("LDAP://<SID="+$FullAccessUserSIDString+">")   
                    if (($DelegatedObject.mail -ne $null) -and ($DelegatedObject.mail -ne ""))
                    {
                        $objOutput.MailEnabled=$true
                    }                                    
                    if ($DelegatedObject.objectClass -contains "group")
                    {
                        $objOutput.DelegatedUser=$FullAccessUserName.Value                     
                        $objOutput.Group=$true
                        #If it's a group pass the mailbox details and then expand the group membership
                        Return-GroupMemberDetails $objOutput $DelegatedObject
                    }elseif (($DelegatedObject.objectClass -contains "user") -and -not ($DelegatedObject.objectClass -contains "computer"))
                    {
                        $objOutput.DelegatedUser=$DelegatedObject.UserPrincipalName.ToString()
                    }
                }else
                {
                    $objOutput.DelegatedUser=$FullAccessUserSIDString
                    $objOutput.Message="Could not translate SID to a valid object"
                    
                }
                #Only process users and groups (skip computer objects)
                if ($objOutput.DelegatedUser -ne "")
                {
                    Write-Output $objOutput
                }
            }
        }elseif((((@($Permissions).Count -eq 0) -or ($Permissions -eq $Null)) -and $NonDelegated))
        {
            #If nondelegated specified only show mailboxes with no filtered delegated permissions
            $objOutput=New-Object -typename PSObject          
            $objOutput | Add-Member NoteProperty "DisplayName" $MailboxToProcess.DisplayName
            $objOutput | Add-Member NoteProperty "EmailAddress" $MailboxToProcess.PrimarySMTPAddress
            $objOutput | Add-Member NoteProperty "DelegatedUser" ""
            $objOutput | Add-Member NoteProperty "MailEnabled" $false

            $objOutput.MailEnabled=$true
            Write-Output $objOutput          
        }
    }
    if (($OrganizationalUnit -ne "") -and ($OrganizationalUnit -ne $null))
        {
            if([adsi]::exists(("LDAP://" + $OrganizationalUnit)))
            {
                $Mailboxes=Get-Mailbox -OrganizationalUnit $OrganizationalUnit -ResultSize Unlimited
                
                $Count=$Mailboxes.Count
                if ($NonDelegated)
                {
                    #If just an OU is specified get a list of mailboxes with delegations from that OU and then
                    #Get a list of all mailboxes without delegations that are not in the first list.
                    Write-Verbose "Building List of Mailboxes with Delegations"                   
                    $DelegatedEMailAddresses = $Mailboxes |  % {Return-MailboxDelegationList $_ -Count:$Count;$Count-=1} | Select -expandproperty EmailAddress
                    Write-Verbose "Return list of Mailboxes with no delegations that are not in the list of mailboxes with delegations."
                    if ($SubOrganizationalUnit -ne "")
                    {
                        #If a valid subou is specified as well, use the subou as the source for the mailboxes
                        #without delegations (this will be a lot faster if you're only interested
                        #in users in a small OU without delegations and who are not delegated to.
                        if([adsi]::exists(("LDAP://" + $SubOrganizationalUnit)))
                        {
                            $Mailboxes=Get-Mailbox -OrganizationalUnit $SubOrganizationalUnit -ResultSize Unlimited
                        }else
                        {
                            Write-Error "Invalid SubOrganizationalUnit specified."
                        }
                    }                    
                    $Count=$Mailboxes.Count
                    $Mailboxes |  % {Return-MailboxDelegationList -nonDelegated $_ -Count:$Count;$Count-=1} | ? {-not ($DelegatedEmailAddresses -contains $_.EmailAddress)}
                    ##>
                }else
                {
                    $Mailboxes |  % {Return-MailboxDelegationList $_ -Count:$Count;$Count-=1}
                }
            }else
            {
                Write-Error "Invalid OrganizationalUnit specified."
            }
        }
 }

The code breaks down as follows;

function Return-MailboxDelegationList
    {
        param
        (
        [Parameter(
            Mandatory=$true)]
            [Microsoft.Exchange.Data.Directory.Management.Mailbox]$MailboxToProcess,
            [switch]$NonDelegated, 
            [int]$Count      
        )
        
        $SamAccountNameSearchString=$DomainNetbiosName+"\"+$MailboxToProcess.SamAccountName
        $Permissions=$MailboxToProcess | Get-MailboxPermission | ?{($_.IsInherited -eq $False) -and -not ($_.User -like "NT AUTHORITY\SELF") -and -not ($_.User -like $SamAccountNameSearchString)}
        #Only process if there are some permissions explictly defined on the mailbox(non-inh

This is the function that returns all the delegates to a mailbox and their permission level.  All I’m really interested in is the mailbox we want to process and if the script is only looking at $NonDelegated mailboxes.

I also added a $count parameter so that it would should show a progress indicator in Verbose mode and I generate the SamAccountName with the domain NetBios name as a prefix to be used later on.

$Permissions are all the permissions on the mailbox though I filter out any inherited permissions (like the various Exchange ones from the parent OU), SELF and the user’s own SAMAccountName.  This just leaves all the permissions that are not the owner.

        if(((@($Permissions).Count -gt 0) -and ($Permissions -ne $Null) -and -not $NonDelegated))
        {
            foreach($SinglePermission in $Permissions)
            {
                $objOutput=New-Object -typename PSObject
                
                $objOutput | Add-Member NoteProperty "DisplayName" $MailboxToProcess.DisplayName
                $objOutput | Add-Member NoteProperty "EmailAddress" ([string]($MailboxToProcess.PrimarySMTPAddress))
                $objOutput | Add-Member NoteProperty "DelegatedUser" ""
                $objOutput | Add-Member NoteProperty "AccessRights" ([string]($SinglePermission.AccessRights))
                $objOutput | Add-Member NoteProperty "ExtendedRights" ([string]($SinglePermission.ExtendedRights))
                $objOutput | Add-Member NoteProperty "Group" $false
                $objOutput | Add-Member NoteProperty "Memberof" ""
                $objOutput | Add-Member NoteProperty "MailEnabled" $false
                $objOutput | Add-Member NoteProperty "Message" ""
 
                #Permissions are 
                $FullAccessUserSIDString=$SinglePermission.User.SecurityIdentifier.Value                               
                $objSid=New-Object System.Security.Principal.SecurityIdentifier $FullAccessUserSIDString
                $FullAccessUserName=$null                                    
                $ErrorActionPreference="SilentlyContinue"                    
                $FullAccessUserName=$objSid.Translate([System.Security.Principal.NTAccount])
                $ErrorActionPreference="Stop"

The initial line filters mailboxes with no permissions on them and only performs the following code if the $NonDelegated switch isn’t set.  It then enumerates every permission on the delegated permission list.

It then creates a new PSObject to hold all the data I retrieve from the mailbox.

The script then tries to generate information about the SID of the delegated object;  if I can get this I can get an LDAP object for the delegated user and get some more information.  I hack around the fact that the Translate method of the object either translates or errors by setting the result to null;

$FullAccessUserName=$null

and setting the script to silently continue if the translation fails;  the result will still be set to $null in that case and I check for it in the next bit of code.

                if ($FullAccessUserName -ne $null)
                { 
                    $DelegatedObject=[adsi]("LDAP://&lt;SID="+$FullAccessUserSIDString+"&gt;")   
                    if (($DelegatedObject.mail -ne $null) -and ($DelegatedObject.mail -ne ""))
                    {
                        $objOutput.MailEnabled=$true
                    }                                    
                    if ($DelegatedObject.objectClass -contains "group")
                    {
                        $objOutput.DelegatedUser=$FullAccessUserName.Value                     
                        $objOutput.Group=$true
                        #If it's a group pass the mailbox details and then expand the group membership
                        Return-GroupMemberDetails $objOutput $DelegatedObject
                    }elseif (($DelegatedObject.objectClass -contains "user") -and -not ($DelegatedObject.objectClass -contains "computer"))
                    {
                        $objOutput.DelegatedUser=$DelegatedObject.UserPrincipalName.ToString()
                    }
                }else
                {
                    $objOutput.DelegatedUser=$FullAccessUserSIDString
                    $objOutput.Message="Could not translate SID to a valid object"
                    
                }
                #Only process users and groups (skip computer objects)
                if ($objOutput.DelegatedUser -ne "")
                {
                    Write-Output $objOutput
                }

If the script has managed to get a valid SID identifier ($FullAccessUserName -ne $null) for the delegated object I use that to get the directory object.  I then use that to get extra information (like whether it is mail enabled) or whether it’s a group (if so I use the function described in part 2 to get the membership).

I only write the delegated user’s UPN into the DelegatedUser property if it’s a user account.  I then write the entire $objOutput object to the stream with all the information in it.

        }elseif((((@($Permissions).Count -eq 0) -or ($Permissions -eq $Null)) -and $NonDelegated))
        {
            #If nondelegated specified only show mailboxes with no filtered delegated permissions
            $objOutput=New-Object -typename PSObject          
            $objOutput | Add-Member NoteProperty "DisplayName" $MailboxToProcess.DisplayName
            $objOutput | Add-Member NoteProperty "EmailAddress" $MailboxToProcess.PrimarySMTPAddress
            $objOutput | Add-Member NoteProperty "DelegatedUser" ""
            $objOutput | Add-Member NoteProperty "MailEnabled" $false

            $objOutput.MailEnabled=$true
            Write-Output $objOutput          
        }

If the $NonDelegated switch has been specified the $objOutput object is a lot smaller;  we’re only showing information about the mailbox itself (as the script has been told that’s all I’m interested in).

    if (($OrganizationalUnit -ne "") -and ($OrganizationalUnit -ne $null))
        {
            if([adsi]::exists(("LDAP://" + $OrganizationalUnit)))
            {
                $Mailboxes=Get-Mailbox -OrganizationalUnit $OrganizationalUnit -ResultSize Unlimited
                
                $Count=$Mailboxes.Count
                if ($NonDelegated)
                {
                    #If just an OU is specified get a list of mailboxes with delegations from that OU and then
                    #Get a list of all mailboxes without delegations that are not in the first list.
                    Write-Verbose "Building List of Mailboxes with Delegations"                   
                    $DelegatedEMailAddresses = $Mailboxes |  % {Return-MailboxDelegationList $_ -Count:$Count;$Count-=1} | Select -expandproperty EmailAddress
                    Write-Verbose "Return list of Mailboxes with no delegations that are not in the list of mailboxes with delegations."
                    if ($SubOrganizationalUnit -ne "")
                    {
                        #If a valid subou is specified as well, use the subou as the source for the mailboxes
                        #without delegations (this will be a lot faster if you're only interested
                        #in users in a small OU without delegations and who are not delegated to.
                        if([adsi]::exists(("LDAP://" + $SubOrganizationalUnit)))
                        {
                            $Mailboxes=Get-Mailbox -OrganizationalUnit $SubOrganizationalUnit -ResultSize Unlimited
                        }else
                        {
                            Write-Error "Invalid SubOrganizationalUnit specified."
                        }
                    }                    
                    $Count=$Mailboxes.Count
                    $Mailboxes |  % {Return-MailboxDelegationList -nonDelegated $_ -Count:$Count;$Count-=1} | ? {-not ($DelegatedEmailAddresses -contains $_.EmailAddress)}
                    ##&gt;
                }else
                {
                    $Mailboxes |  % {Return-MailboxDelegationList $_ -Count:$Count;$Count-=1}
                }
            }else
            {
                Write-Error "Invalid OrganizationalUnit specified."
            }
        }
 }

This is the main body of the code.  It takes the $OrganizationalUnit specified and checks that it’s valid first.  If so I get all the mailboxes on user accounts in that OU.

If the $NonDelegated switch has been specified I’m only interested in mailboxes that have no delegates.  Additionally, if the $SubOrganizationalUnit parameter has been passed I can narrow the filter down even further by only looking for mailboxes that that don’t have delegates within $SubOrganizationalUnit.

If the $NonDelegated switch hasn’t been passed it’s nice and simple;  I just return all the delegate information about all the mailboxes in the passed OU!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: