1 Einleitung

Beim Arbeiten mit DirectoryGruppen sind die Unterschiede zwischen .Net-Skripten und Skripten basierend auf  cmdlets des DirectoryModule besonders deutlich. .Net-Skripte benötigen mehr und komplexeren Code, dafür hat man die volle Kontrolle über sein Skript.
Bei den cmdlets muss man hoffen, dass diese so arbeiten, wie man sich das wünscht. Ab Kapitel 3 werdet ihr sehen, dass dies bei verschachtelten Gruppen mit forestweiten Mitgliedern nicht selbstverständlich ist.

Auf dieser Seite geht es hauptsächlich um das Skripten von Gruppen mit .Net - Methoden
 

1.1 Gruppeneingenschaften in und not-in Globalcatalog

Wie bei allen LDAP-Queries ist immer ein Suchparameter eine LDAPQuery "[ADSISearcher]://LDAP" oder eine GlobalcatalogQuery "[ADSISearcher]://GC:" einsetzen soll. Um das zu bestimmen, ist ein einfaches Skript hilfreich um festzustellen, ob man die gewünschte Eigenschaft wie beispielsweise"Member" überhaupt im GlobalCatalog veröffentlicht ist.
GlobalCataloge können natürlich auch verändert werden oder worden sein, so  dass GC-Queries nicht zwingend in jedem Forest dieselben Ergebnisse zurückbringen.

 

Beispiel 1: Check, if "Member" is published in GC

$CurrentSchema = [System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema]::GetCurrentSchema()
$CurrentSchema.FindProperty("Member").IsInGlobalCatalog
#Ausgabe

$true

siehe auch: 3.2 Mit Powershell ins AD-Schema blicken

 

2 Anlage einer Gruppe

Dieses und alle folgenden Beispiele habe ich in einer Testumgebung erstellt und getestet, wie sie unter "AD Themen -> Übungsstruktur erstellen" gezeigt ist. d.h. eine RootDomain "company.intern" mit zwei Childdomains "asia.company.intern" und europe.company.intern"

Beispiel 1: Anlage einer Gruppe in der eigenen Domäne
In diesem Beispiel seht ihr, wie verschiedene Gruppentypen (global, local, universal und security, distributed) erstellt werden. Die entscheidende Eigenschaft hierfür

lautet "grouptype", hinter der sich eine Enumeration verbirgt. Mit dem binären or (-bor) Operator werden die Eigenschaften kombiniert.

Clear-Host
Set-StrictMode -Version "2.0"

Function Main{
 
  #parameter
  $TargetOU = "OU=Global,OU=Groups,OU=Bangkok,OU=Scripting,DC=asia,DC=company,DC=intern"
  $Description = "Group Created on $(Get-Date)"
  $GroupName = $(Get-Date).ToString("yyyy-MM-dd_") + $(Get-RandomIndentifier 4)
  $Mail = "kai.yorck@powershellpraxis.de"


  #defining grouptype
  #from: http://msdn.microsoft.com/en-us/library/aa772263%28v=vs.85%29.aspx
  $ADS_GROUP_TYPE_GLOBAL_GROUP        = 0x2
  $ADS_GROUP_TYPE_DOMAIN_LOCAL_GROUP  = 0x4
  $ADS_GROUP_TYPE_LOCAL_GROUP         = 0x4
  $ADS_GROUP_TYPE_UNIVERSAL_GROUP     = 0x8
  $ADS_GROUP_TYPE_SECURITY_ENABLED    = 0x80000000

    #examples for grouptype
    #$GroupType = $ADS_GROUP_TYPE_GLOBAL_GROUP -bor $ADS_GROUP_TYPE_SECURITY_ENABLED #Global Security Group
    $GroupType = $ADS_GROUP_TYPE_LOCAL_GROUP -bor $ADS_GROUP_TYPE_SECURITY_ENABLED #Local Security Group
    #$GroupType = $ADS_GROUP_TYPE_GLOBAL_GROUP #Global Distributiongroup

 
  #creating group
  $Class = "Group"
  Try{
    $AdsiOU=[ADSI]"LDAP://$TargetOU"
    $AdsiGroup = $AdsiOU.Children.Add("CN=$GroupName",$Class)
    $AdsiGroup.InvokeSet("Description", $Description)
    $AdsiGroup.InvokeSet("SamAccountName", $GroupName)
    $AdsiGroup.InvokeSet("Mail", $Mail )
    $AdsiGroup.InvokeSet("displayName" , "GroupName")
    $AdsiGroup.InvokeSet("GroupType" , $GroupType )
    $AdsiGroup.CommitChanges()

    #write result
    Write-Host "Group $GroupName has been created"
    Write-Host "ParentOU: $($TargetOU)"
    Write-Host "Grouptype: 0x$([Convert]::ToString($GroupType,16)) ($GroupType)"
  }Catch{
    Write-Host "Creation of Group $GroupName in $TargetOU has been failed" -backgroundcolor yellow
  } # Try/ Catch  

} #End MainFunction

Function Get-RandomIndentifier{
    #creating random identifier for avoiding doubled samaccountnames
    Param ($Count)  
    $ofs = ''
    $Identifier = [string](Get-Random -input $([char[]](65..90)) -count $Count)
    $ofs = ' '
    Return $Identifier
} #Get-RandomIndentifier   
Main
#Ausgabe

Group 2014-05-04_ZNJCPT has been created
ParentOU: OU=Global,OU=Groups,OU=Bangkok,OU=Scripting,DC=asia,DC=company,DC=intern
Grouptype: 0x80000004 (-2147483644)

MSDN:  ADS_GROUP_TYPE_ENUM enumeration


