Rewrite group check script to not use AD libs #120605

This commit is contained in:
Jurjen Ladenius
2025-07-17 14:45:02 +02:00
parent 847173c58e
commit b6328c1185
4 changed files with 228 additions and 114 deletions

View File

@@ -30,7 +30,8 @@ function GetEligibleAssignments {
[string] $scope [string] $scope
) )
$access_token = (Get-AzAccessToken -TenantId "e9792fd7-4044-47e7-a40d-3fba46f1cd09").Token $access_token_secure = (Get-AzAccessToken -TenantId "e9792fd7-4044-47e7-a40d-3fba46f1cd09").Token
$access_token = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($access_token_secure))
$url = "https://management.azure.com/$scope/providers/Microsoft.Authorization/roleEligibilityScheduleInstances?api-version=2020-10-01&`$filter=atScope()" $url = "https://management.azure.com/$scope/providers/Microsoft.Authorization/roleEligibilityScheduleInstances?api-version=2020-10-01&`$filter=atScope()"

View File

@@ -29,7 +29,7 @@ else {
# .\AzureStoragebloblist.ps1 -subscriptionId "14c2354d-45a9-4e0f-98ff-be58cdbcddc7" -resourcegroupName "ec-measurement" -storageAccountName "stecmeasurementprod" # .\AzureStoragebloblist.ps1 -subscriptionId "14c2354d-45a9-4e0f-98ff-be58cdbcddc7" -resourcegroupName "ec-measurement" -storageAccountName "stecmeasurementprod"
# .\AzureStoragebloblist.ps1 -subscriptionId "2a07dfa7-69ee-4608-b2d5-14124fcccc31" -resourcegroupName "questionnaire-server-weu" -storageAccountName "questionnairedataweu" # .\AzureStoragebloblist.ps1 -subscriptionId "2a07dfa7-69ee-4608-b2d5-14124fcccc31" -resourcegroupName "questionnaire-server-weu" -storageAccountName "questionnairedataweu"
# .\AzureStoragebloblist.ps1 -subscriptionId "54794e27-b714-4346-81bc-05eae7ccb5a5" -resourcegroupName "question-management-api-weu" -storageAccountName "qmprojectionsweu" -excludedContainers "`$logs","`$blobchangefeed" # .\AzureStoragebloblist.ps1 -subscriptionId "54794e27-b714-4346-81bc-05eae7ccb5a5" -resourcegroupName "question-management-api-weu" -storageAccountName "qmprojectionsweu" -excludedContainers "`$logs","`$blobchangefeed"
# .\AzureStoragebloblist.ps1 -subscriptionId "7feeb150-9ee0-4aea-992a-5f3a89d933e6" -resourcegroupName "Results" -storageAccountName "myeffectoryresults" -excludedContainers "`$logs","`$blobchangefeed", "attachments", "azure-webjobs-hosts", "azure-webjobs-secrets", "azure-webjobs-dashboard", "azure-webjobs-hosts", "azure-webjobs-secrets", "hierarchydatesettings-leases", "projectcalculations-leases","resultscleanup-applease","resultscleanup-leases","resultsgroupscorecalculator-leases","testhubname-leases"
class BlobCheck { class BlobCheck {
[string] $SubscriptionId = "" [string] $SubscriptionId = ""

View File

@@ -1,112 +0,0 @@
# import AD Module
Import-Module activedirectory
#Set-ExecutionPolicy unrestricted
#---------------------------
#-------- Variable ---------
#---------------------------
$i=0
#---------------------------
#------- Functions ---------
#---------------------------
function Get-ADGroupName ([string] $ADDistinguishedName)
{
$ADGrpName= (Get-ADGroup -Filter "DistinguishedName -eq '$ADDistinguishedName'").SamAccountName
return $ADGrpName
}
function Get-GroupMemberOf ([string] $ADGroupName)
{
if ($ADGroupName -eq "# Developer") { return } # prevent recursing
$ii++; $a=0
$Message=""; $Prefix=""
$GroupCategory=""; $GroupScope=""; $GroupName=""
For ($a=0; $a -lt $ii; $a++) {$Prefix = $Prefix + " "}
$DNs=(Get-ADGroup $ADGroupName -Properties *).MemberOf
if ($DNs.count -ne 0)
{
foreach ($DN in $DNs)
{
$GroupName = (Get-ADGroupName $DN)
$GroupCategory = (Get-ADGroup $GroupName -Properties *).GroupCategory
$GroupScope = (Get-ADGroup $GroupName -Properties *).GroupScope
$Message="$Prefix $ADGroupName => $GroupName [$GroupCategory - $GroupScope]"
Write-Output $Message
Get-GroupMemberOf $GroupName ' '
}# End ForEach
}# End IF
}#End Function
function Get-UserMemberships ([string] $ADUserSID)
{
$ADUser = Get-ADUser $ADUserSID -Properties *
$ADUserMembers=$ADUser.MemberOf
$ADUserName = $ADUser.name
Write-Host "AD-User: $ADUserName ($ADUserSID)"
Write-Output "AD-User: $ADUserName"
#PrimaryGroup
$ADPrimaryGroupDN = (Get-ADUser -Properties * -Filter "SID -eq '$ADUserSID'").PrimaryGroup
$ADPrimaryGroupName=(Get-ADGroupName $ADPrimaryGroupDN)
$ADGroupCategory=(Get-ADGroup $ADPrimaryGroupName).GroupCategory
$ADGroupScope=(Get-ADGroup $ADPrimaryGroupName).GroupScope
$Message = "Primary Group: $ADPrimaryGroupName [$ADGroupCategory, $ADGroupScope]"
Write-Output $Message
#Other groups
foreach ($ADUserMember in $ADUserMembers)
{
$i++
$ADGroupName = (Get-ADGroupName $ADUserMember)
$ADGroupCategory=(Get-ADGroup $ADGroupName).GroupCategory
$ADGroupScope=(Get-ADGroup $ADGroupName).GroupScope
$Message = "($i) $ADGroupName [$ADGroupCategory, $ADGroupScope]"
Write-Output $Message
Get-GroupMemberOf $ADGroupName ' '
Write-Output " "
}
}
function Get-AllMembershipsOfUsers([string] $ADGroupName)
{
$i=0
$devadmaccounts = get-adgroupmember -Identity $ADGroupName -Recursive
foreach ($devADM in $devadmaccounts)
{
Get-UserMemberships $devADM.SID
Write-Output "======================================================================================================"
}
}
#-----------------------------------------------------------------
Clear-Host
[string] $ADGroupName
[string] $date = Get-Date -Format "yyyy-MM-dd HHmm"
$ADGroupName = "# Developer ADM"
Get-AllMembershipsOfUsers $ADGroupName | Out-file "$date Developer ADM.txt"
$ADGroupName = "Domain Admins"
Get-AllMembershipsOfUsers $ADGroupName | Out-file "$date Domain Admins.txt"
$ADGroupName = "# Developer"
Get-AllMembershipsOfUsers $ADGroupName | Out-file "$date Developer.txt"
$ADGroupName = "# Interne Automatisering Team-Assistent"
Get-AllMembershipsOfUsers $ADGroupName | Out-file "$date Interne Automatisering Team-Assistent.txt"
$ADGroupName = "# Interne Automatisering"
Get-AllMembershipsOfUsers $ADGroupName | Out-file "$date Interne Automatisering.txt"

View 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."
}