Get AD group members containing large membership; two different AD forests

To start I have been successfully using the following script to get all the members of the AD groups in a specific OU including those from other forests:

$Groups = Get-ADGroup -Filter * -SearchBase "OU=User Groups,OU=Admin-Groups,DC=Site,DC=Local,DC=forest1,DC=blah,DC=com" -server DC1.forest1.blah.com

$Results = foreach( $Group in $Groups ){

Get-ADGroupMember -Identity $Group | foreach {

    [pscustomobject]@{

        GroupName = $Group.Name

        Name = $_.Name

        }

    }

}

$Results | Export-Csv -Path “\fs1.forest1.blah.com\Server Reports\user-tmp.csv”
pause

This works great until you get AD groups with hundreds of members which results in a timeout. So what I am trying to do is use "Get-ADGroup $Group - properties Member | Select -expandproperty Member" to get each group same as the script above which I don't know how to do that. I need to do this because the users are in a different AD forest.

The output that I get when I enter one group is this:

CN=S-1-5-21-1234567890-987654321-912837465-123456,CN=ForeignSecurityPrincipals,DC=Site,DC=Local,DC=forest1,DC=blah,DC=com
CN=S-1-5-21-1234567890-987654321-912837465-123475,CN=ForeignSecurityPrincipals,DC=Site,DC=Local,DC=forest1,DC=blah,DC=com
CN=S-1-5-21-1234567890-987654321-912837465-123354,CN=ForeignSecurityPrincipals,DC=Site,DC=Local,DC=forest1,DC=blah,DC=com
I need to extract the SID in order to do the lookup but I need to have the ability to do this for each line:
$user = CN=S-1-5-21-1234567890-987654321-912837465-123456,CN=ForeignSecurityPrincipals,DC=Site,DC=Local,DC=forest1,DC=blah,DC=com
$user = -split ',' | Select-Object -First 1
$user = ($user).Trim('CN=')
Get-ADUser -Identity $user -Server dc1.forest2.blah.com
So at the end I can now support doing large lookups for members of our AD groups and still have it output to the CSV as originally defined in my beginning script.

To summarize. I need to extract the membership from all the AD groups to a CSV when dealing with two different AD forests while supporting large AD groups because of the timeouts that I am receiving and other solutions online would work only if they are in the same AD forest. I am thinking that there might need to be a loop inside of a loop because each group and its members needs to be processed/identified before moving on to the next group. This might be able to be done in a variable I suppose I just don’t know how to connect all the dots.

So, you have two AD forest, with the AD trusts in place. Meaning as discussed here:

Active Directory Trusts https://blogs.msmvps.com/acefekay/2016/11/02/active-directory-trusts

Any time you have this use case, the real deal is that it’s not about just the number of users in a given group. It’s also about adjacent groups and nested groups …
(those users may be in - thus the token bloat issues cause by it, historically in challenged AD environments)

https://blogs.technet.microsoft.com/shanecothran/2010/07/16/maxtokensize-and-kerberos-token-bloat http://www.itadmintools.com/2011/09/avoiding-token-bloat-in-your-active.html

In AD, there is a filter: 1.2.840.113556.1.4.1941. It is called “matching rule in chain” and can be used to quickly find nested memberships.

# Example use 
$DN = 'place DN here!'
Get-ADGroup -LDAPFilter "(member:1.2.840.113556.1.4.1941:=$($DN))"

… as well as if these forest are geo-graphically dispersed, then you have potential bandwidth concerns.

Any time you are bringing back large datasets, you need to look to paging / chunking tat data to get it all. AD will not allow you to bring back unlimited datasets by default. You have to explicitly tell it to.

Yet, for a first step, why not just do this… (of course, this is just dumping to the screen, but easy to alter to send to a file)

$Banner = '#'*40
'DC1.forest1.blah.com','DC1.forest2.blah.com' | 
ForEach{
    $Banner 
    "`n* Processing forest root domain controller $($PSItem) *`n"
    $Banner 
    Get-ADGroup -Filter '*' -Server $PSItem | 
    ForEach{ 
        "`n*** Processing $($PSItem.Name) ***`n"
        (Get-ADGroupMember -Identity $PSItem.Name | 
        Where { $PSItem.objectClass -eq 'user' }).SamAccountName
    }
} 

@postanote

I am a fairly basic powershell user so I might need some help understanding what you are presenting to me. The script that I provided in my post was from the interwebs and a suggested script to improve from what I was doing before. Now that script does not work due to very large membership of groups. There are hardly any groups within a group that I am concerned with.

Looking through your post I am not quite understanding where the $PSItem is being set that is referenced in your last script and how the $DN value is referenced.

