A Tale of Two Scripts: Making Entra Groups Based on OUs
News flash! Microsoft removed something useful again. When making groups in EntraID you used to be able to add users to groups dynamically based on the “organizationalUnit” attribute. That’s since been removed, and now us hybrid admins are left to our own devices on figuring out a workaround. Fret not! I’ve got you covered. Check this out:
(P.S. the scripts I’m explaining here can be found here and here on GitHub)
Part 1 | extensionAttributes, AD, and You!
Peruse through the object properties that you can make dynamic groups on and you’ll see a healthy amount of “extensionAttribute” items. Let’s leverage one of these.

To do this, we’re simply going to “foreach” through all of our users, grab the DistinguishedName property, splice it up, and set it. Simple. See below:
$Users = Get-ADUser -Filter * -Properties SamAccountName, extensionAttribute1
foreach($user in $Users){
$DistinguishedNames = Get-ADUser -Identity $user | Where-Object DistinguishedName -Like "*OU=Users*" | Select-Object -ExpandProperty DistinguishedName
foreach($name in $DistinguishedNames){
$OU = $name.Split("=")[3]
$CleanName = $OU.Split(",")[0]
write-host "$($user.Name) | $CleanName"
Set-ADUser -Identity $user -Add @{"extensionAttribute1"="$CleanName"}
}
}
Now, the gotcha here is the “Where-Object” pipe-through. This is specific to my environment. Yours’ may differ. Plan appropriately. Secondly, just double check what you’re capturing in the $OU variable. If you have different nestings you may need to adjust the numerical value in the split braces.
Part 2 | Makin’ the Groups
Before I go any further remember the following: Utilizing Graph? Use PowerShell 7+. If you remember this very catchy phase then you too can be as well-versed as me*.
Now, let’s connect to Microsoft Graph and only give ourselves the ability to read/write with groups. Whether you’re just running the cmdlet or running the full script: don’t forget to authenticate.
Connect-MgGraph -Scopes "Group.ReadWrite.All"
This next piece will scroll through your created OUs and store them. I made the $BadOUs array as a way to exclude certain OUs that you don’t want an Entra group for.
$BadOUs = @(
<# enter in OUs to ignore here #>
)
$OUNames = Get-ADOrganizationalUnit -filter * -SearchScope OneLevel | Where-Object Name -NotIn $BadOUs | Select-Object Name
Next, run a foreach loop for every OU that the previous command found. We’re going to define the parameters of our Entra groups within as well because we’re naming our groups based on the OUs. Let me break down this array:
foreach($OU in $OUNames){
$OUNameNoSpace = $OU.Name.Replace(" ","")
write-host "Creating Group $($OU.Name) in M365"
$parameters = @{
GroupTypes = @('DynamicMembership')
Description = "$($OU.Name) Users"
DisplayName = "Groups - $($OU.Name)"
MailEnabled = $false
SecurityEnabled = $true
MailNickname = "$OUNameNoSpace"
MembershipRuleProcessingState = "On"
MembershipRule = "(user.extensionAttribute1 -eq `"$($OU.Name)`")"
"[email protected]" = @("https://graph.microsoft.com/v1.0/me")
}
Note: $OUNameNoSpace is exactly what it sounds like. We’ll use this later.
- GroupTypes – must be in array format. Use DynamicMembership for a dynamic group
- Description – can be any string
- DisplayName – self explanatory, but make it a good one!
- MailEnabled – boolean. Turning this to True will make it a Microsoft 365 group.
- SecurityEnabled – boolean. In conjunction, this will enforce a Security group rather than a Microsoft 365 group
- MailNickname – must not contain special characters or spaces
- MembershipRuleProcessingState – set to “On”, so we can define a dynamic rule for group membership
- MembershipRule – this is what your dynamic membership rule/query will be.
- “[email protected]” – wtf does this mean? This is setting an owner for the group. In my script, I just set myself as the owner with the universal URL.
Phew! That’s a lot to digest. Luckily, I made these dynamic based on variables involving the OU properties. You may want to adjust nitpicky things like the substring values in DisplayName and Description.
Lastly, we just run a cool Graph command to create the groups.
Try{
New-MgGroup -BodyParameter $parameters
}
Catch {
Write-Host "Failed to create group for $($OU.Name): $($_.Exception.Message)" -ForegroundColor Red
}
Since we’re in a foreach loop we’re going to meet the goal of creating Entra groups based on our OUs, and since these are dynamic groups with a membership rule query we’re done. We let the cloud do the rest. Once your EntraConnect sync runs, your groups will populate.

That’s all I’ve got for this entry. I hope this helps someone tackle a potentially daunting taste. And as always, remember:
This project is provided “as is” without any warranty of any kind, express or implied. Use it at your own risk. The authors and contributors are not responsible for any damage, data loss, or other issues that may arise from using this software. You are solely responsible for any actions taken based on this code.
*Additional Comments: This was a joke. I ALWAYS forget to use PS7 for Graph commands, then it hits me and I feel like a dummy. Also, that’s not very catchy at all.