Beispiel 2: Anlegen einer Gruppe in einer fremden Domäne unter anderen Credentials
Im Vergleich zum letzten Beispiel übergebe ich der DirectoryEntry-Klasse jetzt auch Credentials.(Username, Password sowie den Authenticationtype

Clear-Host
Set-StrictMode -Version "2.0"

Function Main{
  $UserName = "kaiadm@europe.company.intern"
  $Password = "Hurra123"
  $AuthenticationType = [System.DirectoryServices.AuthenticationTypes]::"Secure"
 
  $TargetOU = "OU=Global,OU=Groups,OU=Munich,OU=Scripting,DC=europe,DC=company,DC=intern"
  $Description = "Group Created on $(Get-Date)"
  $GroupName = $(Get-Date).ToString("yyyy-MM-dd_") + $(Get-RandomIndentifier 6)

  #from: http://msdn.microsoft.com/en-us/library/aa772263%28v=vs.85%29.aspx
  $ADS_GROUP_TYPE_GLOBAL_GROUP        = 0x2
  $ADS_GROUP_TYPE_DOMAIN_LOCAL_GROUP  = 0x4
  $ADS_GROUP_TYPE_LOCAL_GROUP         = 0x4
  $ADS_GROUP_TYPE_UNIVERSAL_GROUP     = 0x8
  $ADS_GROUP_TYPE_SECURITY_ENABLED    = 0x80000000
 
  #$GroupType = $ADS_GROUP_TYPE_GLOBAL_GROUP -bor $ADS_GROUP_TYPE_SECURITY_ENABLED #Global Security Group
  $GroupType = $ADS_GROUP_TYPE_LOCAL_GROUP -bor $ADS_GROUP_TYPE_SECURITY_ENABLED #Local Security Group
  #$GroupType = $ADS_GROUP_TYPE_GLOBAL_GROUP #Global Distributiongroup

   
  $Class = "Group"
  #$AdsiOU=[ADSI]"LDAP://$TargetOU"
   $AdsiOU = New-Object System.DirectoryServices.DirectoryEntry `

     ("LDAP://$TargetOU",$Username,$Password,$AuthenticationType)
    
  Try{
    $AdsiGroup = $AdsiOU.Children.Add("CN=$GroupName",$Class)
    $AdsiGroup.InvokeSet("Description", $Description)
    $AdsiGroup.InvokeSet("SamAccountName", $GroupName)
    $AdsiGroup.InvokeSet("Mail", "admin@powershellpraxis.de" )
    $AdsiGroup.InvokeSet("displayName" , "GroupName")
    $AdsiGroup.InvokeSet("GroupType" , $GroupType )
    $AdsiGroup.CommitChanges()
    
    Write-Host "Group $GroupName has been created"
    Write-Host "ParentOU: $($TargetOU)"
    Write-Host "Grouptype: 0x$([Convert]::ToString($GroupType,16)) ($GroupType)"
  }Catch{
    Write-Host "Creation of Group $GroupName in $TargetOU has been failed" -backgroundcolor yellow
  } # Try/ Catch  
} #End Function

Function Get-RandomIndentifier{
     Param ($Count)  
     #creating random identifier for avoiding doubled samaccountnames
     $ofs = ''
     $Identifier = [string](Get-Random -input $([char[]](65..90)) -count $Count)
     $ofs = ' '
    Return $Identifier
}     
Main

mögliche Ausgabe

Creation of Group 2014-05-04_YMCTOU in OU=Global,OU=Groups,OU=Munich,OU=Scripting,DC=europe,DC=company,DC=intern has been failed

MSDN: DirectoryEntry-Konstruktor (String, String, String, AuthenticationTypes)

MSDN: DirectoryEntry.AuthenticationType-Eigenschaft

 

3 Gruppenmitglieder

Wir sind hier wieder bei einem Thema bei dem man sein Skripting-Knowhow richtig effizient einsetzen kann. Aufgabenstellungen wie

- Liste alle verschachtelten Gruppenmitglieder mit ihren Eigenschaften "samaccountname, isdisabled, pwdlastset..." auf
- in welchen Gruppen ist ein Account Mitglied
- vergleiche die Mitglieder der GruppeA mit denen der GruppeB
- suche alle leeren Gruppen

ließen sich ohne Programmierung oder Scripting meist gar nicht erfüllen

Auf der anderen Seite .... es ist immer wieder erschreckend, wie einfach jeder einfache authenticated User ohne jedes Privilige Informationen über einen ganzen Forest abgreifen kann.
- Welche Mitglieder sind in den Gruppen "Administratoren"  und " DomainAdmins"
-  welche Admins sind enabled, haben ihr PW auf neverexpires, haben sich seit längerem nicht mehr angemleldet,...

Microsoft-Domänen und Forests  (incl. Windows 2012R2) liefern meiner Meinung  Konfigurationsdaten ganzer Forests (natürlich nicht nur über Gruppen) der Category "Confidential"  nahezu ungeschützt am Silbertablet einem Angreifer aus.
Da es aber nun mal so ist, wie es ist  -mir ist zumindest kein Gegenmittel bekannt-, müsst ihr euren Gruppen und Gruppenmitgliedern und die damit verbundenen Berechtigungen als Teil eures Sicherheitskonzeptes besonders gut pflegen! Die erwähnten "DomainAdmins" und "Administrators" sind ein guter Anfang dafür :-)

Professionelle Angreifer werden sicher kein Powershellskript für eine Attacke auf die Admingruppen verwenden, sondern spezifischere ung mächtigere Werkzeuge. 
 

3.1 Auflisten von Gruppenmitgliedern

Diese Thema hört sich gar nicht so besonders schwierig an, hats aber doch in sich. Das liegt daran, das es erstens eine ganze Reihe verschiedener Möglichkeiten zur Lösung dieser Aufgabe gibt und zweitens auch relativ viele Randbedingungen zu beachten sind.


Möglichkeiten zum Auslesen von Gruppenmitgliedern

a) dsget-Tool
invoke-command -scriptblock {dsget group $GroupDN -members -expand}

b) cmdlet:
get-ADGroupMember

c) DirectoryEntry-Klasse
[ADSI]"LDAP://$GroupDN" /  New-Object System.DirectoryServices.DirectoryEntry("LDAP://$GroupDN")

d) ADSISearcher / DirectorySearcher
([ADSISearcher]"((SamaccountName=SmallGlobalGroup))").FindOne()

e) GroupPrincipal-Klasse
[System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($ct,$GroupName)

Randbedingungen

a) Targetgruppe in der eigenen/ fremden Domäne (ein Forest)
b) Targetgruppe enthält verschachtelte Objekte aus der eigenen/ fremden Domäne
c) LDAP oder GC Search
d) Gruppentypen
e) verschiedene Domain und Forestlevel
f) Anzahl der Gruppenmitglieder mehr oder weniger 1500

Ob ich damit alle Möglichekeiten und alle Randbedingungen erwischt habe, bin ich mir absolut nicht sicher. Es ist jedenfalls so, dass die Ergebnisse einer Abfrage stark von beiden Faktoren abhängen.
Wenn ihr euch nur in einer Single-Domain bewegt, kommt ihr wahrscheinlich kaum in Schwierigkeiten. Wollt ihr aber in einem Forest von einer Domäne aus die Mitgleider einer Cross-Forest verschachtelten Gruppe in einer anderen Domäne ermitteln, dann fallen  einge Möglichkeiten oben weg und ihr solltet sorgfältig prüfen, ob das Ergebnis eures Codes den Erwartungen entspricht. Ich spreche aus eigener Erfahrung.:-)

Ich versuche für alle oben genannten Möglichkeiten ein prägnantes Beispiel zu geben und die Vor- und Nachteile, soweit ich sie sehe zu erklären.
 

3.1.1  DSTools - dsget

Vorteile:

  • einfaches, bewährtes Tool
  • sucht sich selbstständig passende Domaincontroller auch über Domaingrenzen hinweg
  • kommt sehr gut mit Verschachtelungen über Domaingrenzen zurecht
  • keine Probleme mit Gruppen > 1500 Mitgliedern


Nachteile:

  • relativ wenige direkte Informationen
  • lässt nur ldap-Queries auf Port 389 zu
  • Ausgabe in Hochkommas eingeschlossen
  • Letzte Ausgabe eine Leerzeile
  • Ausgabe kann nicht auf Userobjekte eingeschränkt werden
  • nicht auf jedem Rechner verfügbar


Beispiel1 : Gruppenmitglieder mti dsget auslesen und Eigenschaften über ADSISearcher ermitteln

Clear-Host
Set-StrictMode -Version "2.0"

Function Main{
 
  $GroupDN = "CN=SmallLocalGroup,OU=Global,OU=Groups,OU=Bangkok,OU=Scripting,DC=asia,DC=company,DC=intern"
  #$GroupDN = "CN=GG-Europe01,OU=Local,OU=Groups,OU=Munich,OU=Scripting,DC=europe,DC=company,DC=intern"

  #$GroupDN = "CN=Administrators,CN=Builtin,DC=asia,DC=company,DC=intern"
  #$GroupDN = "CN=Administrators,CN=Builtin,DC=europe,DC=company,DC=intern"

  $Searchroot = "GC://DC=company,Dc=intern"


  Get-GroupMembers_by_dsget $GroupDN $SearchRoot
}

Function Get-GroupMembers_by_dsget {
  Param ($GroupDN, $Searchroot)

  $Members =  Invoke-command -scriptblock {Dsget group $GroupDN -members -expand}
  #Removing the "s in dsget-output
  $Members = $Members | Foreach {
     $_ -Replace ('"','')
  }

  #Removing last line if it is empty
  if ( $Members[-1] -eq "") {
    $membersCount = $Members.Count
    $Members = $Members[0..$($Members.Count-2)]
  }

  #Searching for every DN in GlobalCatalog and find out some properties
  Foreach ($MemberDN in $Members){
    $Filter =@()
    $Filter = "(objectcategory=*)"
    $Filter += "(distinguishedname=$MemberDN)"
    [string]$Filter = "(&$Filter)"
   
    $ADSISearcher = [ADSISearcher]"GC://"
    $ADSISearcher.Filter = $Filter
    $ADSISearcher.SearchRoot = $Searchroot
   
    $Memberobj = $ADSISearcher.FindOne()
    if($Memberobj){
      $SamAccountName = $Memberobj.Properties.samaccountname
      $ObjectClass = $($Memberobj.Properties.objectclass)[1]
      Write-Host "$SamAccountName;$ObjectClass"
    } 
  }#Foreach
}#end Function

Main
#mögliche Ausgabe

NestedGroup3;group
NestedGroup1;group
UserADU010;person
UserADU002;person


Ich hole mir also zuerst die DistinguishedNames aller Gruppenmitglieder und führe damit einen LDAPSearch über den gesamten Forest durch. Damit bekomme ich wirklich alle Gruppenmitglieder angezigt, egal wie deren forestweite Verschachtelung aussieht.
Außerdem kann ich so Gruppenmitglieder in unterschiedlichen Domänen ermitteln, ohne dort explizite Adminrechte zu benötigen.

Ich halte diese Suchmethode nach Gruppenmitgliedern für die universellste! Die folgenden Methoden haben mehr oder weniger starke Einschränkungen

Beispiel 2: Gruppenmitglieder der ersten Ebene auslesen

Clear-Host
Set-StrictMode -Version "2.0"

$GroupName = "Administrators"
$GroupDN = Invoke-command {dsquery group -samid $groupName }
$GroupDN = $GroupDN -Replace ('"','')

$Group = [ADSI]"LDAP://$GroupDN"

"`nAusgabe der SamAccountnamen der Gruppenmitglieder und der Objektklasse"
$Group.member | foreach{   
  $gmember=[ADSI]"LDAP://$_"
  "$($gmember.name) $($gmember.objectclass[1])"
}
#Ausgabe

 

 

3.1.2 Get-ADGroupMember

Technet: Get-AdGroupMember

Vorteile:

  • Komfort eines cmdlets
  • kommt sehr gut mit Verschachtelungen über Domaingrenzen zurecht


Nachteile:

  • relativ wenige direkte Informationen
  • lässt nur ldap-Queries auf Port 389 zu
  • nur Userobjekte werden zurückgegeben
  • ActiveDirectory-Modul auf älteren Betriebssystemen evtl. nicht verfügbar
  • Schwierigkeiten bei Gruppenmitgliedern > 1500
     

Beispiel1 : Gruppenmitglieder mti Get-ADGroupmember auslesen und Eigenschaften über ADSISearcher ermitteln
Das Beispiel hier ist sehr ähnlich zum ersten Beispiel "Gruppenmitglieder mti dsget auslesen und Eigenschaften über ADSISearcher ermitteln" im letzten Kapitel

Clear-Host
Set-StrictMode -Version "2.0"

Import-Module ActiveDirectory

Function Main{
  $GroupDN = "CN=SmallLocalGroup,OU=Global,OU=Groups,OU=Bangkok,OU=Scripting,DC=asia,DC=company,DC=intern"
     #$GroupDN = "CN=Europe-Main,OU=Local,OU=Groups,OU=Munich,OU=Scripting,DC=europe,DC=company,DC=intern"

  #$GroupDN = "CN=Administrators,CN=Builtin,DC=asia,DC=company,DC=intern"
  #$GroupDN = "CN=Administrators,CN=Builtin,DC=europe,DC=company,DC=intern"
 
  $Searchroot = "GC://DC=company,Dc=intern"
  $ServerName = "DC02"
  Get-GroupMembers_by_cmdlet $GroupDN $SearchRoot $ServerName
}

Function Get-GroupMembers_by_cmdlet {
  Param ($GroupDN, $SearchRoot, $Servername)

  $Members =  Get-ADGroupMember -identity $GroupDN -recursive -server $Servername
  $Members = $Members | Foreach {$_.distinguishedname}
 
  #Searching for every DN in GlobalCatalog and find out some properties
  Foreach ($MemberDN in $Members){
    $Filter =@()
    $Filter = "(objectcategory=*)"
    $Filter += "(distinguishedname=$MemberDN)"
    [string]$Filter = "(&$Filter)"
   
    $ADSISearcher = [ADSISearcher]"GC://"
    $ADSISearcher.Filter = $Filter
    $ADSISearcher.SearchRoot = $Searchroot
   
    $Memberobj = $ADSISearcher.FindOne()
    if($Memberobj){
      $SamAccountName = $Memberobj.Properties.samaccountname
      $ObjectClass = $($Memberobj.Properties.objectclass)[1]
      Write-Host "$SamAccountName;$ObjectClass"
    }
  }#Foreach
}#end Function

Main
#mögliche Ausgabe

UserADU002;person
UserADU004;person

Das Skript ist im Bezug auf Verschachtelungstiefe und flexible Anwendungsmöglichkeiten mit dem dsget-Skript oben durchaus vergleichbar, hat aber auch ein paar (gelborange-markierte) Unterschiede, die man beachten muss.

  a) das ActiveDirectory-Mudule muss geladen werden
  b) wenn die Abfrage in eine andere Domäne des Forests geht, muss ein Domaincontroller mitgegeben werden
  c) das Umwandeln der Resultate von Get-ADGroupMember in ein Array aus DistinguishedNames ist natürlich etwas anders als bei dsget.

