mirror of
https://dev.azure.com/effectory/Survey%20Software/_git/Cloud%20Engineering
synced 2026-02-27 18:52:18 +01:00
Rewrite group check script to not use AD libs #120605
This commit is contained in:
225
Powershell/Lists/Entra/GroupMemberships.ps1
Normal file
225
Powershell/Lists/Entra/GroupMemberships.ps1
Normal file
@@ -0,0 +1,225 @@
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$GroupId
|
||||
)
|
||||
|
||||
# GroupMemberships.ps1 -GroupId "# Developer ADM"
|
||||
# GroupMemberships.ps1 -GroupId "Domain Admins"
|
||||
# GroupMemberships.ps1 -GroupId "# Developer"
|
||||
# GroupMemberships.ps1 -GroupId "# Interne Automatisering Team-Assistent"
|
||||
# GroupMemberships.ps1 -GroupId "# Interne Automatisering"
|
||||
|
||||
[string] $date = Get-Date -Format "yyyy-MM-dd HHmm"
|
||||
[string] $OutputPath = ".\$date ($GroupId) group members.csv"
|
||||
[string] $membershipOutputPath = ".\$date ($GroupId) group memberships - parent groups .csv"
|
||||
|
||||
# Connect to Microsoft Graph if not already connected
|
||||
Write-Host "Connecting to Microsoft Graph..."
|
||||
Connect-MgGraph -Scopes "Group.Read.All", "GroupMember.Read.All" -NoWelcome
|
||||
|
||||
# If GroupId is actually a group name, resolve it to the actual GroupId
|
||||
if ($GroupId -notmatch '^[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-Host "Resolving group name '$GroupId' to GroupId..."
|
||||
try {
|
||||
$group = Get-MgGroup -Filter "displayName eq '$GroupId'" -ErrorAction Stop
|
||||
if ($group) {
|
||||
if ($group.Count -gt 1) {
|
||||
Write-Warning "Multiple groups found with name '$GroupId'. Using the first one."
|
||||
$GroupId = $group[0].Id
|
||||
} else {
|
||||
$GroupId = $group.Id
|
||||
}
|
||||
Write-Host "Resolved to GroupId: $GroupId"
|
||||
} else {
|
||||
Write-Error "Group with name '$GroupId' not found."
|
||||
exit 1
|
||||
}
|
||||
} catch {
|
||||
Write-Error "Error resolving group name: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Function to get groups that this group is a member of (reverse membership)
|
||||
function Get-GroupMembershipRecursive {
|
||||
param(
|
||||
[string]$GroupId,
|
||||
[int]$Level = 0,
|
||||
[hashtable]$ProcessedMemberships = @{}
|
||||
)
|
||||
|
||||
# Check for circular reference in membership chain
|
||||
if ($ProcessedMemberships.ContainsKey($GroupId)) {
|
||||
Write-Warning "Circular membership reference detected for group: $GroupId"
|
||||
return @()
|
||||
}
|
||||
|
||||
# Check for maximum depth
|
||||
if ($Level -gt 50) {
|
||||
Write-Warning "Maximum membership recursion depth reached for group: $GroupId"
|
||||
return @()
|
||||
}
|
||||
|
||||
# Mark group as being processed for membership
|
||||
$ProcessedMemberships[$GroupId] = $true
|
||||
$membershipResults = @()
|
||||
|
||||
try {
|
||||
# Get groups that this group is a member of
|
||||
$memberOfGroups = Get-MgGroupMemberOf -GroupId $GroupId -All
|
||||
|
||||
foreach ($parentGroup in $memberOfGroups) {
|
||||
if ($parentGroup.AdditionalProperties['@odata.type'] -eq '#microsoft.graph.group') {
|
||||
$parentGroupDetails = Get-MgGroup -GroupId $parentGroup.Id -Select "DisplayName,Mail,GroupTypes"
|
||||
|
||||
$membershipObject = [PSCustomObject]@{
|
||||
ChildGroupId = $GroupId
|
||||
ParentGroupId = $parentGroup.Id
|
||||
ParentGroupName = $parentGroupDetails.DisplayName
|
||||
ParentGroupType = $parentGroupDetails.GroupTypes -join ","
|
||||
ParentGroupEmail = $parentGroupDetails.Mail
|
||||
MembershipLevel = $Level
|
||||
}
|
||||
|
||||
$membershipResults += $membershipObject
|
||||
|
||||
# Recursively get parent groups of this parent group
|
||||
if ($parentGroup.Id -ne $GroupId) {
|
||||
$nestedMemberships = Get-GroupMembershipRecursive -GroupId $parentGroup.Id -Level ($Level + 1) -ProcessedMemberships $ProcessedMemberships
|
||||
$membershipResults += $nestedMemberships
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Error "Error getting group memberships for $GroupId`: $($_.Exception.Message)"
|
||||
}
|
||||
finally {
|
||||
# Remove from processed memberships when done
|
||||
$ProcessedMemberships.Remove($GroupId)
|
||||
}
|
||||
|
||||
return $membershipResults
|
||||
}
|
||||
|
||||
|
||||
# Initialize collections
|
||||
$groupMembers = @()
|
||||
$processedGroups = @{}
|
||||
$groupStack = @()
|
||||
|
||||
# Function to get group members recursively
|
||||
# This function will handle circular references and maximum recursion depth
|
||||
function Get-GroupMembersRecursive {
|
||||
param(
|
||||
[string]$GroupId,
|
||||
[int]$Level = 0,
|
||||
[string]$ParentGroupName = ""
|
||||
)
|
||||
|
||||
# Check for circular reference
|
||||
if ($processedGroups.ContainsKey($GroupId)) {
|
||||
Write-Warning "Circular reference detected for group: $GroupId"
|
||||
return
|
||||
}
|
||||
|
||||
# Check for stack overflow (max depth)
|
||||
if ($Level -gt 50) {
|
||||
Write-Warning "Maximum recursion depth reached for group: $GroupId"
|
||||
return
|
||||
}
|
||||
|
||||
# Mark group as being processed
|
||||
$processedGroups[$GroupId] = $true
|
||||
$groupStack += $GroupId
|
||||
|
||||
try {
|
||||
# Get the group information
|
||||
$group = Get-MgGroup -GroupId $GroupId -ErrorAction Stop
|
||||
|
||||
# Get group members
|
||||
$members = Get-MgGroupMember -GroupId $GroupId -All
|
||||
|
||||
foreach ($member in $members) {
|
||||
# Create custom object for the result
|
||||
$memberObject = [PSCustomObject]@{
|
||||
ParentGroupId = $GroupId
|
||||
ParentGroupName = $group.DisplayName
|
||||
ParentGroupType = $group.GroupTypes -join ","
|
||||
MemberId = $member.Id
|
||||
MemberType = $member.AdditionalProperties['@odata.type'] -replace '#microsoft.graph.', ''
|
||||
Level = $Level
|
||||
Path = ($groupStack -join " -> ") + " -> " + $member.Id
|
||||
}
|
||||
|
||||
# Get member details based on type
|
||||
switch ($member.AdditionalProperties['@odata.type']) {
|
||||
'#microsoft.graph.user' {
|
||||
$user = Get-MgUser -UserId $member.Id -Select "DisplayName,UserPrincipalName,Mail"
|
||||
$memberObject | Add-Member -NotePropertyName "MemberName" -NotePropertyValue $user.DisplayName
|
||||
$memberObject | Add-Member -NotePropertyName "MemberUPN" -NotePropertyValue $user.UserPrincipalName
|
||||
$memberObject | Add-Member -NotePropertyName "MemberEmail" -NotePropertyValue $user.Mail
|
||||
}
|
||||
'#microsoft.graph.group' {
|
||||
$memberGroup = Get-MgGroup -GroupId $member.Id -Select "DisplayName,Mail,GroupTypes"
|
||||
$memberObject | Add-Member -NotePropertyName "MemberName" -NotePropertyValue $memberGroup.DisplayName
|
||||
$memberObject | Add-Member -NotePropertyName "MemberUPN" -NotePropertyValue ""
|
||||
$memberObject | Add-Member -NotePropertyName "MemberEmail" -NotePropertyValue $memberGroup.Mail
|
||||
$memberObject | Add-Member -NotePropertyName "MemberGroupTypes" -NotePropertyValue ($memberGroup.GroupTypes -join ",")
|
||||
}
|
||||
default {
|
||||
$memberObject | Add-Member -NotePropertyName "MemberName" -NotePropertyValue "Unknown"
|
||||
$memberObject | Add-Member -NotePropertyName "MemberUPN" -NotePropertyValue ""
|
||||
$memberObject | Add-Member -NotePropertyName "MemberEmail" -NotePropertyValue ""
|
||||
}
|
||||
}
|
||||
|
||||
$script:groupMembers += $memberObject
|
||||
|
||||
# If member is a group, recurse into it
|
||||
if ($member.AdditionalProperties['@odata.type'] -eq '#microsoft.graph.group') {
|
||||
# Check if this group is already in the current path to prevent immediate loops
|
||||
if ($member.Id -notin $groupStack) {
|
||||
Get-GroupMembersRecursive -GroupId $member.Id -Level ($Level + 1) -ParentGroupName $group.DisplayName
|
||||
} else {
|
||||
Write-Warning "Immediate circular reference detected. Skipping group: $($memberGroup.DisplayName)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Error "Error processing group $GroupId`: $($_.Exception.Message)"
|
||||
}
|
||||
finally {
|
||||
# Remove from stack and processed groups when done
|
||||
$groupStack = $groupStack[0..($groupStack.Length-2)]
|
||||
$processedGroups.Remove($GroupId)
|
||||
}
|
||||
}
|
||||
|
||||
# Start the recursive process
|
||||
Write-Host "Starting recursive group membership scan for group: $GroupId"
|
||||
Get-GroupMembersRecursive -GroupId $GroupId
|
||||
|
||||
# Export results to CSV
|
||||
if ($groupMembers.Count -gt 0) {
|
||||
$groupMembers | Export-Csv -Path $OutputPath -NoTypeInformation
|
||||
Write-Host "Results exported to: $OutputPath"
|
||||
Write-Host "Total members found: $($groupMembers.Count)"
|
||||
} else {
|
||||
Write-Host "No members found in the specified group."
|
||||
}
|
||||
|
||||
# Get group memberships (groups this group belongs to)
|
||||
Write-Host "Getting group memberships for group: $GroupId"
|
||||
$groupMemberships = Get-GroupMembershipRecursive -GroupId $GroupId
|
||||
|
||||
# Export group memberships to separate CSV if any found
|
||||
if ($groupMemberships.Count -gt 0) {
|
||||
$groupMemberships | Export-Csv -Path $membershipOutputPath -NoTypeInformation
|
||||
Write-Host "Group memberships exported to: $membershipOutputPath"
|
||||
Write-Host "Total parent groups found: $($groupMemberships.Count)"
|
||||
} else {
|
||||
Write-Host "No group memberships found for the specified group."
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user