So, I had a long blurb about how long it’s been since I posted because I was on vacation… But if you read the last post you know that I realized I needed to update contacts first, so that was my post, and this should get posted the next day. ANYWAY, to… THE POST!

This particular post goes into great detail about setting up groups that exist in O365 and don’t exist on premise. It gets the groups, gets their membership, builds the groups in AD, and populates membership.

ADVICE TIME!!!! DON’T DO WHAT I JUST DESCRIBED! SERIOUSLY!!! *cough cough* Sorry about that, I fixed my caps button, anyway, don’t. There are two reasons for this, first off, you cannot add contacts to groups on premise, you can only add other groups and users. Secondly, existing on premise groups will get screwed up members, which if they are used for access control, could cause more looting than Ferguson.

Another piece of advice, when running foreach loops in your code, always put a $null value at the top of the foreach loop, that way you can make sure you aren’t adding a bunch of people to the wrong group. (Or having your dog knock over your pina colada, and getting your grandmother all wet… Because nobody likes that) This is just good practice in code anyway, but especially important here.

So the first piece of code I’ll go over is the end result of getting the groups down and adding their membership. I would highly, and I mean HIGHLY recommend you do this into an empty OU so that you can play with the groups afterwards because you will always have missing members. I’ve added the ability to place a parameter of -searchBase. This will allow you to specify an OU by it’s distinguished name (Example: “OU=O365 Groups,OU=TestUsers,DC=Girindustries,DC=com”) This will save you a lot of headaches. Additionally, I’ve added the -userSearchBase and -contactSearchBase commands to do the same thing. This will help if you have a disabled users OU. Lastly, I added a -limitGroupMembershipScope to allow you to say that the groupSearchBase flag can be used for members of other groups as well. (That is really confusing… Hmmm… So basically if a group has another group as a member, this allows you to scope where it will look for the ‘member’ group where you would look for the original group… That makes more sense right!? RIGHT!?!?)

The next part is the contact vs group vs user. Basically it will look and match up the object type, because it needs to use a different command to fetch that object.

The last piece that is important is the way the membership is added. It does this by overwriting current members (Which is why I said to specify a searchbase). I had to do it this way because you cannot use Add-ADGroupMember with a contact, so instead it writes directly to the Members attribute on the group with an array of the DNs of the objects that are members. So… Here is the code:

Param(
    [string]$groupSearchBase,
    [string]$userSearchBase,
    [string]$contactSearchBase,
    [switch]$limitGroupMembershipScope
)
$groups = Get-Group -Resultsize Unlimited
Set-Location AD:
$newGroups = 0
$existingGroupsUpdated = 0
foreach ($group in $groups) {
    $members = $null
    $members = Get-DistributionGroupMember -Identity $group.Name -Resultsize Unlimited
    if ($members) {
        $samAccountName = $group.WindowsEmailAddress.TrimEnd('@girindustries.com')
        $displayName = $group.displayName
        $groupExistsTest = $null
        $groupExistsTest2 = $null
        if ($searchBase) {
            $groupExistsTest = Get-ADGroup -Filter { SamAccountName -like $samAccountName } -SearchBase $searchBase
            $groupExistsTest2 = Get-ADGroup -Filter { samAccountName -like $displayName } -SearchBase $searchBase
        }
        else {
            $groupExistsTest = Get-ADGroup -Filter { SamAccountName -like $samAccountName }
            $groupExistsTest2 = Get-ADGroup -Filter { samAccountName -like $displayName }
        }
        $dlMembers = New-Object System.Collections.ArrayList
        foreach ($member in $members) {
            $UPN = $member.WindowsLiveID
            if ($member.RecipientType -like "*user*") {
                if ($userSearchBase) {
                    $test = (Get-ADUser -Filter { userPrincipalName -like $UPN } -SearchBase $userSearchBase).distinguishedName
                }
                else {
                    $test = (Get-ADUser -Filter { userPrincipalName -like $UPN }).distinguishedName
                }
            }
            elseif ($member.RecipientType -like "*group*") {
                $memberName = $member.Name
                if ($limitGroupMembershipScope) {
                    $test = (Get-ADGroup -Filter { name -like $memberName } -SearchBase $groupSearchBase).distinguishedName
                }
                else {
                    $test = (Get-ADGroup -Filter { name -like $memberName }).distinguishedName
                }
            }
            elseif ($member.RecipientType -like "*contact*") {
                Set-Location C:
                $memberName = $member.Name
                if ($contactSearchBase) {
                    $test = (Get-ADObject -Filter { name -like $memberName } -SearchBase $contactSearchBase).distinguishedName
                }
                else {
                    $test = (Get-ADObject -Filter { name -like $memberName }).distinguishedName
                }
                Set-Location AD:
            }
            if ($test) {
                $dlMembers.Add($test)
            }
            else {
                Write-Host "$member not found in AD"
            }
        }
        $userPrincipalName = $group.WindowsEmailAddress
        if ($groupExistsTest) {
            $groupDN = $groupExistsTest.distinguishedName
            Set-ItemProperty -Path $groupDN -Name mail -Value $userPrincipalName
            $existingGroupsUpdated++
        }
        elseif ($groupExistsTest2) {
            $groupDN = $groupExistsTest2.distinguishedName
            Set-ItemProperty -Path $groupDN -Name mail -Value $userPrincipalName
            $existingGroupsUpdated++
            $samAccountName = $displayName
        }
        else {
            New-ADGroup -Name $group.Name -DisplayName $group.displayName -GroupCategory security -GroupScope Universal -OtherAttributes @{'mail' = $userPrincipalName } -Path "OU=testusers,DC=girindustries,DC=local"
            $newGroups++
        }
        [array]$dlArray = $dlMembers
        Set-ItemProperty -Path $groupDN -Name Member -Value $dlArray
    }
}
Set-Location C:
Write-Host "$newGroups were created!"
Write-Host "$existingGroupsUpdated were updated!"

Now, you thought this was the end huh? But it isn’t. See, the groups still won’t likely be built out properly. There will be some weird one offs, like Disabled users, or ‘Williams,’ who goes by Bill (Damn it Bill!) in Office 365, or other things. So I wrote this fancy script to help compare group membership. This can take a while to run depending on how many groups you have, so I’d kick it off and come back later.

$groups = Get-ADGroup -Filter * -SearchBase 'OU=testusers,DC=girindustries,DC=local'
$erroredGroups = New-Object System.Collections.Arraylist
foreach ($group in $groups) {
    $members = Get-ADGroupMember $group
    $cloudMembers = Get-DistributionGroupMember $group.Name -Resultsize Unlimited
    foreach ($member in $members) {
        foreach ($cloudMember in $cloudMembers) {
            if ($cloudMember.Name -like $member.Name) {
                $found = $true
                break
            }
        }
        if ($found -eq $null) {
            $groupMisMatch = $true
        }
        else {
            $found = $null
        }
    }
    if ($groupMisMatch) {
        $erroredGroups.Add($group.Name)
        $groupMisMatch = $null
    }
}
$erroredGroups | Export-Csv -Path .\erroredGroups
Expand

This will dump a CSV with all the groups that have missmatched members. An important caveat of this is that it will show an error if the group has those pesky contacts in it. (Since the contacts won’t show up in Get-ADGroupMember again). You can use this list to figure out why the group membership isn’t matching properly.

This is a five part series!

Part 1
Part 2
Part 3
Part 4
Part 5

Leave a Reply

Your email address will not be published. Required fields are marked *