Public/Get-GroupWritebackReconciliationOperations.ps1
|
function Get-GroupWritebackReconciliationOperations { [CmdletBinding()] Param( [Parameter(Mandatory = $false)] [Switch] $DoNotWarnIfMissingOnPremDN, [Parameter(Mandatory = $false)] [Switch] $DisableCacheForADGroupObjectSIDLookup ) Process { Write-Verbose "Building cache of all AD groups by ObjectSID." $AllADGroups = @{} Get-ADGroup -Filter * | Foreach-object { $AllADGroups[$_.SID.Value] = $_ } $ADGroups = Get-ADGroup -Filter $Script:ADGroupFilter -Properties member, adminDescription $ADGroupsByAdminDescription = @{} $ADGroups | ForEach-Object { if ( $_.adminDescription -notmatch "^TakenOver_Group_[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" -and $_.adminDescription -notmatch "^Group_[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" ) { Write-Warning "Group '$($_.Name)' does not have a valid adminDescription. Skipping group." continue } $ADGroupsByAdminDescription[($_.adminDescription -replace "^TakenOver_Group_" -replace "^Group_")] = $_ } if (!$ADGroupsByAdminDescription) { Write-Error "No valid on-premises AD groups matching the filter." return @() } $ADGroupsByAdminDescription.GetEnumerator() | ForEach-Object { $ADGroup = $_.Value $EntraIDGroupObjectId = $_.Key Write-Verbose "Processing group '$($ADGroup.Name)' with objectguid '$($ADGroup.ObjectGUID)'." Write-Verbose " - Fetching Entra ID group members for group '$EntraIDGroupObjectId'." $EntraIDMembers = @{} $uri = "https://graph.microsoft.com/v1.0/groups/$EntraIDGroupObjectId/members?`$select=id,onPremisesDistinguishedName,onPremisesDomainName,onPremisesSamAccountName,onPremisesSecurityIdentifier&`$top=999" try { do { $Response = Invoke-RestMethod -Uri $Uri -Method Get -Headers (Get-EntraIDAccessTokenHeader -Profile $Script:AccessTokenProfile) if ($Response.value) { foreach ($Member in $Response.value) { Write-Debug "Processing member with ID '$($Member.id)' of type '$($Member.'@odata.type')' in group '$EntraIDGroupObjectId'." if($Member.'@odata.type' -eq "#microsoft.graph.user") { if ($Member.onPremisesDistinguishedName) { $EntraIDMembers[$Member.onPremisesDistinguishedName] = $Member } else { if (!$DoNotWarnIfMissingOnPremDN.IsPresent) { Write-Warning "Member with ID '$($Member.id)' in group '$EntraIDGroupObjectId' does not have an onPremisesDistinguishedName. Skipping member." } } } elseif($Member.'@odata.type' -eq "#microsoft.graph.group") { $Handled = $false if(![string]::IsNullOrEmpty($Member.onPremisesSecurityIdentifier)) { if($AllADGroups.ContainsKey($Member.onPremisesSecurityIdentifier)) { Write-Debug "Resolved on-premises group member with ID '$($Member.id)' in group '$EntraIDGroupObjectId' via onPremisesSecurityIdentifier." $Handled = $true $EntraIDMembers[$AllADGroups[$Member.onPremisesSecurityIdentifier].DistinguishedName] = $Member } } if(!$Handled) { if($ADGroupsByAdminDescription.ContainsKey($Member.Id)) { Write-Debug "Resolved on-premises group member with ID '$($Member.id)' in group '$EntraIDGroupObjectId' via adminDescription." $Handled = $true $EntraIDMembers[$ADGroupsByAdminDescription[$Member.Id].DistinguishedName] = $Member } } if(!$Handled) { Write-Warning "Member with ID '$($Member.id)' in group '$EntraIDGroupObjectId' is an on-premises group that we are unable to handle. Skipping member." } } else { Write-Warning "Member with ID '$($Member.id)' in group '$EntraIDGroupObjectId' is of unsupported type '$($Member.'@odata.type')'." } } } $Uri = $Response.'@odata.nextLink' } while ($Uri) Write-Verbose " - Found $($EntraIDMembers.Count) members in Entra ID group '$EntraIDGroupObjectId'." } catch { Write-Warning "Failed to fetch members for Entra ID group '$EntraIDGroupObjectId'. Error details: $($_.Exception.Message)" return } # Compare members $ADGroupMemberMap = @{} if($ADGroup.member) { $ADGroupMemberMap = $ADGroup.member | Group-Object -AsHashTable -AsString } # Find members in Entra that are not in AD if($EntraIDMembers) { $EntraIDMembers.Keys | Where-Object { -not $ADGroupMemberMap.ContainsKey($_) } | ForEach-Object { New-GroupWritebackReconciliationOperation -Action "Add member" -Group $ADGroup.DistinguishedName -Member $_ } } else { Write-Warning "No members found in Entra ID group '$EntraIDGroupObjectId'." } # Find members in AD that are not in Entra (and should be removed) if ($ADGroupMemberMap) { $ADGroupMemberMap.Keys | Where-Object { -not $EntraIDMembers.ContainsKey($_) } | ForEach-Object { New-GroupWritebackReconciliationOperation -Action "Remove member" -Group $ADGroup.DistinguishedName -Member $_ } } } } } |