Public/activedirectory/Get-ADUserGroupInventory.ps1
|
#Requires -Version 5.1 function Get-ADUserGroupInventory { <# .SYNOPSIS Retrieves all group memberships including nested groups for AD users .DESCRIPTION For one or more Active Directory users, retrieves all group memberships including nested (recursive) groups using LDAP recursive member resolution. Returns one object per user-group combination for audit and review purposes. Users can be specified by identity or discovered from an organizational unit. .PARAMETER Identity One or more user identifiers (SamAccountName, DN, SID, or GUID). Accepts pipeline input. When omitted, queries all users in the domain or within the scope defined by SearchBase. .PARAMETER SearchBase The distinguished name of an OU to scope user discovery. Only applies when Identity is not provided. .PARAMETER Server The Active Directory domain controller to target for all queries. .PARAMETER Credential The PSCredential object used to authenticate against Active Directory. .EXAMPLE Get-ADUserGroupInventory -Identity 'jdoe' Retrieves all direct and nested group memberships for user jdoe. .EXAMPLE Get-ADUserGroupInventory -Identity 'jdoe', 'asmith' -Server 'DC01.contoso.com' Retrieves group memberships for two users targeting a specific domain controller. .EXAMPLE 'jdoe', 'asmith' | Get-ADUserGroupInventory -Credential (Get-Credential) Retrieves group memberships via pipeline input with alternate credentials. .OUTPUTS PSWinOps.ADUserGroupInventory Returns objects with UserName, DisplayName, GroupName, GroupDN, GroupScope, GroupCategory, and Timestamp properties. .NOTES Author: Franck SALLET Version: 1.0.0 Last Modified: 2026-04-04 Requires: PowerShell 5.1+ / Windows only Requires: ActiveDirectory module (RSAT) .LINK https://github.com/k9fr4n/PSWinOps .LINK https://learn.microsoft.com/en-us/powershell/module/activedirectory/get-aduser #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias('SamAccountName')] [ValidateNotNullOrEmpty()] [string[]]$Identity, [Parameter()] [ValidateNotNullOrEmpty()] [string]$SearchBase, [Parameter()] [ValidateNotNullOrEmpty()] [string]$Server, [Parameter()] [ValidateNotNull()] [System.Management.Automation.PSCredential]$Credential ) begin { Write-Verbose -Message "[$($MyInvocation.MyCommand)] Starting" $moduleAvailable = $false try { Import-Module -Name 'ActiveDirectory' -ErrorAction Stop -Verbose:$false $moduleAvailable = $true } catch { Write-Error -Message "[$($MyInvocation.MyCommand)] ActiveDirectory module not available: $_" } $adSplat = @{} if ($PSBoundParameters.ContainsKey('Server')) { $adSplat['Server'] = $Server } if ($PSBoundParameters.ContainsKey('Credential')) { $adSplat['Credential'] = $Credential } $userList = [System.Collections.Generic.List[object]]::new() $identityProvided = $false $timestamp = Get-Date -Format 'o' } process { if (-not $moduleAvailable) { return } if ($PSBoundParameters.ContainsKey('Identity')) { $identityProvided = $true foreach ($id in $Identity) { $getUserSplat = @{ Identity = $id Properties = @('DisplayName') ErrorAction = 'Stop' } try { Write-Verbose -Message "[$($MyInvocation.MyCommand)] Querying user: $id" $adUser = Get-ADUser @getUserSplat @adSplat $userList.Add($adUser) } catch { Write-Warning -Message "[$($MyInvocation.MyCommand)] Failed to retrieve user ${id}: $_" } } } } end { if (-not $moduleAvailable) { return } if (-not $identityProvided) { Write-Verbose -Message "[$($MyInvocation.MyCommand)] No Identity specified, querying all users" $getAllUserSplat = @{ Filter = '*' Properties = @('DisplayName') ErrorAction = 'Stop' } if ($PSBoundParameters.ContainsKey('SearchBase')) { $getAllUserSplat['SearchBase'] = $SearchBase } try { $discoveredUsers = Get-ADUser @getAllUserSplat @adSplat foreach ($discoveredUser in $discoveredUsers) { $userList.Add($discoveredUser) } } catch { Write-Error -Message "[$($MyInvocation.MyCommand)] Failed to query users: $_" return } } Write-Verbose -Message "[$($MyInvocation.MyCommand)] Processing $($userList.Count) user(s)" $resultList = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($currentUser in $userList) { $ldapFilter = "(member:1.2.840.113556.1.4.1941:=$($currentUser.DistinguishedName))" $getGroupSplat = @{ LDAPFilter = $ldapFilter Properties = @('GroupScope', 'GroupCategory') ErrorAction = 'Stop' } try { $groupList = Get-ADGroup @getGroupSplat @adSplat if (-not $groupList) { Write-Verbose -Message "[$($MyInvocation.MyCommand)] No groups found for user: $($currentUser.SamAccountName)" continue } foreach ($grp in $groupList) { $inventoryEntry = [PSCustomObject]@{ PSTypeName = 'PSWinOps.ADUserGroupInventory' UserName = $currentUser.SamAccountName DisplayName = $currentUser.DisplayName GroupName = $grp.Name GroupDN = $grp.DistinguishedName GroupScope = $grp.GroupScope GroupCategory = $grp.GroupCategory Timestamp = $timestamp } $resultList.Add($inventoryEntry) } } catch { Write-Warning -Message "[$($MyInvocation.MyCommand)] Failed to resolve groups for $($currentUser.SamAccountName): $_" } } $resultList | Sort-Object -Property 'UserName', 'GroupName' Write-Verbose -Message "[$($MyInvocation.MyCommand)] Completed - $($resultList.Count) record(s) returned" } } |