Nachteilig finde ich nur Punkt b) der Serverübergabe insbesondere bei automatisierten Abfragen in einem größeren Forest. Man muss erstmal einen aktiven DC der Targetdomäne suchen, dsget erledigt dies alleine.
Natürlich lässt sich auch das skripten, siehe "Forest  und Domain", wenn man überhaupt über mehrere Domänen hinweg skripten muss.

 

3.1.3 DirectorySearcher [ADSISearcher]

Vorteile:

  • bewährte und gut dokumentierte Klasse
  • für LDAP- und  GC Queries verwendbar
  • auf jedem Windowssystem nativ verfügbar


Nachteile:

  • Gruppenmitglieder nicht über GC-Search auflistbar
  • Verschachtelungen sind schwierig aufzulösen -> Recursion erforderlich
  • Schwierigkeiten bei Gruppenmitgliedern > 1500 (siehe Kapitel 3.2)


Beispiel 1: Gruppenmitglieder der ersten Ebene auslesen

Clear-Host
Set-Strictmode -Version "2.0"

Function Main{
  $Searchroot = "DC=company,dc=intern"
  $GroupDN = "SmallLocalGroup"

  #Function Call
  Get-MyGroupMembers_By_ADSISearcher $GroupDN
}#Main

Function Get-MyGroupMembers_By_ADSISearcher{
  Param($GroupDN)

  $Filter =@()
  $Filter = "(&(objectcategory=group))"
  $Filter += "(name=$GroupDN)"
  [string]$Filter = "(&$Filter)"
 
  $DirectorySearcher = ([ADSISearcher]"LDAP://")
  $DirectorySearcher.Searchroot = "GC://$Searchroot"
  $DirectorySearcher.Filter = $Filter
  $DirectorySearcher.Searchscope = "SubTree"

  $DirectorySearcher.Findall()  | Foreach{  
  $($_.Properties).distinguishedname
  ""
  $($_.Properties).member
  ""
  }#foreach
}#Function Get-MyGroupMembers_By_ADSISearcher

