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

This is the second part (part 1 here, final part here) describing my script that enumerates delegates on mailboxes from a set of users.  This part looks at the first of the two main functions that do all the work.

Having looked through the script, there may well be better / cleaner ways of doing this;  but I’m publishing this ‘as is’ while remembering writing this on the fly with a curious customer in one ear and a hasty Project Manager in the other 🙂Here’s the first function in full;

    function Return-GroupMemberDetails
    {
        <#
        .SYNOPSIS
        This function will recursively list the details of each member of a passed group.
        .DESCRIPTION
        This function will recursively list the details of each member of a passed group.
        .PARAMETER $OutDetail
        This is a hashtable containing the passed output details of the group being enumerated.
        .PARAMETER $Group
        This is the group which needs it's membership enumerating.
        #>

        param
        (
        [Parameter(
            Mandatory=$true)]
            [PSObject]$OutDetail,
        [Parameter(
            Mandatory=$true)]
            [System.DirectoryServices.DirectoryEntry]$Group
        )     
        $objMemberOutput=New-Object -typename PSObject        
        $objMemberOutput | Add-Member NoteProperty "DisplayName" $OutDetail.DisplayName
        $objMemberOutput | Add-Member NoteProperty "EmailAddress" $OutDetail.EmailAddress
        $objMemberOutput | Add-Member NoteProperty "DelegatedUser" $OutDetail.DelegatedUser
        $objMemberOutput | Add-Member NoteProperty "AccessRights" $OutDetail.AccessRights
        $objMemberOutput | Add-Member NoteProperty "ExtendedRights" $OutDetail.ExtendedRights
        $objMemberOutput | Add-Member NoteProperty "Group" $OutDetail.Group
        $objMemberOutput | Add-Member NoteProperty "Memberof" $OutDetail.MembeOf
        $objMemberOutput | Add-Member NoteProperty "MailEnabled" $OutDetail.MailEnabled
        $objMemberOutput | Add-Member NoteProperty "Message" $OutDetail.Message
        
        $objMemberOutput.MemberOf=$DomainNetbiosName+"\"+$Group.SamAccountName
        foreach($MemberDN in $Group.Member)
        {
            $MemberObject=[adsi]("LDAP://"+$MemberDN)
            $objMemberOutput.EmailAddress=([string]$MemberObject.mail)
            if (($MemberObject.mail -ne $null) -and ($MemberObject.mail -ne ""))
            {
                $objMemberOutput.MailEnabled=$true
            }
            #Only process users and groups (skip computer objects)
            if ($MemberObject.objectClass -contains "group")
            {
                $objMemberOutput.DelegatedUser=$DomainNetbiosName+"\"+$MemberObject.SamAccountName
                $objMemberOutput.Group = $true
                Return-GroupMemberDetails $objMemberOutput $MemberObject
                Write-Output $objMemberOutput
            }elseif (($MemberObject.objectClass -contains "user") -and -not ($MemberObject.objectClass -contains "computer"))
            {
                $objMemberOutput.Group = $false
                $objMemberOutput.DelegatedUser=$MemberObject.UserPrincipalName.Value
                Write-Output $objMemberOutput
            }                
        }   

As you might expect, this gets all the members of a group.  It doesn’t just return those members though, but rather enumerates any more groups recursively and adds their permissions to the original parent object.

        param
        (
        [Parameter(
            Mandatory=$true)]
            [PSObject]$OutDetail,
        [Parameter(
            Mandatory=$true)]
            [System.DirectoryServices.DirectoryEntry]$Group
        )  

This defines the two parameters the function requires.  $Group is a directory entry for the group we want to enumerate the membership of.  $OutDetail is an object that holds the information we want to display about each member of the group.  This is passed (instead of grabbed from the group itself) so we can get details of the parent object (the mailbox with the delegation).

        $objMemberOutput=New-Object -typename PSObject        
        $objMemberOutput | Add-Member NoteProperty "DisplayName" $OutDetail.DisplayName
        $objMemberOutput | Add-Member NoteProperty "EmailAddress" $OutDetail.EmailAddress
        $objMemberOutput | Add-Member NoteProperty "DelegatedUser" $OutDetail.DelegatedUser
        $objMemberOutput | Add-Member NoteProperty "AccessRights" $OutDetail.AccessRights
        $objMemberOutput | Add-Member NoteProperty "ExtendedRights" $OutDetail.ExtendedRights
        $objMemberOutput | Add-Member NoteProperty "Group" $OutDetail.Group
        $objMemberOutput | Add-Member NoteProperty "Memberof" $OutDetail.MembeOf
        $objMemberOutput | Add-Member NoteProperty "MailEnabled" $OutDetail.MailEnabled
        $objMemberOutput | Add-Member NoteProperty "Message" $OutDetail.Message
        
        $objMemberOutput.MemberOf=$DomainNetbiosName+"\"+$Group.SamAccountName

$objMemberOutput is a new object I will use to hold all the reporting details about the delegating I’m looking at.  I can output this to the stream and the administrator running the script can query, sort and process as they see fit.

This is an example of a block of code that can be done in a much better way.  It was written because just creating a new object and copying an existing object just makes the new variable a reference to the old one;  i.e. if I make a change to the new variable the original gets changed too.

So with this code I create a new object then copy each property individually so the new object can be modified without changing the original.

A better way would have been;

$objMemberOutput=$OutDetail.PSObject.Copy()

Much nicer 🙂

        foreach($MemberDN in $Group.Member)
        {
            $MemberObject=[adsi]("LDAP://"+$MemberDN)
            $objMemberOutput.EmailAddress=([string]$MemberObject.mail)
            if (($MemberObject.mail -ne $null) -and ($MemberObject.mail -ne ""))
            {
                $objMemberOutput.MailEnabled=$true
            }
            #Only process users and groups (skip computer objects)
            if ($MemberObject.objectClass -contains "group")
            {
                $objMemberOutput.DelegatedUser=$DomainNetbiosName+"\"+$MemberObject.SamAccountName
                $objMemberOutput.Group = $true
                Return-GroupMemberDetails $objMemberOutput $MemberObject
                Write-Output $objMemberOutput
            }elseif (($MemberObject.objectClass -contains "user") -and -not ($MemberObject.objectClass -contains "computer"))
            {
                $objMemberOutput.Group = $false
                $objMemberOutput.DelegatedUser=$MemberObject.UserPrincipalName.Value
                Write-Output $objMemberOutput
            }                
        }    

I enumerate each member of the group (represented as a Distringuished Name in $MemberDN).  I use that to reference it via ADSI and get the object itself.

After that I update the object with all the details we’re going to output ($objMemberOutput) with the email address of the group member object I’m referencing.  At the same time I update the MailEnabled property once I know it’s got an email address.

The only two group members I’m interested in are groups (which I’ll need to enumerate the membership of) and users.  I don’t want computer objects.

For a group I recursively call the function again and output the result to the stream.  I update the $objMemberOutput object and pass that as the new $OutDetails when I call the function again.

For a user delegate I update the name of the DelegatedUser to the reflect the UPN before writing it to the stream.

Leave a comment