evetsleep

Get group memberships (paging)

Jul 6th, 2015
624
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. [CmdletBinding()]Param(
  2.     [Parameter( Position                        = 0,
  3.                 Mandatory                       = $true
  4.                 )]
  5.         [String]$Identity
  6. )
  7.  
  8. function QueryAD {
  9.     [CmdletBinding()]Param (
  10.       [Parameter(Mandatory = $false, Position = 0)]
  11.         [ValidateSet('GC','LDAP')]
  12.       [string]$queryType = 'GC',
  13.    
  14.       [Parameter(Mandatory = $true, Position = 1)]
  15.       [string]$ComputerName,
  16.  
  17.       [Parameter(Mandatory = $true, Position = 2)]
  18.       [string]$Filter,
  19.    
  20.       [Parameter(Mandatory = $false, Position = 3)]
  21.       [string]$SearchBase,
  22.  
  23.       [Parameter(Mandatory = $false, Position = 4)]
  24.       [string[]]$Properties
  25.     )
  26.  
  27.     $queryString = '{0}://{1}' -f $queryType,$ComputerName
  28.     if($SearchBase){
  29.         $queryString = $queryString + '/' + $SearchBase
  30.     }
  31.  
  32.     $queryRoot  = [ADSI]"$queryString"
  33.     $searcher   = new-object System.DirectoryServices.DirectorySearcher($queryRoot)
  34.     $searcher.Filter = $Filter
  35.     if($Properties){
  36.         $searcher.PropertiesToLoad.AddRange($Properties)
  37.     }
  38.  
  39.     $searchArrayList = New-Object System.Collections.ArrayList
  40.     $searcher.FindAll() | foreach { [void]$searchArrayList.Add($_) }
  41.     Write-Output $searchArrayList
  42. }
  43.  
  44. Function Get-GCList {
  45.     [CmdletBinding()]Param(
  46.         [Parameter( Position                        = 0,
  47.                     Mandatory                       = $true,
  48.                     ValueFromPipeline               = $false,
  49.                     ValueFromPipelineByPropertyName = $false
  50.                     )]
  51.             [String]$Forest,
  52.            
  53.         [Parameter( Position                        = 1,
  54.                     Mandatory                       = $false,
  55.                     ValueFromPipeline               = $false,
  56.                     ValueFromPipelineByPropertyName = $false
  57.                     )]
  58.             [String]$Site
  59.     )  
  60.  
  61.     # If Site is specified then we look for GC's in a specific site, otherwise we look forest wide
  62.     if ($Site) {
  63.         $lookup = nslookup -querytype=srv ('_ldap._tcp.{1}._sites.gc._msdcs.{0}.' -f $Forest, $Site)
  64.     }
  65.     else {
  66.         $lookup = nslookup -querytype=srv ('_gc._tcp.{0}.' -f $Forest)
  67.     }
  68.  
  69.     $gcList = New-Object System.Collections.ArrayList
  70.    
  71.     # Go through each result and pull out the host name and add to the array list.
  72.     $lookup | where { $_ -match 'svr hostname' } | foreach { $_ -match '\w+\.\w+\.\w+\.\w+' | out-null; [void]$gcList.Add($matches[0].ToLower()) }
  73.     Write-Verbose -Message ('Found {0} glocal catalogs.' -f $gcList.count)
  74.  
  75.     # Return the array list.
  76.     Write-Output $gcList   
  77. }
  78.  
  79. Function GetMembers {
  80.     [CmdletBinding()]Param(
  81.         [Parameter( Position                        = 0,
  82.                     Mandatory                       = $true,
  83.                     ValueFromPipeline               = $false,
  84.                     ValueFromPipelineByPropertyName = $false
  85.                     )]
  86.             [String]$distinguishedName,
  87.            
  88.         [Parameter( Position                        = 1,
  89.                     Mandatory                       = $true,
  90.                     ValueFromPipeline               = $false,
  91.                     ValueFromPipelineByPropertyName = $false
  92.                     )]
  93.             [String]$GlobalCatalog
  94.     )
  95.    
  96.     # Create an array to store members
  97.     $memberList = New-Object System.Collections.ArrayList
  98.    
  99.     # Bind to the group and see if we need to page out the membership
  100.     $listEntry = New-Object DirectoryServices.DirectoryEntry("GC://$GlobalCatalog/$distinguishedName")
  101.     $listSearcher = New-Object DirectoryServices.DirectorySearcher($listEntry,'(objectClass=*)','member','Base')
  102.     $listObject = $listSearcher.FindOne()
  103.    
  104.     # Check to see if we get a special return for the member property which tells us that we need
  105.     # to page out the membership.  If all we get back is just member than no paging is required, but
  106.     # if we get back something like member;0-1499 in attition to the member property then we need
  107.     # to page the membership.
  108.     if ($listObject.Properties.PropertyNames -match '^member;'){
  109.        
  110.         # Define a starting point of 0 and init $done as $false so that we can set to $true
  111.         # when we're done.
  112.         $start = 0
  113.         $done = $false
  114.  
  115.         while ($done -eq $false){
  116.             # Set our page range for this query (starting at 0-999, the next time it would be 1000-1999, and so on).
  117.             $end = $start + 999
  118.            
  119.             # Create a searcher object and then pull down the member;range property
  120.             $directoryObject = New-Object DirectoryServices.DirectorySearcher($listEntry,'(objectClass=*)',"member;range=$start-$end",'Base')
  121.             $getMembers = $directoryObject.FindOne()
  122.  
  123.             # Store the member property (for logging really..which is not used right now).
  124.             # Then we pull out the members that were returned for that range and add them
  125.             # to the array list.
  126.             [string]$memberProperty = $getMembers.Properties.PropertyNames -match '^member.*/*$'
  127.             $getMembers.Properties.$memberProperty | foreach { [void]$memberList.Add($_) }
  128.            
  129.             # Increment our starting point for the next batch.
  130.             $start += 1000
  131.  
  132.             # When we get to the last batch Active Directory will return a special member property that looks like
  133.             # this:
  134.             #
  135.             # member;range=6000-*
  136.             #
  137.             # The asterisk at the end signifies that there are no more results to page so we need
  138.             # to stop.  We look for that and when we hit that we set $done to $true
  139.             if ($getMembers.Properties.PropertyNames -match '^member.*\*$'){
  140.                 $done = $true
  141.             }
  142.         }
  143.     }
  144.     else {
  145.         # If we get back just member then we can drop the membership as it is.
  146.         $listObject.Properties.member | foreach { [void]$memberList.Add($_) }
  147.     }
  148.  
  149.     # Return the contents of memberList.
  150.     Write-Output $memberList
  151. }
  152.  
  153. #---------------------------------------
  154. #region Get a working global catalogs
  155.  
  156. # Parse out the forest from where the script is being executed.
  157. $Forest = (([ADSI]$('LDAP://{0}/RootDSE' -f ($env:logonserver -replace '\\'))).ldapservicename -split ':')[0]
  158.  
  159. # Fill in if you want GC's from a certain site.
  160. $dnsQuery = @{Forest=$Forest;Site=$Site}
  161. [array]$globalCatalogs = Get-GCList @dnsQuery
  162.  
  163. if ($globalCatalogs.count -eq 0){
  164.     Write-Warning -Message ('No global catalogs found!')
  165.     return
  166. }
  167.  
  168. # Randomize the array order
  169. $globalCatalogs = $globalCatalogs | Get-Random -Count $globalCatalogs.count
  170.  
  171. # Find a working one.
  172. for($i=0; $i -lt $globalCatalogs.count;$i++){
  173.     $rootDSEPath = 'GC://{0}/RootDSE' -f $globalCatalogs[$i]
  174.     Write-Verbose -Message ('Trying {0}' -f $rootDSEPath)
  175.     try {
  176.         [ADSI]$rootDSEPath | Out-Null
  177.     }
  178.     catch {
  179.         Write-Verbose -Message ('Unable to connect to {0}' -f $globalCatalogs[$i])
  180.         continue
  181.     }
  182.    
  183.     # If we got here then we found a working GC and we can stop.
  184.     $gc = $globalCatalogs[$i]
  185.    
  186.     # Stop the loop since we have a working GC.
  187.     break
  188. }
  189.  
  190. #endregion Get a working global catalog
  191. #---------------------------------------
  192.  
  193. #---------------------------------------
  194. #region Get the distinguished name of the group and write out the membership
  195.  
  196. # Check to see if a distinguished name was passed.  If so we use that, otherwise
  197. # we need to lookup the group name in the global catalog.
  198. if ($Identity -match '^CN='){
  199.     $groupDN = $Identity
  200. }
  201. else {
  202.     try {
  203.         $filterString = '(&(objectClass=Group)(sAMAccountName={0}))' -f $Identity
  204.         $groupDN = QueryAD -ComputerName $gc -Filter $filterString -Properties distinguishedName | foreach { [string]$_.Properties.distinguishedname }
  205.     }
  206.     catch {
  207.         Write-Warning -Message ('Unable to get distinguishedName of {0}: {1}' -f $Identity,$_.exception.message)
  208.         return
  209.     }
  210.  
  211.     if ($groupDN.count -gt 1){
  212.         Write-Warning -Message ('Too many matches for {0}' -f $Identity)
  213.         $groupDN | foreach { Write-Warning -Message ('->{0}' -f $_.distinguishedname) }
  214.         return
  215.     }
  216. }
  217.  
  218. GetMembers -GlobalCatalog $gc -distinguishedName $groupDN
  219.  
  220. #endregion Get the distinguished name of the group and write out the membership
  221. #---------------------------------------
  222.  
  223. <#
  224. .SYNOPSIS
  225. Return the membership of any list (small or large) from a global catalog.
  226.  
  227. .DESCRIPTION
  228. Find a working global catalog and then search for the list provided.  After it is found
  229. we return the membership.  If the membership needs to be paged out we automatically determine
  230. that and then page that out and return it as an array list.
  231.  
  232. .PARAMETER Identity
  233. The name of the list\group. This can be a friendly name (CN or sAMAccountName) or it can be a distinguishedName
  234.  
  235. .EXAMPLE
  236. $listMembers = .\Get-ListMembers.ps1 -Identity companyWideList
  237. C:\PS>$listMembers.count
  238. 38897
  239.  
  240. .NOTES
  241. This is free to use and expand on.  Hope it was helpful to someone.
  242. #>
Advertisement
Add Comment
Please, Sign In to add comment