#Requires -module ActiveDirectory <# Get-ADGroupMemberShips.ps1 Original Author: I.C.A. Strachan https://pshirwin.wordpress.com/2016/01/22/report-a-users-nested-groupmembership/ 6/2/16 Alan Kaplan, Large number of changes Purpose: Get User nested group membership report #> [CmdletBinding()] param( [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=0)] [string] $UserSAM ) Function Get-NTNameFromDN([string]$DNPath){ #Based on #http://www.itadmintools.com/2011/09/translate-active-directory-name-formats.html #Intialize NameTranlate ComObject $oNameTrans=New-Object -ComObject NameTranslate #Initialize with Global Catalog search ADS_NAME_INITTYPE_GC=3 [System.__ComObject].InvokeMember(“init”,”InvokeMethod”,$null,$oNameTrans,(3,$null)) #Take anything as input NameType UNKNOWN = 8 [System.__ComObject].InvokeMember(“Set”,”InvokeMethod”,$null,$oNameTrans,(8,$DNPath)) [System.__ComObject].InvokeMember("ChaseReferral", "SetProperty", $NULL, $oNameTrans,(0x60) ) #Return in format NTDNAME=3 $NTName = [System.__ComObject].InvokeMember(“Get”,”InvokeMethod”,$null,$oNameTrans,3) #Not very pretty, I know... $NTName = $NTName|out-string $NTName = $NTName.Trim() #cleanup com object [System.Runtime.Interopservices.Marshal]::ReleaseComObject($oNameTrans) | Out-Null if (($NTName).EndsWith("\")) {$NTName = $NTName.Substring(0,$NTname.Length-1)} Return [string]$NTName } function Get-ADUserGroups { param ($Group,$indent,$Parent) #rcf added to get the domain of the group you are searching for so you dont get errors searching outside domain... $server = ((($Group.Split(",") | Where {$_ -like "DC=*"}) -join ".") -replace "DC=",'') Get-ADGroup -server $server –Identity $Group –Properties MemberOf | Select-Object -ExpandProperty MemberOf | ForEach-Object { $GroupDN = $_ [string]$groupSAM=Get-NTNameFromDN $GroupDN $GroupSam = $groupSAM.Trim() Write-Progress $groupSAM $GroupName = ($_).Split(',')[0].Split('=')[1] if ($SecurityGroups -contains $GroupSAM){$bSG=$true}Else{$bSG = $false} if(!(($Parent).Contains($GroupName))){ $Newparent = "$Parent>$GroupName" [PSCustomObject]@{ GroupName = $GroupName GroupSAM=$groupSAM NestedLevel = $indent SecurityGroup = $bSG InheritedFrom = $Parent GroupDN= $GroupDN } Get-ADUserGroups -Group $_ -indent ($indent+1) -Parent $Newparent } else{ [PSCustomObject]@{ GroupName = $GroupName GroupSAM=$groupSAM NestedLevel = $indent SecurityGroup = $bSG InheritedFrom = "$Parent>Circular Reference. Please review" GroupDN= $groupDN } } } } Function Get-AllSecurityGroupsForUser{ <# .Synopsis Get all security groups that a user belongs to, directly and indirectly .DESCRIPTION This script uses ADSI to get all of the groups that a user is a member of. The forest is searrched for the user's SamAccountName .PARAMETER Name The SamAccountName for the user. Because the entire forest is searched, the domain should be omitted .EXAMPLE Get-AllGroupsForUser $env:UserName | Out-GridView .Notes The interesting bits are from http://abhishek225.spaces.live.com/, Function to list the Security Groups a User is Member Of Kaplan switched to DN for input, added handling for SIDs in output also added code to search for name in forest from comments below https://blogs.technet.microsoft.com/heyscriptingguy/2010/10/12/use-powershell-to-translate-a-users-sid-to-an-active-directory-account-name/ #> [CmdletBinding()] Param ( [Parameter( Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Position=0, HelpMessage="Enter SamAccount for user")] [string]$Name ) Begin{ $forest=[system.directoryservices.activedirectory.Forest]::GetCurrentForest().Name } Process { $SAMName = $Name If ($name.Contains("@")){ Write-Warning "$Name is not a name in the NT format, quitting" Break } if ($name.Contains("\")){ $a = ($name).Split("\") $SAMName = $a[1] } $searcher=[adsisearcher]"samaccountname=$SAMName" $searcher.SearchRoot="GC://$Forest" $searcher.PropertiesToLoad.Add('distinguishedname') | Out-Null $results=$searcher.FindOne() $DistinguishedName = $($results.Properties.distinguishedname).ToString() # assumes you have the right permissions, otherwise use new-object and pass creds $user = [adsi]"LDAP://$DistinguishedName" # Load the TokenGroups attribute in the Property Cache $user.psbase.refreshCache(@("TokenGroups")) # Convert the SID to to NT Account $irc = new-object System.Security.Principal.IdentityReferenceCollection foreach($sidByte in $user.TokenGroups) { $irc.Add((new-object System.Security.Principal.SecurityIdentifier $sidByte,0)) } ($irc.Translate([System.Security.Principal.NTAccount]) | foreach { #Kaplan - takes care of some built-in groups that don't work with name-translate if ($_.isAccountSid){ $bind = "LDAP://" ([adsi]$Bind).name }ELSE{$_} }).GetEnumerator().value | sort } } ### Script Begins ### if ($UserSAM.Contains("\")){ $a = ($UserSAM).Split("\") $SAMName = $a[1] $Server = $a[0] }ELSE{ $SamName = $UserSAM $Server = ($env:USERDNSDOMAIN).ToLower() } $SecurityGroups = Get-AllSecurityGroupsForUser $SAMName $logFile= "$env:USERPROFILE\Desktop\$($user.SamAccountName) All Groups.csv" $user = Get-ADUser -Server $Server -identity $SamName -Properties MemberOf, CanonicalName $user | Select-Object -ExpandProperty MemberOf | ForEach-Object { #Set is From Type Get is to Type $GroupDN = $_ [string]$groupSAM=Get-NTNameFromDN $GroupDN $GroupSam = $groupSAM.Trim() Write-Progress $groupSAM $GroupName = ($_).Split(',')[0].Split('=')[1] if ($SecurityGroups -contains $GroupSAM){$bSG=$true}Else{$bSG = $false} [PSCustomObject]@{ GroupName = $GroupName GroupSAM = $GroupSAM NestedLevel = 0 SecurityGroup = $bSG InheritedFrom = $null GroupDN= $GroupDN } Get-ADUserGroups -Group $_ -indent 1 -Parent $GroupName } | #Out-GridView -Title "AD Groups for $($user.CanonicalName), $($user.SamAccountName)" Export-Csv -NoTypeInformation $logFile Write-Progress "Script Complete" -Completed ii $logFile