Main
#mögliche Ausgabe

CN=Europe-GG,OU=Local,OU=Groups,OU=Munich,OU=Scripting,DC=europe,DC=company,DC=intern
CN=NestedGroup3,OU=Global,OU=Groups,OU=Bangkok,OU=Scripting,DC=asia,DC=company,DC=intern
CN=NestedGroup1,OU=Global,OU=Groups,OU=Bangkok,OU=Scripting,DC=asia,DC=company,DC=intern
CN=UserADU010,OU=Finance,OU=Users,OU=Munich,OU=Scripting,DC=europe,DC=company,DC=intern
CN=UserEOM006,OU=Finance,OU=Users,OU=Bangkok,OU=Scripting,DC=asia,DC=company,DC=intern


Bei dieser Art einer LDAP-Abfrage werden nur die Member der ersten Ebene, also keine Mitglieder von verschachtelten Gruppen zurückgegeben. Eine LDAP-Query, die auch verschachtelte Gruppen zurückgebt, findet ihr im nächsten Beispiel.

Beispiel 2: Verschachtelte Gruppenmitglieder auslesen
Mit diesem Beispiel könnt ihr sehr gut Gruppenmitglieder -auch wenn die in verschachtelten Gruppen liegen- auslesen, solange ihr euch in einer einzigen Domäne des Forests bewegt.
Liegen in der Gruppe verschachtelte Gruppen ausanderen Domänen, werden diese zwar noch angezigt aber nicht mehr aufgelöst

Clear-Host
Set-StrictMode -Version "2.0"

Function Main{
 $GroupDN = "CN=SmallLocalGroup,OU=Global,OU=Groups,OU=Bangkok,OU=Scripting,DC=asia,DC=company,DC=intern"
  #$GroupDN = "CN=Europe-Main,OU=Local,OU=Groups,OU=Munich,OU=Scripting,DC=europe,DC=company,DC=intern"

 
  Get-MyGroupMembers_By_ADSISearcher $GroupDN
}# End Main

Function Get-MyGroupMembers_By_ADSISearcher{
  Param($GroupDN)
 
  $Filter =@()
  $Filter = "(objectcategory=*)"   #  user | group | *
  $Filter += "(memberof:1.2.840.113556.1.4.1941:=$GroupDN)"
  [string]$Filter = "(&$Filter)"

  $DirectorySearcher = [ADSISearcher]"GC://"
  $DirectorySearcher.Searchscope = "Subtree"
  $DirectorySearcher.Filter = $Filter
  $SearchRoot = "GC://DC=Company,DC=Intern"
  $DirectorySearcher.Searchroot = $SearchRoot
  $Members = $DirectorySearcher.Findall()
  $Members | Foreach {
    $ObjectCategory = $( $($_.Properties.objectcategory) -Split (',CN=') )[0]
    $ObjectCategory = $ObjectCategory -Split ('CN=')
    "$($_.properties.distinguishedname) ; $ObjectCategory"
    }
}#End Function

Main    
#mögliche Ausgabe

CN=UserGJA10683,OU=IT,OU=Users,OU=Bangkok,OU=Scripting,DC=asia,DC=company,DC=intern ;  Person
CN=UserGJA10684,OU=IT,OU=Users,OU=Bangkok,OU=Scripting,DC=asia,DC=company,DC=intern ;  Person
CN=NestedGroup1,OU=Global,OU=Groups,OU=Bangkok,OU=Scripting,DC=asia,DC=company,DC=intern ;  Group
CN=NestedGroup2,OU=Global,OU=Groups,OU=Bangkok,OU=Scripting,DC=asia,DC=company,DC=intern ;  Group

Die Auflösung der verschachtelten Gruppen (in einer Domäne!) gelingt über das LDAP-Control 1.2.840.113556.1.4.1941. 
Denkt bitte wieder daran, die Properties ("objectcategory" und "distinguishedname") unbedingt in Kleinbuchstaben zu schreiben.

Den Unterschied zwischen objectcategory und objectclass könnt ihr bei Interesse hier nachlesen.
 

3.1.4 GroupPrincipal - Klasse

Vorteile

  • auch verschachtelte Gruppenmitglieder aus anderen Domänen werden aufgelöst
  • keine Probleme mit Gruppen > 1500 Mitgliedern


Nachteil

  • keine GC-Abfrage möglich
  • keine Verbindung auf andere Domänen möglich
     
Clear-Host
Set-StrictMode -Version "2.0"

$GroupName = "SmallLocalGroup"

[void][reflection.assembly]::LoadWithPartialName("System.DirectoryServices.AccountManagement")

$Recurse = $true
$ContextType = [System.DirectoryServices.AccountManagement.ContextType]::Domain
$GroupPrincipal = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($ContextType,$GroupName)
$GroupPrincipal.GetMembers($Recurse) | FT SamAccountName,LastPasswordSet -Auto

$Members = $GroupPrincipal.GetMembers($Recurse) | Foreach {$_}
$Members.count
#mögliche Ausgabe

SamAccountName LastPasswordSet    
-------------- ---------------    
UserADU010     04.05.2014 12:51:52
UserADU002     04.05.2014 12:51:51
UserADU006     04.05.2014 12:51:52

5

dies ist eine sehr gute Möglichkeit um Gruppenmitglieder aus einer Gruppe der eigenen Domäne zu erhalten.
 

3.2 Große Gruppen > 1500 Mitgliedern