$PSItem is a default variable in PowerShell.
You will also see it written this way… $_
All it means is use whatever is being piped in. In this case, it’s these…

'DC1.forest1.blah.com','DC1.forest2.blah.com'

If you loop them by themselves, you’ll see what that means,

'DC1.forest1.blah.com','DC1.forest2.blah.com' | 
ForEach-Object { $PSItem }

# Results
DC1.forest1.blah.com
DC1.forest2.blah.com

'DC1.forest1.blah.com','DC1.forest2.blah.com' | 
ForEach-Object { $_ }

# Results
DC1.forest1.blah.com
DC1.forest2.blah.com

or see more details here:

https://blogs.msdn.microsoft.com/mvpawardprogram/2013/04/15/working-with-the-new-psitem-automatic-variable-in-windows-powershell-3-0

Difference Between $_ and $PSItem in Windows PowerShell
https://www.interfacett.com/videos/difference-_-psitem-windows-powershell

Since you say you are new, see also the below to assist you in you ramp up and use.

Resource list

Microsoft Virtual Academy
'mva.microsoft.com/liveevents/powershell-jumpstart
mva.microsoft.com/en-us/training-courses/getting-started-with-microsoft-powershell-8276
mva.microsoft.com/search/SearchResults.aspx#!q=PowerShell&lang=1033

Microsoft Channe9
channel9.msdn.com/Series/GetStartedPowerShell3
channel9.msdn.com/Search?term=powershell#ch9Search&lang-en=en&pubDate=year

Youtube
'youtube.com/watch?v=wrSlfAfZ49E 'youtube.com/results?search_query=beginning+powershell
youtube.com/results?search_query=powershell+ise+scripting+for+beginners

Windows PowerShell Survival Guide
social.technet.microsoft.com/wiki/contents/articles/183.windows-powershell-survival-guide.aspxStart-Process

eBooks…
blogs.technet.microsoft.com/pstips/2014/05/26/free-powershell-ebooks
'idera.com/resourcecentral/whitepapers/powershell-ebookpowershell.org/ebooks

Books Learn Windows PowerShell in a Month of Lunches
manning.com/jones

Windows PowerShell in Action, Third Edition
amazon.com/s/ref=nb_sb_ss_c_1_28/133-0145057-4953560?url=search-alias%3Daps&field-keywords=windows+powershell+in+action%2C+third+edition&sprefix=Windows+PowerShell+in+Action%2Caps%2C220&crid=1IS8LZE5E6EIE

Windows PowerShell Cookbook, 2nd Edition
shop.oreilly.com/product/9780596801519.do

And start with lots of examples.
'powershellgallery.com 'gallery.technet.microsoft.com/scriptcenter/site/requeststechnet.microsoft.com/en-us/scriptcenter/bb410849.aspx

Always use the help system directly as your primary starting point.

# All Help topics and locations
Get-Help about_*
Get-Help about_Functions

Get-Help about* | Select Name, Synopsis

Get-Help about* | 
Select-Object -Property Name, Synopsis |
Out-GridView -Title 'Select Topic' -OutputMode Multiple |
ForEach-Object { Get-Help -Name $_.Name -ShowWindow }

explorer "$pshome\$($Host.CurrentCulture.Name)"


# Get parameters, examples, full and Online help for a cmdlet or function

# Get a list of all functions
Get-Command -CommandType Function | 
Out-GridView -PassThru -Title 'Available functions'

# Get a list of all commandlets
Get-Command -CommandType Cmdlet | 
Out-GridView -PassThru -Title 'Available cmdlets'

# Get a list of all functions for the specified name
Get-Command -Name '*ADGroup*' -CommandType Function | 
Out-GridView -PassThru -Title 'Available named functions'

# Get a list of all commandlets for the specified name
Get-Command -Name '*ADGroup**'  -CommandType Cmdlet | 
Out-GridView -PassThru -Title 'Available named cmdlet'

# get function / cmdlet details
(Get-Command -Name Get-ADUser).Parameters
Get-help -Name Get-ADUser -Full
Get-help -Name Get-ADUser -Online
Get-help -Name Get-ADUser -Examples

Function Get-HelpExamples
{
    [CmdletBinding()]
    [Alias('ghe')]

    Param
    (
        [string]$CmdletName = (
            Get-Command -Name '*' | 
            Out-GridView -PassThru -Title 'Select a cmdlet to see examples'
        )
    )

    If ((Get-Help -Name $CmdletName).Examples)
    {
        (((Get-Help -Name $CmdletName).Examples | 
        Out-String -Stream) -match '.*\\>|C:\\PS>') -replace '.*\\>|C:\\PS>' | 
        Out-GridView -Title 'Select a sample to use' -PassThru
    }
    Else {Write-Warning -Message "The were no help examples discovered"}
}