-
-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathExport-AllADUserTransitiveGroupMemberships.ps1
More file actions
189 lines (159 loc) · 8.3 KB
/
Copy pathExport-AllADUserTransitiveGroupMemberships.ps1
File metadata and controls
189 lines (159 loc) · 8.3 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
[CmdletBinding()]
param (
[Parameter()]
[ValidateNotNullOrEmpty()]
[ValidateScript({ Test-Path $_ -IsValid })]
[string]
$ExportDirectory = $PWD
)
process {
Start-Transcript -Path (Join-Path -Path $PWD -ChildPath "$($MyInvocation.MyCommand).RunHistory.log") -Append -Verbose:$false
Export-AllUserGroupMemberships -ExportDirectory $ExportDirectory
} # end process block
begin {
# Import the functions to be used in this script.
function Export-AllUserGroupMemberships {
<#
.SYNOPSIS
Exports all users' group memberships from the current Active Directory domain.
.DESCRIPTION
The purpose of this script is to get the members of all groups in Active Directory in a format that can be easily
analyzed with tools like Excel or PowerBI. For this purpose, the script exports the data to a JSON file.
.PARAMETER ExportDirectory
The directory to create group exports in. Defaults to the 'GroupExports' folder in the current directory.
.NOTES
Author: Sam Erde
Company: Sentinel Technologies, Inc
Date: 2025-02-24
NOTE: Be sure to account for nested groups and circular groups!
#>
[CmdletBinding()]
param (
# The directory to create the exported file in. Defaults to the current directory.
[Parameter()]
[ValidateNotNullOrEmpty()]
[ValidateScript({ Test-Path $_ -IsValid })]
[string]
$ExportDirectory = $PWD
)
process {
# Get all users in the domain and their group memberships.
Write-Verbose -Message 'Getting all enabled users in the domain.'
Write-Information 'Checking all users'' transitive group memberships in the domain. This will take a while...'
$Users = Get-ADUser -Filter 'Enabled -eq $true' -Properties EmployeeId |
Select-Object Name, DisplayName, samAccountName, userPrincipalName, EmployeeId, @{Name = 'Groups'; Expression = {
Get-ADUserTransitiveGroupMembership -UserDN $_.DistinguishedName
}
}
Write-Verbose -Message " - Found $($Users.Count) users in the domain."
# Export the data to a JSON file.
$JsonData = $Users | ConvertTo-Json
$FilePath = (Join-Path -Path $ExportDirectory -ChildPath 'ADUsersGroupMemberships.json')
Write-Verbose 'Exporting user group memberships to JSON file.'
try {
$JsonData | Out-File -FilePath $FilePath -Force
Write-Verbose ' - Export complete!'
} catch {
throw "Unable to create the file '$FilePath'. $_"
}
} # process
# This begin block gets executed first.
begin {
# Start-Transcript -Path (Join-Path -Path $PWD -ChildPath "$($MyInvocation.MyCommand).RunHistory.log") -Append -Verbose:$false
Import-Module ActiveDirectory -Verbose:$false
# Check if the ExportDirectory exists; if not, create it. Quit if unable to create the directory.
if (-not (Test-Path -Path $ExportDirectory -PathType Container)) {
try {
New-Item -Path (Split-Path -Path $ExportDirectory -Parent) -Name (Split-Path -Path $ExportDirectory -Leaf) -ItemType Directory
} catch {
throw "Failed to create directory '$ExportDirectory'. $_"
} # end try
} # end if
} # begin
# This end block gets executed last.
end {
Remove-Variable ExportDirectory, FilePath, JsonData, Users -Verbose:$false -ErrorAction SilentlyContinue
# Stop-Transcript -Verbose:$false
} # end
} # end function Export-AllUserGroupMemberships
function Get-ADUserTransitiveGroupMembership {
<#
.SYNOPSIS
Get the full transitive group membership of an Active Directory user.
.DESCRIPTION
Get the full transitive group membership of an Active Directory user by searching the global catalog. This performs
a transitive LDAP query which effectively flattens the group membership hierarchy more efficiently than a recursive
memberOf lookup could.
.PARAMETER UserDN
The distinguished name of the user to search for. This is required and it accepts input from the pipeline.
.PARAMETER Server
A global catalog domain controller to connect to. This will get a GC in the current forest if none is specified.
.PARAMETER Port
Port to connect to the global catalog service on. Defaults to 3269 (using TLS).
.EXAMPLE
Get-ADUser -Identity JaneDoe | Get-ADUserTransitiveGroupMembership
Gets the transitive group membership of the user JaneDoe (include all effective nested group memberships).
.EXAMPLE
Get-ADUserTransitiveGroupMembership -UserDN 'CN=Jane Doe,OU=Users,DC=example,DC=com'
Gets the transitive group membership of the user Jane Doe (include all effective nested group memberships).
.NOTES
Author: Sam Erde
Company: Sentinel Technologies, Inc
Version: 1.0.0
Date: 2025-02-27
#>
[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipeline, HelpMessage = 'The distinguished name of the user to search for.')]
[string]$UserDN,
[Parameter(HelpMessage = 'A global catalog domain controller to connect to.')]
[ValidateScript({ (Test-NetConnection -ComputerName $_ -InformationLevel Quiet -ErrorAction SilentlyContinue).PingSucceeded })]
[string]$Server = ([System.DirectoryServices.ActiveDirectory.GlobalCatalog]::FindOne([System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Forest)).Name,
# Port to connect to the global catalog service on.
[Parameter(HelpMessage = 'Port to connect to the global catalog service on. Default is 3268, or 3269 for using TLS.')]
[ValidateSet(3268, 3269)]
[int]$Port = 3269
)
begin {
if ($Port -eq 3269) {
$AltPort = 3268
} else {
$AltPort = 3269
}
$CurrentProgressPreference = Get-Variable -Name ProgressPreference -ValueOnly
try {
Set-Variable -Name ProgressPreference -Value 'SilentlyContinue' -Scope Global -Force -ErrorAction SilentlyContinue
# Check if the global catalog server is available on the specified port.
if (-not (Test-NetConnection -ComputerName $Server -Port $Port -InformationLevel Quiet -ErrorAction SilentlyContinue)) {
if (-not (Test-NetConnection -ComputerName $Server -Port $AltPort -InformationLevel Quiet -ErrorAction SilentlyContinue)) {
throw "Unable to connect to the global catalog server '$Server' on port '$Port' or '$AltPort.'"
}
}
} finally {
Set-Variable -Name ProgressPreference -Value $CurrentProgressPreference -Scope Global -Force -ErrorAction SilentlyContinue
}
}
process {
# Set the searcher parameters
$Filter = "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:=$UserDN))"
$Searcher = New-Object System.DirectoryServices.DirectorySearcher
$Searcher.SearchRoot = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$Server`:$Port")
$Searcher.Filter = $Filter
$Searcher.PageSize = 1000
$Searcher.PropertiesToLoad.Add('DistinguishedName') | Out-Null
$Results = $Searcher.FindAll()
Write-Verbose "Found $($Results.Count) groups for ${UserDN}."
$TransitiveMemberOfGroupDNs = foreach ($result in ($results.properties)) {
$result['distinguishedname']
}
}
end {
$TransitiveMemberOfGroupDNs | Sort-Object -Unique
Remove-Variable Filter, TransitiveMemberOfGroupDNs, Results, Searcher, Server, Port, UserDN -ErrorAction SilentlyContinue
}
} # end function Get-ADUserTransitiveGroupMembership
} # end begin block
end {
Stop-Transcript -Verbose:$false
Remove-Variable ExportDirectory -ErrorAction SilentlyContinue
} # end end block