Das Problem ist eigentlich nicht die Gruppengröße selbst, sondern das Auflisten derselben insbesondere mit LDAP. (Directorysearcher Class). Die meiner Meinung nach beste Möglichkeit mit großen Gruppen umzugehen ist über die ds-Tools "dsquery" und "dsget"   (siehe Kapitel 3.1.1)

Alternativ dazu kann man auch nur einen Teil (Range) einer Gruppe auslesen, wie im nächsten Beispiel gezeigt. Solange der Range unter 1500 bleibt, gibt es keine Fehler.

Beispiel 1: Range an Gruppenmitgliedern auslesen

Clear-Host
Set-StrictMode -Version "2.0"

Function Main{
  $GroupDN = "CN=Asia-GG,OU=Global,OU=Groups,OU=Bangkok,OU=Scripting,DC=asia,DC=company,DC=intern"
 
  $LowerLimit = 6500
  $UpperLimit = 6700
    
  $Members = Get-MemberOfLargeGroup $GroupDN $LowerLimit $UpperLimit
  $Members | Foreach {
     ([ADSI]"LDAP://$_") | Select -expandproperty Samaccountname
     }
 
} #End Function Main

Function Get-MemberOfLargeGroup{
  Param ($GroupDN,$LowerLimit,$UpperLimit)
 
  $DirectorySearcher = [ADSISearcher]"LDAP://"

  #Filtercriteria  
  $Filter =@()
  $Filter = "(objectcategory=group)"
  $filter = "(distinguishedname=$GroupDN)"
  [string]$Filter = "(&$Filter)"
  $DirectorySearcher.Filter = $Filter
    
  $Range = 'member;range=' + $LowerLimit + '-' + $UpperLimit
 
  [void]$DirectorySearcher.PropertiesToLoad.Clear()
  [void]$DirectorySearcher.PropertiesToLoad.Add($Range)
    
  $Group = $DirectorySearcher.Findone()
  $Users  = $Group.Properties.$Range
  Return ,$Users
} #end Get-MemberOfLargeGroup

Main

Dieses Skript liest also nur die Gruppenmitglieder 6500 bis 6700 aus. Die Differenz darf 1500 nicht übersteigen!


Beispiel 2: Mehr als 1500 Gruppenmitgliedern mit ADSISearcher auslesen
Das Beispiel funktioniert einigermaßen, zeigt aber keine verschachtelten Gruppenmitglieder an und auch das letzte Gruppenmitglied geht verloren. Nutzt lieber die weiter oben gezeigten Beispiele der DS-Tools oder der GroupPrincipal-Klasse. Da ich dieses Skript aber nunmal schon erstellt habe, zeige ich es euch. Vielleicht brauchts jemand.

Clear-Host
Set-StrictMode -Version "2.0"

Function Main{
  $GroupDN = "CN=Asia-GG,OU=Global,OU=Groups,OU=Bangkok,OU=Scripting,DC=asia,DC=company,DC=intern"
 
  $LowerLimit = -1
  $UpperLimit = -1
    
  Do {
  $LowerLimit += 1
  $UpperLimit += 1 # "*"  for last member
  $Members = Get-MemberOfLargeGroup $GroupDN $LowerLimit $UpperLimit
  If ( $members -eq 0) {continue} #goto the end of the do-loop
 
  $Members | Foreach {
  ([ADSI]"LDAP://$_") | Select -expandproperty Samaccountname
   }#foreach
 
 }While ($Members -ne 0)
 
} #End Function Main

Function Get-MemberOfLargeGroup{
  Param ($GroupDN,$LowerLimit,$UpperLimit)
 
  $DirectorySearcher = [ADSISearcher]"LDAP://"

  #Filtercriteria  
  $Filter =@()
  $Filter = "(objectcategory=group)"
  $filter = "(distinguishedname=$GroupDN)"
  [string]$Filter = "(&$Filter)"
  $DirectorySearcher.Filter = $Filter
    
  $Range = 'member;range=' + $LowerLimit + '-' + $UpperLimit
 
  [void]$DirectorySearcher.PropertiesToLoad.Clear()
  [void]$DirectorySearcher.PropertiesToLoad.Add($Range)
 
  Try{  
    $Group = $DirectorySearcher.Findone()
    $Users  = $Group.Properties.$Range
    Return ,$Users
  }Catch{
    Return 0
  }
} #end Get-MemberOfLargeGroup

Main
 

 

3.3 Entfernen/ Hinzufügen von Gruppenmitgliedern

3.3.1 Grundlegende Operationen

 

Beispiel 1:  Hinzufügen von Useraccounts (Distinguishednames) zu einer Gruppe
Liegen der Gruppenname und die Useraccounts bereits als DistinguishedNames vor, so ist das Skript relativ übersichtlich

Clear-Host
Set-StrictMode -Version "2.0"

Function Main{
  $TargetGroup = "CN=GG_S_GroupZAU001,OU=Global,OU=Groups,OU=Bangkok,OU=Scripting,DC=dom1,DC=intern"
  $Members2Add = @("CN=UserIMS002,OU=IT,OU=Users,OU=Bangkok,OU=Scripting,DC=dom1,DC=intern",
                   "CN=UserIMS003,OU=IT,OU=Users,OU=Bangkok,OU=Scripting,DC=dom1,DC=intern" ,
                   "CN=UserIMS004,OU=IT,OU=Users,OU=Bangkok,OU=Scripting,DC=dom1,DC=intern")
 
  #creating an object of the directoryentry-class
  $ADSIGroup = [ADSI]"LDAP://$TargetGroup"

  #adding the members to the targetgroup
  ForEach($Member in $Members2Add){
    #checking membership and adding member to the target group or not
    If ($ADSIGroup.IsMember("LDAP://$Member") -eq $False){
       $ADSIGroup.Add("LDAP://$Member") #!!!!!!!
       Write-Host "$Member successfully added to the targetgroup" -BackgroundColor DarkGreen
    }Else{
      Write-Host "$Member is already a member of the targetgroup" -BackgroundColor DarkRed
    }#If
  }#Foreach

}#Function Main

Main
#mögliche Ausgabe

CN=UserIMS002,OU=IT,OU=Users,OU=Bangkok,OU=Scripting,DC=dom1,DC=intern successfully added to the targetgroup
CN=UserIMS003,OU=IT,OU=Users,OU=Bangkok,OU=Scripting,DC=dom1,DC=intern successfully added to the targetgroup
CN=UserIMS004,OU=IT,OU=Users,OU=Bangkok,OU=Scripting,DC=dom1,DC=intern successfully added to the targetgroup

 

Beispiel 2: Hinzufügen von Useraccounts (Samaccountnames) zu einer Gruppe
Liegen der Gruppen- und/ oder die Accountnamen als Samaccountnames vor, so muss man zuerst die Distinguishednamen der Objekte bestimmen.

In diesem Skript benütze ich dazu die FindOne()-Methode der DirectorySearcher-Klasse (=[ADSISearcher]) .

Clear-Host
Set-StrictMode -Version "2.0"

Function Main{
  $TargetGroup = "GG_S_GroupZAU001"
  $Members2Add = @("UserIMS002","UserIMS003","UserIMS004")

  #function call
  Add-Members2Group $TargetGroup $Members2Add
}

