mirror of
https://dev.azure.com/effectory/Survey%20Software/_git/Cloud%20Engineering
synced 2026-02-27 18:52:18 +01:00
226 lines
9.0 KiB
PowerShell
226 lines
9.0 KiB
PowerShell
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."
|
|
}
|
|
|