Repository navigation
Expand file tree
/
Copy pathResolve-ADGroupRecursive.ps1
More file actions
191 lines (162 loc) · 10.7 KB
/
Copy pathResolve-ADGroupRecursive.ps1
File metadata and controls
191 lines (162 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
<#
.SYNOPSIS
Loest AD-Gruppen rekursiv auf und listet je Gruppe alle wirksamen
Mitglieder.
.DESCRIPTION
Durchsucht einen SearchBase nach Gruppen und ermittelt zu jeder die
Mitglieder ueber alle Verschachtelungsebenen hinweg. Damit wird sichtbar,
wer ueber Umwege tatsaechlich in einer Gruppe steckt - was bei
Berechtigungspruefungen der eigentlich interessante Wert ist.
.NOTES
Domaene, SearchBase, SearchScope und Namensfilter stehen als Variablen am
Dateianfang und muessen angepasst werden.
SearchScope moeglichst auf OneLevel begrenzen oder ueber SearchBase
einschraenken - Subtree ueber die gesamte Domaene dauert entsprechend.
#>
Import-Module ActiveDirectory
$DomainName = "my-domain" # CaseSensitive
$TopLevelDomainName = "local" # CaseSensitive
# SearchScope should be limited to OneLevel if possible. (or limit by setting the SeachBase)
# $SearchScope = "OneLevel"
$SearchScope = "Subtree" # Base (only the queried Item) or 0 # OneLevel (alls item in the OU) or 1 # Subtree (rescursively the OU and all Sub-OUs) or 2
# Search for groups in the following OU
# $SearchBase = "OU=Projektstruktur,OU=GRPMGMT,OU=ITMGMT,DC=$DomainName,DC=$TopLevelDomainName"
$SearchBase = "OU=ITMGMT,DC=$DomainName,DC=$TopLevelDomainName"
$SearchBase = "DC=$DomainName,DC=$TopLevelDomainName"
# Limit to group names that contain the following string (* can be used as a wildcard)
$GroupFilter = "*"
$mygroups = Get-ADGroup -SearchBase $SearchBase -Filter * -SearchScope $SearchScope -Properties modifyTimeStamp, Members, MemberOf | Where-Object Name -like $GroupFilter
function Get-ADGroupMembersRecursive {
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[string]$GroupName,
[System.Collections.Generic.Dictionary[string, object]]$ProcessedGroups = (New-Object 'System.Collections.Generic.Dictionary[string, object]')
)
function Get-Members {
param (
[Parameter(Mandatory = $true)]
[string]$GroupName, # Group, which should be revolved
[Parameter(Mandatory = $true)]
[int]$Level, # Which level are we at?
[Parameter(Mandatory = $false)]
[string]$SourceGroupName = '', # where did the call come from
[Parameter(Mandatory = $false)]
[string]$Breadcrumb = '', # Track recursive group memberships
[Parameter(Mandatory = $true)]
[System.Collections.Generic.Dictionary[string, object]]$ProcessedGroups
)
$userList = @()
# Check if group has already been processed
if ($ProcessedGroups.ContainsKey($GroupName)) {
# Arriving at this point, the group has been seen before.
# Write-Host("")
# Write-Host("What kind of group are we examining (GroupName): “ + $GroupName) -ForegroundColor Red
# Write-Host("Where do we come from (SourceGroupName) : “ + $SourceGroupName) -ForegroundColor Red
# Write-Host("Total path so far (breadcrumb) : ” + $Breadcrumb) -ForegroundColor Red
# The MinLevel is required to determine the number that is added to the current level $level
# Example: $_.level - $MinLevel results in 0 if it is the lowest level
# Therefore: ($_.level - $MinLevel) + $level gives the correct level
$MinLevel = ($ProcessedGroups[$GroupName].level | Measure-Object -Minimum).Minimum
# Was it a cached group?
Write-Host("CG-") -NoNewline -ForegroundColor Green
$ProcessedGroups[$GroupName] | ForEach-Object {
$userList += [pscustomobject]@{
member = $_.member
inGroup = $_.inGroup # same like $GroupName
SourceGroupName = $SourceGroupName # not use $_.SourceGroupName, because it has changed!
level = [int](($_.level - $MinLevel) + $level) # calculate new level based on the $MinLevel (see above)
# Search the Group in the current breadcrumb (because of recursion, there could be groups in groups... therefore it should be replaced)
# $dasWasZuErsetzenIst = ($_.breadcrumb -split "->")[0..(($_.breadcrumb -split "->").IndexOf($GroupName))] -join "->"
# ist das Gleiche wie
# $dasWasZuErsetzenIst = ($_.breadcrumb -match '^.*?->' + $GroupName + '->') ? $matches[0] : $null
breadcrumb = $_.breadcrumb -replace ('^.*?->' + $GroupName + '->'), ($breadcrumb + "->") # New breadcrumb is the old breadcrumb replaced up to the point, where to groupname first appears
cached = $true # this is not $false anymore... it is good to know, if this was cached or not.
}
}
return $userList
}
else {
# Was it a New Group?
Write-Host("NG-") -NoNewline -ForegroundColor Yellow
# Write-Host("")
# Write-Host("What kind of group are we examining (GroupName): “ + $GroupName) -ForegroundColor Blue
# Write-Host("Where do we come from (SourceGroupName) : “ + $SourceGroupName) -ForegroundColor Blue
# Write-Host("Total path so far (breadcrumb) : ” + $Breadcrumb) -ForegroundColor Blue
# If this $groupName is NOT cached, resolve the users and groups und call this function recursively if it is of type group.
Try {
# If an foreigenSecurityPrincipal is in a Group and the DN can not be resolved by the other domain controller, an error occures in Get-ADGroupMember.
# One example could be an orphaned SID
# You have to take a look here: CN=ForeignSecurityPrincipals,DC=AB,DC=local
# I something can not be resolved, it should be deleted
$members = Get-ADGroupMember -Identity $GroupName -ErrorAction Stop
foreach ($member in $members) {
$IsForeignSecurityPrincipal = $false
if (($member.distinguishedName -split ',DC=')[1] -ne $DomainName) {
$IsForeignSecurityPrincipal = $true;
#Write-Host($member.distinguishedName -split ',DC=')[1]
}
if ($member.objectClass -eq 'user') {
$userList += [pscustomobject]@{
member = $member.SamAccountName
inGroup = $GroupName
SourceGroupName = $SourceGroupName
level = $Level
breadcrumb = ($Breadcrumb + "->[user]" + $member.SamAccountName)
cached = $false
}
}
elseif ($member.objectClass -eq 'group' -and (-not $IsForeignSecurityPrincipal)) {
$newLevel = $Level + 1
$newBreadcrumb = $Breadcrumb + "->" + $member.SamAccountName # Here: The Groupname is being added to the breadcrumb
$nestedGroupMembers = Get-Members -GroupName $member.SamAccountName -Level $newLevel -SourceGroupName $GroupName -Breadcrumb $newBreadcrumb -ProcessedGroups $ProcessedGroups
$userList += $nestedGroupMembers # At this point, only user objects are in $nestedGroupMembers
}
}
}
Catch {
Write-Host("") -ForegroundColor Yellow
Write-Host("Fehler in: " + $GroupName) -ForegroundColor Yellow
Write-Host("SourceGroupName: " + $Breadcrumb ) -ForegroundColor Yellow
Write-Host("Breadcrumb: " + $Breadcrumb ) -ForegroundColor Yellow
return $userList
}
# Cache results of the Group
$ProcessedGroups[$GroupName] = $userList
return $userList
} # End of Else
}
$userList = @()
# The next line returns the list of all users in the group and subgroups and returns the dependencies, from which source the user is in what group and give an exact explanation in form of breadcrumbs
$userList += Get-Members -GroupName $GroupName -Level 0 -Breadcrumb $GroupName -ProcessedGroups $ProcessedGroups
return $userList
}
Write-Host('Warning: The following step can take a very long time, depending on the size of $mygroups and the recursion depth!') -ForegroundColor Red
$myUsers = ($mygroups | ForEach-Object { Get-ADGroupMembersRecursive -GroupName $_.SamAccountName }) # Recursively resolve the groups
# $myUsers | Where-Object member -eq bde01 # see if a user and how a user obtained the permissions!
$maxlevel = $myUsers.level | Sort-Object -Unique -Descending | Select-Object -First 1
$myUsers | Out-GridView -Title "All Users and Group Memberships"
# $indexedMyUsers | Out-GridView -Title "All Users and Group Memberships (indexiert)"
Write-Host('Max Level: ' + $maxlevel) -ForegroundColor Red
# # Caution: Takes a long time: Returns the number of authorizations for a user. Is an indication of too many useless authorizations
# $gesamtanzahl = $myUsers.member | Sort-Object -Unique | % { [pscustomobject]@{member=$_;Anzahl=($myUsers | Where member -eq $_ | Measure-Object).Count}}
# Get-ADPrincipalGroupMembership -Identity myuser
# Idee ProcessedGroups könnte man um die AD Property der Gruppe erweitern und modifyTimeStamp mit abspeichern, um viele Abfragen an das AD zu reduzieren.
Get-ADGroup -Identity "AG_Personal_R" -Properties samAccountName, modifyTimeStamp | Select-Object samAccountName, modifyTimeStamp
$myUsers | Group-Object -Property breadcrumb | Where-Object Count -gt 1 | Select-Object -ExpandProperty name
# # This solution gets every group, resolves recursively (over sub-groups) all users, which are belonging to it and measures the time receiving it and the last modification date of the group
# $howmany = 1000
# $start = Get-Date
# $erg = Get-ADGroup -Filter * -Properties modifyTimeStamp | Select -First $howmany| % {
# Write-Host(".") -NoNewLine
# $groupSamAccountName = $_.samAccountName;
# $modifyTimeStamp = $_.modifyTimeStamp
# [PSCustomObject]@{
# groupSamAccountName = $groupSamAccountName
# modifyTimeStamp = $modifyTimeStamp
# groupMemberUser = (Get-ADGroupMember -Recursive $_ | Where objectClass -eq user)
# }
# }
# Write-Host("Laufzeit: " + ((Get-Date) - $start))
# ($erg | Where groupSamAccountName -eq "MDBS_Lehrhaus_W").groupMemberUser
# $erg | Sort-Object -Property modifyTimeStamp -Descending | select -First 20
# # Get-ADUser -Filter * -Properties modifyTimeStamp | Sort-Object -Property modifyTimeStamp -Descending | Select -First 50 -Property Name,SamAccountName,modifyTimeStamp