Function Add-Members2Group{
  Param($TargetGroup, $Members2Add)
 
  #reading the distinguishednames of the members into an array
  $Members2Add_DN = @()
  $Members2Add_DN = ForEach($Member in $Members2Add){
     $( ([ADSISearcher]"(&(objectcategory=user)(samaccountname=$Member))").FindOne() ).Properties.distinguishedname
  }

  #getting the distinguishedname of the targetgroup
  $TargetGroup_DN = $( ([ADSISearcher]"(&(objectcategory=group)(samaccountname=$TargetGroup))").FindOne() ).properties.distinguishedname
 
  #adding the members to the targetgroup
  ForEach($Member_DN in $Members2Add_DN){
    #creating directoryentry-object, because only an directoryobject has the add-method
    $ADSIGroup = [ADSI]"LDAP://$TargetGroup_DN"
    
    #checking membership and adding member to the target group or not
    If ($ADSIGroup.IsMember("LDAP://$Member_DN") -eq $False){
       $ADSIGroup.Add("LDAP://$Member_DN")
       Write-Host "$Member_DN successfully added to $($TargetGroup)" -BackgroundColor DarkGreen
    }Else{
      Write-Host "$Member_DN is already a member of $($TargetGroup)" -BackgroundColor DarkRed
    }#If
  }#Foreach
}#Function #Add-Members2Group

Main
#mögliche Ausgabe

CN=UserIMS002,OU=IT,OU=Users,OU=Bangkok,OU=Scripting,DC=dom1,DC=intern successfully added to GG_S_GroupZAU001
CN=UserIMS003,OU=IT,OU=Users,OU=Bangkok,OU=Scripting,DC=dom1,DC=intern successfully added to GG_S_GroupZAU001
CN=UserIMS004,OU=IT,OU=Users,OU=Bangkok,OU=Scripting,DC=dom1,DC=intern is already a member of GG_S_GroupZAU001

 

3.3.2 Gruppen miteinander vergleichen

Beim Reorganisieren der Gruppen im Activedirectories kommt es immer wieder, 

Gruppenmitglieder zusammenzufassen oder eine neue Gruppe aus vorhandenen Gruppen zu erstellen. Das folgende Beispiel zeigt dazu ein paar hilfreiche Handgriffe.

Beispiel 1: Mit Arrays arbeiten

Clear-Host
Set-StrictMode -Version "2.0"


$GroupA_Members = @("user1","user2","user3","user4")
$GroupB_Members = @("user3","user4","user5","user6")

Write-Host ""`n"Member, die in GroupA, aber nicht in GroupB sind"
$Members = $GroupA_Members | Where {$GroupB_Members -NotContains $_}
$Members

Write-Host ""`n"Member, die sowohl in GroupA, als auch in GroupB sind"
$Members = $GroupA_Members | Where {$GroupB_Members -Contains $_}
$Members

Write-Host ""`n"Alle Members in GroupA und GroupB. Duplikate entfernen"
$Members = $GroupA_Members + $GroupB_Members | sort -Unique
$Members
#Ausgabe

Member, die in GroupA, aber nicht in GroupB sind
user1
user2
 
Member, die sowohl in GroupA, als auch in GroupB sind
user3
user4
 
Alle Members in GroupA und GroupB. Duplikate entfernen
user1
user2
user3
user4
user5
user6

 

Alternativ dazu kann man auch das cmdlet
Technet: Compare-Object

 

mit ein paar mehr Möglichkeiten wie Wildcards beim Vergleich nutzen, Für einfache Listen finde ich die Variante im Beispiel etwas übersichtlicher.

 

3.3.2.1 praktische Beispiele

 

Beispiel 1: Useraccounts die in GruppeA Mitglied, aber in GruppeB nicht Mitglied sind, in GruppeC aufnehmen

Clear-Host
Set-StrictMode -Version "2.0"

Function Main{
  $GroupA = "GG_S_GroupZAU001"
  $GroupB = "GG_S_GroupZAU002"
  $TargetGroup = "GG_S_GroupZAU003"

  $GroupA_Members = Get-GroupMembers $GroupA
  $GroupB_Members = Get-GroupMembers $GroupB
  $Members2TargetGroup = $GroupA_Members | Where {$GroupB_Members -NotContains $_}

  Add-Members2Group $Members2TargetGroup $TargetGroup

} #end Main

Function Add-Members2Group{
  Param($Members2TargetGroup, $TargetGroup)
 
  $TargetGroup = ([ADSISearcher]"(&(objectcategory=group)(samaccountname=$TargetGroup))").FindOne()
  $TargetGroupDN = $TargetGroup.properties.distinguishedname

  ForEach($MemberDN in $Members2TargetGroup){
    $ADSIGroup = [ADSI]"LDAP://$TargetGroupDN"
    $ADSIMember = [ADSI]"LDAP://$MemberDN"
    If ($ADSIGroup.IsMember($ADSIMember.ADsPath) -eq $False){
      $ADSIGroup.Add($ADSIMember.ADsPath)
      Write-Host "$MemberDN successfully added to $($TargetGroup.properties.samaccountname)" -BackgroundColor DarkGreen
    }Else{
      Write-Host "$MemberDN is already Member of $($TargetGroup.properties.samaccountname)" -BackgroundColor DarkRed

    }#If
  }#Foreach
}#Function #Add-Members2Group

Function Get-GroupMembers{
  Param($GroupName)
  [void][reflection.assembly]::LoadWithPartialName("System.DirectoryServices.AccountManagement")

  $Recurse = $true
  $ContextType = [System.DirectoryServices.AccountManagement.ContextType]::Domain
  $GroupPrincipal = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($ContextType,$GroupName)
 
  $Members = $GroupPrincipal.GetMembers($Recurse) | Foreach {$_.distinguishedname}
  Return $Members
}

Main
#mögliche Ausgabe

CN=UserOMA002,OU=Finance,OU=Users,OU=Bangkok,OU=Scripting,DC=dom1,DC=intern successfully added to GG_S_GroupZAU003
CN=UserOMA003,OU=Finance,OU=Users,OU=Bangkok,OU=Scripting,DC=dom1,DC=intern successfully added to GG_S_GroupZAU003

Anstelle der rot-hervorgehobenen Codezeile könnt ihr auch andere logische Verknüpfungen von GroupA und GroupB einbauen, sowie sie in Beispiel 1 im Kapitel 3.3.2 kurz vorher beschrieben sind

 

 

4 MemberOf

Beispiel 2: Anzeige aller MemberOf-Gruppen inklusiver verschachtelter Gruppen eines Users

Set-StrictMode -Version "2.0"
Clear-Host

 

Function Main{
  $User = "UserYHM018"  #Enter the Username here!!!

  $Domain = "asia.company.intern"   #Enter the Domainname here!!!

    $MainPDC = Get-DomainPDCCN $Domain

  Check-ADModule
  $DirectMemberOfGroups = (Get-ADUser –Identity $User -Server $MainPDC –Properties MemberOf `
                | Select-Object MemberOf).MemberOf

  Write-Host "MemberOf of $User"

  ForEach($DirectmemberOfGroup in $DirectMemberOfGroups)
   {
   $strGroup = $DirectmemberOfGroup.Split(',')[0]
   $MemberOfGroupCN = $strGroup.Split('=')[1]
   #Write-Host $DirectMemberOfGroup #DistinguishedName
   Write-Host $(GET-CanonicalName $DirectMemberOfGroup)
   $Pdc = Get-DomainPDCFromDN $DirectmemberOfGroup
   $Reminders = @()
   Get-Groups -MemberOfGroupCN $MemberOfGroupCN -PDC $PDC -NestedLevel "" -Reminders $Reminders 
  }#Foreach
}#Function Main

 

Function Get-DomainPDCCN{
    Param($DomainCN)
    $Context = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain",$DomainCN)
    $Domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($Context)
    $PdcCN = $Domain.PdcRoleOwner.Name
    Return $PdcCN
}#End Function

 

Function Get-DomainPDCFromDN{
  Param($DN)
  
  $Pattern1 = ",DC=" 
  $Pattern2 =","

  $Regex1 = [Regex]$Pattern1
  $Regex2 = [Regex]$Pattern2

  $Match1 = $Regex1.Match($DN)
  $Pos1 = $Match1.Index  #position of the first ",DC=" 
  
  $Regex2 = [Regex]$Pattern2
  $Match2 = $Regex2.Match($DN,1+$Pos1)  #Searching for "," behind $Pos1
  $Pos2 = $Match2.Index #Comma after DomainName
   
  $Length =  $Pos2 - $Pos1
  $Domainname = $DN.Substring(4+$Pos1,$Length-4)
  
  $Context = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain",$DomainName)
  $Domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($Context)
    
  $PdcRoleOwner=$Domain.PdcRoleOwner.Name
  
  Return $PdcRoleOwner
}#Function DomainPDCfromDN

 

Function Get-Groups{
  Param ( $MemberOfGroupCN, $PDC, $NestedLevel, $Reminders)
    
  $MemberOfGroups = (Get-ADGroup –Identity $MemberOfGroupCN -Server $PDC –Properties MemberOf | `
                     Select-Object MemberOf).MemberOf

  ForEach($MemberOfGroup in $MemberOfGroups) {
    $strMemberOfGroup = $MemberOfGroup.split(',')[0]
    $MemberOfGroupCN = $strMemberOfGroup.split('=')[1]
    $NestedLevel += "~"
    #Write-Host "$NestedLevel $MemberOfGroup)" #DistinguishedName
    Write-Host "$NestedLevel$(GET-CanonicalName $MemberOfGroup)" 
          
     If( $Reminders -Contains $MemberOfGroup) {
        Write-Host $NestedLevel"Attention, Circular Membership!" -BackgroundColor DarkYellow
        Continue
     } #if 
     $Reminders = $Reminders + $MemberOfGroup

    Get-Groups -MemberOfGroupCN $MemberOfGroupCN  $PDC $NestedLevel $Reminders 
  }#ForEach
}#Function Get-Groups

 

Function GET-CanonicalName{
#get canonicalname http://gallery.technet.microsoft.com/scriptcenter/04e4e149-519a-4834-9626-02275de57ea6
 param([string]$DN)
 $CN=""

 # Split the Distinguished name into separate bits
 #
 $Parts=$DN.Split(",")
 
 # Figure out how deep the Rabbit Hold goes
 #
 $NumParts=$Parts.Count
 
 # Although typically 2 DC entries, make sure and figure out the length of the FQDN
 #
 $FQDNPieces=($Parts -match 'DC').Count
 
 # Keep track of where the FQDN is (calling it the middle even if it
 # Could be WAY out there somewhere
 #
 $Middle=$NumParts-$FQDNPieces
 
 # Build the CN.  First part is separated by '.'
#  
foreach ($x in ($Middle+1)..($NumParts)) {
    $CN+=$Parts[$x-1].SubString(3)+'.'
    }
 
# Get rid of that extra Dot
#
$CN=$CN.substring(0,($CN.length)-1)
 
# Now go BACKWARDS and build the rest of the CN
#
foreach ($x in ($Middle-1)..0) {  
    #$Parts[$x].substring(3)
    $CN+="/"+$Parts[$x].SubString(3)
    }

  $CanonicalName = $CN 
  Return $CanonicalName
}#Function Get-CanonicalName

 

Function Check-ADModule{
  If (!(Get-Module ActiveDirectory)) {
    If (Get-Module -ListAvailable | ? {$_.Name -eq "ActiveDirectory"}) {
       Import-Module ActiveDirectory
       "Module ActiveDirectory Loaded"
       Return $True #Module is been n
    } Else {
      "Module Active Directory not available! Probably you have to install the RSAT Tools"
      Return $False
    }
 } Else {
   #Return $True  #Modul ActiveDirectory has been already loaded
 }
} #End Function


Main

#mögliche Ausgabe

MemberOf of UserYHM018
company.intern/Scripting/Groups/Universal/UG-Company-HelpdeskAll
~company.intern/Scripting/Groups/Universal/UG3
~~company.intern/Scripting/Groups/Universal/UG-Company-ReadAll
~~~company.intern/Scripting/Groups/Universal/UG3
~~~Attention, Circular Membership!
asia.company.intern/Scripting/Beijing/Groups/Global/GG_S_GroupVFZ002
~asia.company.intern/Scripting/Beijing/Groups/Global/GG_S_GroupVFZ004
~~asia.company.intern/Scripting/Beijing/Groups/Global/GG_S_GroupVFZ005
~~asia.company.intern/Scripting/Beijing/Groups/Global/GG_S_GroupVFZ003
asia.company.intern/Scripting/Beijing/Groups/Global/GG_S_GroupVFZ001
~asia.company.intern/Scripting/Beijing/Groups/Global/GG_S_GroupVFZ004
~~asia.company.intern/Scripting/Beijing/Groups/Global/GG_S_GroupVFZ005
asia.company.intern/Scripting/Bangkok/Groups/Global/GG_S_GroupKTP003
~asia.company.intern/Scripting/Bangkok/Groups/Global/GG_S_GroupKTP004
~~asia.company.intern/Scripting/Bangkok/Groups/Global/GG_S_GroupKTP005
~~~asia.company.intern/Scripting/Bangkok/Groups/Global/GG_S_GroupLZE001
~~~~asia.company.intern/Scripting/Bangkok/Groups/Global/GG_S_GroupKTP003
~~~~~asia.company.intern/Scripting/Bangkok/Groups/Global/GG_S_GroupKTP004
~~~~~Attention, Circular Membership!

Der Übersichtlichkeit halber verwende ich den sogenannten Canonicalname für die Gruppennamen. Wenn ihr lieber den DistinguishedName verwendet, sucht im Script nach "Write-Host" und ändert die Ausgabe entsprechend. 
Um die Verschachtelungstiefe deutlich zu machen, setze ich das "~"-Zeichen. Die "~" kommt hoffentlich in keinem Gruppenamen vor, so dass man das Zeichen bei einem Import nach Excel gut als Separator verwenden kann. Bei Bedarf könnt ihr im Skript nach der Zeile  ' $NestedLevel += "~"' suchen und das Zeichen ändern oder löschen. 


5 Sicherheitsgruppen

Beispiel: GroupName from Fix-SID

Clear-Host
Set-StrictMode -Version "2.0"

Function Main{

$GroupSID = "S-1-5-32-544"
$SearchRoot = "GC://DC=Company,DC=intern"
$SearchRoot = "LDAP://DC=company,DC=intern"
$GroupDN = Get-ObjectFromSID $GroupSID $SearchRoot
$GroupDN
}

Function Get-ObjectFromSID{
 Param($ObjSID,$SearchRoot)
 $SearchCategory = "group"


 $Filter =@()
 $Filter = "(objectcategory=$Searchcategory)"
 $Filter += "(objectSID=$ObjSid)"
 [string]$Filter = "(&$Filter)"
 
 $DirectorySearcher = ([ADSISearcher]"LDAP://")
 $DirectorySearcher.Filter = $Filter
 $DirectorySearcher.Searchroot = $SearchRoot
 $DirectorySearcher.SearchScope = "Subtree"
 $DirectorySearcher.PageSize = 1000
 $DirectorySearcher.Asynchronous = $True
 $DirectorySearcher.CacheResults = $False

 $Obj = $DirectorySearcher.Findall()
 $ObjDN = $Obj.properties.distinguishedname
 Return $ObjDN
}

Main

 

Beispiel: GroupName from Domain specific SID (domain Admins)

Set-StrictMode -Version "2.0"
Clear-Host

Function Main{
  $User = "User01" #Enter the Username here!!!
  $DomainCN = "asia.company.intern"

  $MainPDC = Get-DomainPDCCN $DomainCN
  Check-ADModule
  
  $DirectMemberOfGroups = (Get-ADUser –Identity  $User -Server $MainPDC –Properties MemberOf `
       | Select-Object MemberOf).MemberOf
  Write-Host "MemberOf of $User"

  ForEach($DirectmemberOfGroup in $DirectMemberOfGroups)
   {
   $strGroup = $DirectmemberOfGroup.Split(',')[0]
   $MemberOfGroupCN = $strGroup.Split('=')[1]
   #Write-Host $DirectMemberOfGroup #DistinguishedName
   Write-Host $(GET-CanonicalName $DirectMemberOfGroup)
   $Pdc = Get-DomainPDCFromDN $DirectmemberOfGroup
   $Reminders = @()
   Get-Groups -MemberOfGroupCN $MemberOfGroupCN -PDC $PDC -NestedLevel "" -Reminders $Reminders 
  }#Foreach
}#Function Main
 
Function Get-DomainPDCCN{
    Param($DomainCN)
    $Context = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain",$DomainCN)
    $Domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($Context)
    $PdcCN = $Domain.PdcRoleOwner.Name
    Return $PdcCN
}

Function Get-DomainPDCFromDN{
  Param($DN)
  
  $Pattern1 = ",DC=" 
  $Pattern2 =","

  $Regex1 = [Regex]$Pattern1
  $Regex2 = [Regex]$Pattern2

  $Match1 = $Regex1.Match($DN)
  $Pos1 = $Match1.Index  #position of the first ",DC=" 
  
  $Regex2 = [Regex]$Pattern2
  $Match2 = $Regex2.Match($DN,1+$Pos1)  #Searching for "," behind $Pos1
  $Pos2 = $Match2.Index #Comma after DomainName
   
  $Length =  $Pos2 - $Pos1
  $Domainname = $DN.Substring(4+$Pos1,$Length-4)
  
  $Context = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain",$DomainName)
  $Domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($Context)
    
  $PdcRoleOwner=$Domain.PdcRoleOwner.Name
  
  Return $PdcRoleOwner
}#Function DomainPDCfromDN

Function Get-Groups{
  Param ( $MemberOfGroupCN, $PDC, $NestedLevel, $Reminders)
  
  Try{  
  $MemberOfGroups = @(Get-ADGroup –Identity $MemberOfGroupCN -Server $PDC –Properties MemberOf | `
                     Select-Object MemberOf).MemberOf

  }Catch{
   $MemberOfGroups=@()
  }
  ForEach($MemberOfGroup in $MemberOfGroups) {
    $strMemberOfGroup = $MemberOfGroup.split(',')[0]
    $MemberOfGroupCN = $strMemberOfGroup.split('=')[1]
    $NestedLevel += "~"
    #Write-Host "$NestedLevel $MemberOfGroup)" #DistinguishedName
    Write-Host "$NestedLevel$(GET-CanonicalName $MemberOfGroup)" 
          
     If( $Reminders -Contains $MemberOfGroup) {
        Write-Host $NestedLevel"Attention, Circular Membership!" -BackgroundColor DarkYellow
        Continue
     } #if 
     $Reminders = $Reminders + $MemberOfGroup

    Get-Groups -MemberOfGroupCN $MemberOfGroupCN  $PDC $NestedLevel $Reminders 
  }#ForEach
}#Function Get-Groups

 

Function GET-CanonicalName{
#get canonicalname http://gallery.technet.microsoft.com/scriptcenter/04e4e149-519a-4834-9626-02275de57ea6
 param([string]$DN)
 $CN=""

 # Split the Distinguished name into separate bits
 #
 $Parts=$DN.Split(",")
 
 # Figure out how deep the Rabbit Hold goes
 #
 $NumParts=$Parts.Count
 
 # Although typically 2 DC entries, make sure and figure out the length of the FQDN
 #
 $FQDNPieces=($Parts -match 'DC').Count
 
 # Keep track of where the FQDN is (calling it the middle even if it
 # Could be WAY out there somewhere
 #
 $Middle=$NumParts-$FQDNPieces
 
 # Build the CN.  First part is separated by '.'
#  
foreach ($x in ($Middle+1)..($NumParts)) {
    $CN+=$Parts[$x-1].SubString(3)+'.'
    }
 
# Get rid of that extra Dot
#
$CN=$CN.substring(0,($CN.length)-1)
 
# Now go BACKWARDS and build the rest of the CN
#
foreach ($x in ($Middle-1)..0) {  
    #$Parts[$x].substring(3)
    $CN+="/"+$Parts[$x].SubString(3)
    }

  $CanonicalName = $CN 
  Return $CanonicalName
}#Function Get-CanonicalName
 
 Function Check-ADModule{
  If (!(Get-Module ActiveDirectory)) {
    If (Get-Module -ListAvailable | ? {$_.Name -eq "ActiveDirectory"}) {
       Import-Module ActiveDirectory
       "Module ActiveDirectory Loaded"
       Return $True #Module is been n
    } Else {
      "Module Active Directory not available! Probably you have to install the RSAT Tools"
      Return $False
    }
 } Else {
   #Return $True  #Modul ActiveDirectory has been already loaded
 }
} #End Function

Main

                

#mögliche Ausgabe
 

company.intern
CN=Domain Admins,CN=Users,DC=company,DC=intern

 

asia.company.intern
CN=Domain Admins,CN=Users,DC=asia,DC=company,DC=intern

Dieses Skript zeigt also den DistinguishedName der Gruppe mit der SID -512 (=Domain Admins) an.