1 kurzer Vergleich zwischen [ADSISearcher] und cmdlets

2 Computer

3 User

4 Gruppen

 

1 kurzer Vergleich zwischen [ADSISearcher] und cmdlets

Die beiden folgenden Beispiele suchen nach einem Objekt im Activedirectory und geben ein paar Properties zurück

 

Beispiel 1: Objektsuche mit einem cmdlet (Get-ADObject oder Get-ADUser)

Set-StrictMode -Version "2.0"
Clear-Host
 
$UserName = "UserLIQ002"
$ObjectCategory = "User"
$Properties = @("SamAccountName","UserAccountControl","cn","company","objectcategory")
$Filter = 'ObjectCategory -eq $ObjectCategory -and SamAccountName -eq $UserName'
 
$User = Get-ADObject -Filter $Filter -Properties $Properties  #Get-ADuser funktioniert auch
 
$User.company
$User.objectcategory
$User.objectclass
$User.useraccountcontrol

#mögliche Ausgabe (identisch Beispiel 1)

 

testcompany
CN=Person,CN=Schema,CN=Configuration,DC=dom1,DC=intern
UserLIQ002
544

 

Beispiel 2: Objektsuche mit [ADSISearcher]

Set-StrictMode -Version "2.0"
Clear-Host
 
$SamAccountName = "UserLIQ002"
$ObjectCategory = "User"
$Filter = "(&(Objectcategory=user)(SamaccountName=$SamAccountName))"
 
$User = ([ADSISearcher]$Filter).FindOne()
 
$User.Properties.company
$User.Properties.objectcategory
$User.Properties.cn
$User.Properties.useraccountcontrol

#mögliche Ausgabe (identisch Beispiel 1)

 

testcompany
CN=Person,CN=Schema,CN=Configuration,DC=dom1,DC=intern
UserLIQ002
544

Beide Möglichkeiten (cmdlet/ ADSI) zum Auffinden von Objekten funktionieren gleichgut. Letztlich entscheidet der persönliche Geschmack

2 Computer

 

3 User

Anstelle des im Kapitel 4.2 gezeigten Weges über die DirectoryEntry-Klasse [ADSI] bietet sich mit dem cmdlet "New-ADUser" ein einfacherer Weg an, um ein Userkonto im AD zu erstellen. Allerdings hat dieser Weg auch mehrere Nachteile oder zumindest Einschränkungen:  

Einschränkungl 1) Properties des IADsTSUserEx interface , das heisst alle Properties die mit Terminalserverprofilen zu tun haben, wie Terminalserverprofilepath oderTerminalServicesHomeDirectory können über die cmdlets des ActiveDirectory-Moduls nicht gesetzt werden. Wollt ihr diese Remote-Attribute setzen, so muss dies über die DirectoryEntry-Klasse geschehen: Kapitel 4.2 -> Beispiel 2 Anlage eines Users (mit vielen Properties)

 

Einschränkung 2) Bei weitem nicht alle Properties eines AD-Users haben in den entsprechenden cmdlets einen eigenen Positionsparameter, unter anderem alle MultiValue-Properties. Diese Properties müssen über eine Hashtabelle dem Postionsparameter -OtherAttributes übergeben werden. Um den zugehörigen Attributnamen herauszufinden, sieht man am besten unter MSDN: User class oder im ADSIEdit.msc nach und sucht das Attribut mit seinem LDAPDisplayName heraus und trägt diese in der Hashtabelle ein. Andererseits tragen die Positionsparameter selbst nicht unbedingt den LDAPDisplayname. "cmdlet New-AdUser: AccountExpirationDate" <-> "LDAP-Display-Name: accountExpires"

Einschränkung 3) Soll dem angelegten Userkonto ein Password mitgegeben werden, muss das Passwort in einen Securestring konvertiert werden

 

Beispiel 1: Anlage eines Beispielsusers mit New-AdUser

Set-StrictMode -Version "2.0"
Clear-Host
 
#Teil 0, DomänenDaten bestimmen
#Get-ADDomain
$DomainDN =(Get-ADDomain).DistinguishedName
$DomDNSRoot = (Get-ADDomain).DNSRoot
 
#Teil 1, Usereigenschaften definieren
#Basisinformationen für die initiale Anlage
$Sn="Napf4"
$GivenName="Karl"
$SamAccountName="A90008"
$OuPath="OU=Benutzer,OU=Scripting,$domainDN"
$Class ="User"
 
#Eigenschaften des Users
$CNUser="CN= $SN $givenName"
$UserPrincipalName="$SamAccountName@$DomFqdn"
$UserWorkstations= "PC1,PC2,PC3"
 
$Description="Bananenbieger und Tulpenknicker"
$Telephonenumber="001-288-12444"
$Othertelephone="005-133-12444","005-133-12445"
$Mail="Karl.Napf@company.intern"
 
$HomeDirectory="\\Fileserver2\Homes\%username%"
$ProfilePath="\\Fileserver2\Profiles\%username%"
$ScriptPath="\\Fileserver2\Logonscripts\%username%"
 
$Homephone="001-289-34567"
$OtherHomephone="001-289-7142585","001-289-5602310"
 
$Title="Vorstandsvorsitzender"
$Department="Vorstand"
$StreetAddress="Am Acker 9"
$Postalcode="04711"
 
$Company="Company"
$Mobile="002-2347212"
 
#Passwort verschlüsseln
$InitialPassword="Hurra123"
$AccountPassword = ConvertTo-SecureString $InitialPassword -AsPlainText -Force
$PwdLastset=-1 #User muss PW nicht bei der ersten Anmeldung ändern
 
#UseraccountControls 
$ChangePasswordAtLogon = 1 
$SmartCardLogonRequired = 1
$Enabled = 1
 
$OtherAttributes = @{
  'OtherHomephone'=$OtherHomePhone;
  'UserWorkstations'=$Userworkstations
  'operatorCount' = 1}
 
#Teil 2 Useranlage
New-ADUser -Name "$Sn$GivenName" -SamAccountName $SamAccountName -Path $OuPath `
  -GivenName $GivenName -Surname $Sn -UserPrincipalName $UserPrincipalName -Description $Description `
  -HomeDirectory $HomeDirectory -ProfilePath $ProfilePath -EmailAddress $Mail `
  -ScriptPath $ScriptPath -HomePhone $Homephone -Title $Title -Department $Department `
  -StreetAddress $StreetAddress -Postalcode $PostalCode -Company $Company -Mobile $Mobile `
  -AccountPassword $AccountPassword -ChangePasswordAtLogon $ChangePasswordAtLogon `
  -SmartCardLogonRequired $SmartCardLogonRequired -Enabled $Enabled  `
  -OtherAttributes $OtherAttributes
  #-OtherAttributes @{'OtherHomephone'=$OtherHomePhone;'UserWorkstations'=$UserWorkstations

Den Großteil des Codes macht nur die Definition der Properties aus. Die eigentliche Useranlage geschieht mit New-ADUser in Teil 2.

Beispiel 2: Useranlage mit Daten aus einer csv-Datei

csv-Datei:

Nummer;Surname;GivenName;CN;OUPath;SamAccountname;Description;InitialPassword;AccountEnabled
1;Napf;Karl;A90001;OU=Benutzer,OU=Scripting,DC=Dom2,DC=intern;A90001;Bananenbieger und Tulpenknicker;Pa$$word123;True
2;Napf;Franz-Josef;A90002;OU=Benutzer,OU=Scripting,DC=Dom2,DC=intern;A90002;Sohn von Karl Napf;Pa$$word124;True
3;Napf;Fred;A90003;OU=Benutzer,OU=Scripting,DC=Dom2,DC=intern;A90003;;Pa$$word125;False
4;Napf;Heinz;A90004;OU=Benutzer,OU=Scripting,DC=Dom2,DC=intern;A90004;Bruder von Karl Napf;Pa$$word126;False

PowershellCode:

Set-StrictMode -Version "2.0"
Clear-Host

#User importieren
$Users=Import-Csv "c:\powershell\user.csv” -delimiter ";"
$Users | foreach {
  $AccountPassword=ConvertTo-SecureString $_.UserPassword -AsPlainText -Force
  If ($_.AccountEnabled -eq "True"){
    $Enabled=1
  }else{
    $Enabled=0
  }

#Useranlage
New-AdUser -Surname $_.Sn -GivenName $_.GivenName -Name $_.CN -Path $_.OUPath `
   -SamAccountName $_.SamAccountName -Description $_.Description -AccountPassword $AccountPassword `
   -Enabled $Enabled  
 
}

Im Vergleich zur Anlage mit [ADSI] in Kapitel 4.1 -> Beispiel 3 ist dieser Code doch deutlich kompakter. Aber, wie bereits erwähnt, stellen die cmdlets keine Parameter zur Eingabe von Terminalserverprofile-Eigenschaften bereit.

Neben der erforderlichen Verschlüsselung des Initialpasswords muss man hier mit der Verwendung von Boolschen Werten (AccountEnabled) aufpassen. Direkt als $False akzeptiert der Positionsparameter "-Enabled" die Eigenschaft $_AccountEnabled nur, wenn sie vollkommen leer ist! Ich prüfe daher diese Variable, ob sie "True" ist und setze die $Enabled entsprechend.

Ich habe im Vergleich zum [ADSI]-Code die FeldNamen in der CSV-Datei an die Positionsparameternamen des cmdlets angepasst. (AccountDisabled -> AccountEnabled)

 

Beispiel 3: Auslesen von Userkonten, die sich seit xx-Tagen nicht mehr an der Domäne angemeldet haben (Get-ADUser/ Set-ADUser)

Set-StrictMode -Version "2.0"
Clear-Host

 
#VariablenDefinition
$OuDn="OU=Benutzer,OU=Scripting,Dc=Dom1,Dc=intern" #User werden aus dieser OU gelesen
$MaxAgeInDays=90

 
#Userobjekte der OU auslesen und weiterverarbeiten
$Users = Get-ADUser -Filter * -SearchBase $OUDn -SearchScope 1 -Properties LastLogonDate,Enabled
$Users | foreach {
 if($_.LastLogonDate){
   if($((Get-date)-$_.LastLogonDate).days -gt $MaxAgeInDays){
   #Set-AdUser $_.SamAccountname -Enabled 0 #disablen alter Useraccounts
   "{0};{1:d};{2}" -f $_.Name,$_.LastLogonDate,$_.Enabled
  }#if($((Get-Date)...
 }#($_.LastLogonDate)
}#$Users | foreach
#mögliche Ausgabe

C15008;09.06.2012;True
C15009;24.11.2010;False
Karl Napf;17.08.2012;True

-SearchScope kann folgende Werte haben

Base or 0  - Es werden alle Elemente des angegebenen Pfades ohne Unterpfade ausgelesen
OneLevel or 1 - Es werden alle Elemente des angegebenen Pfades und die ersten Unterpfade ausgelesen
Subtree or 2 (Default) - Es werden alle Elemente des angegebenen Pfades und alle Unterpfade ausgelesen

 

Beispiel 4: Auslesen von AD-Usern auf den Bildschirm und in eine csv-Datei

Set-StrictMode -Version "2.0"
Clear-Host
 
#Domäneninformationen bestimmen
$DomainDN = ([ADSI]"LDAP://rootDSE").defaultNamingContext
 
#Variablen setzen
$OuDn="OU=BenutzerA,OU=scripting,$DomainDN" #User werden aus dieser OU gelesen
$Properties=@("SamAccountName","LastLogonDate","OtherTelephone","Mail","Url","cn")
$SearchScope=1  #0,1,2
$myOFS = "#" #Trennzeichen bei Multivalued-Attributen
 
#Userobjekte in das Array $Users einlesen
$Users = @(Get-ADUser -Filter * -SearchBase $OUDn -SearchScope $SearchScope -Properties $Properties)
 
#Anlage der DataTabelle mit Spalten für jede Property in Properties
$DataTable=New-Object System.Data.DataTable("ExportUsers")
$Properties | foreach {
 $Column = New-Object System.Data.DataColumn($_)
 $DataTable.Columns.Add($Column)
 }
#$DataTable.PrimaryKey = $DataTable.Columns["SamAccountName"]
 
#Befüllen der DataTable mit Daten
 foreach ($User in $Users){
   $PropertiesInRow =@()
   foreach($Property in $Properties) {
      $OFS=$myOFS
      $PropertiesInRow += [String]$($User.$($Property.ToString()))
      $OFS="+"
   }#foreach($Property in $Properties)
    $DataTable.Rows.Add($PropertiesInRow) |Out-Null
}#foreach ($User in $Users)

#Ausgabe
$DataTable | Format-Table -Auto
$DataTable | Export-Csv "c:\temp\myUsers.csv" -Delimiter ";"
#mögliche Bildschirmausgabe gekürzt

SamAccountName LastLogonDate       OtherTelephone   Mail                  Url                cn        
-------------- -------------       --------------   ----                  ---                --        
C15008         06/09/2012 13:57:10 456#3456#234#123 test@yahoo.de                            C15008    
C15009         11/24/2010 23:55:38 3353#334#333     test@intern.de        www.c.de#www.b.de  C15009    
C15039                                                                                       C15039    
C15041                                                                                       C15041    
C15042                                                                                       C15042    
Heinz_Hinz                                                                                   Heinz Hinz
Karl_Napf      08/17/2012 14:17:13                  Karl.Napf@dom1.intern                    Karl Napf

 

Beispiel 5: Auslesen der Tombstoned Userobjects

Set-StrictMode -Version "2.0"
Clear-Host
 
Import-Module ActiveDirectory
 
$Properties = @("SamAccountName","UserAccountControl","cn","WhenChanged","objectcategory") 
$Filter = 'ObjectClass -eq "User"'
$Users = Get-ADObject -Filter $Filter -IncludeDeletedObjects -Properties $Properties
 
 $users | %{
   If ($_.deleted -eq $True){
     "{0}  {1}" -f $($_.cn),$($_.whenchanged)
  }
}

#mögliche Ausgabe

 

UserVUP020
DEL:3bbef799-a763-47e4-8346-aa9a280e072c  03.10.2014 14:52:59

Das Auslesen von Tombstoned-Objekten mit Hilfe von [ADSISearcher] findet ihr im Kapitel LDAPQueries "4.2.7 Eigenschaft Tombstone"

 

Falls ihr euch für den Restore von Tombstoned-Objekten interessiert, findet ihr hier weitere Informationen

Technet: Restoring a deleted Active Directory object using the Get-ADObject and Restore-ADObject cmdlets

 

4 Gruppen

Auch für die Verwaltung von Gruppen liefert das ActiveDirectory-Module eine Reihe von cmdlets

Get-Command -module ActiveDirectory -name *Group* | Select Name
#Ausgabe mit PS V3.0

Name  
----            
Add-ADGroupMember
Add-ADPrincipalGroupMembership
Get-ADAccountAuthorizationGroup
Get-ADGroup
Get-ADGroupMember
Get-ADPrincipalGroupMembership
New-ADGroup
Remove-ADGroup
Remove-ADGroupMember 
Remove-ADPrincipalGroupMembership
Set-ADGroup

 

Beispiel 1: Gruppenmitglieder von einer Gruppe in eine andere Gruppe kopieren (Get-ADGroupMember/ Add-ADGroupMember)

Es kommt natürlich vor, dass einige Mitglieder der Quell-GruppeA bereits in der Ziel-GruppeB enthalten sind. Solche Member aus GruppeA sollen dann nicht der GruppeB hinzugefügt werden, da sonst eine Fehlermeldung geworfen würde.

Folgender Code versucht nur Groupmembers zu GroupB hinzuzufügen, die dort noch nicht existieren:

Clear-Host
Set-StrictMode -Version "2.0"

$GroupNameA = "Group1"
$GroupNameB = "Group2"

$MembersA = @(Get-ADGroupMember $GroupNameA | Foreach{$_.SamAccountName})
$MembersB = @(Get-ADGroupMember $GroupNameB | Foreach{$_.SamAccountName})
$Members2Add = $MembersA | Where {$MembersB -NotContains $_}
 

 If($Members2Add){
  Add-ADGroupMember $GroupNameB -Members $Members2Add
}

Im orange eingefärbten Block erstelle ich aus den Mitgliedern der Quell- und Zielgruppe je ein Array (Members1 und Members2). Durch die Subtraction des Arrays MembersB von Array MembersA erhalte ich letztlich das Array $Members2Add, dass ich im grün eingefärbten Codeteil der GruppeB hinzufüge. Die If-Abfrage am Ende dient dazu Fehler abzufangen, falls $Member2Add leer sein sollte.

 

Solche oder ähnliche Aufgabentypen kommen im ActiveDirectory-Leben durchaus häufig vor: "Füge Accounts (Objekte) irgendwo hinzu, aber prüfe zuerst ob der Account oder das Object schon vorhanden ist. Oder eine Abwandlung: Füge nur aktive Accounts hinzu."

Als ersten Lösungsansatz hat man vielleicht folgendes Vorgehen im Sinn,

  1. die Mitglieder der Quellgruppe auszulesen (wie im Beispiel gezeigt)
  2. mit einer If-Abfrage zu prüfen, ob die Bedingungen (beispielsweise Account  ist enabled) für jede einzelnes Mitglied aus GruppeA zutrifft
  3. jedes passende Mitglied aus GruppeA einzeln zu GruppeB hinzu zufügen.
  4. Mittels Try/Catch Fehler abzufangen
  5.  

Beispiel 2: Nur Enabled GroupMembers von einer Gruppe in eine andere Gruppe kopieren

Vielleicht sieht ein Entwurf Entwurf so aus:

Set-StrictMode -Version "2.0"
Clear-Host

$GroupNameA = "Group1"
$GroupNameB = "Group2"

$MembersA = @( Get-ADGroupMember $GroupNameA | Foreach{$_.SamAccountName} )

ForEach($MemberA in $MembersA){
  If ( ((Get-ADUser $MemberA -Properties Enabled).Enabled) -eq $True ){
    Try{
      Add-ADGroupMember $GroupNameB -Members $MemberA
    }Catch{}
  }  
}

Dieser Entwurf funktioniert einwandfrei, kann aber aufgrund seiner vielen ADSI-Zugriffe gegen einen Domaincontroller bei großen Aktionen mit vielen Groupmembers viel Zeit (Stunden) verbrauchen.

Schneller und übersichtlicher wird der Code, wenn wir Beispiel 1 leicht abwandeln:

Clear-Host
Set-StrictMode -Version "2.0"

$GroupNameA = "Group1"
$GroupNameB = "Group2"


$MembersA = @(Get-ADGroupMember $GroupNameA |  Get-ADUser | Where { $_.Enabled -eq 'True' } | Foreach{$_.SamAccountName})
$MembersB = @(Get-ADGroupMember $GroupNameB | Foreach{$_.SamAccountName})
$Members2Add = $MembersA | Where {$MembersB -NotContains $_}
 

 If($Members2Add){
  Add-ADGroupMember $GroupNameB -Members $Members2Add
}

Tipp: Vermeidet bei Massenaktionen im AD -soweit möglich- Schleifen, If-Abfragen und Try/ Catch - Konstrukte.

Oft kommt man Arrays und Listen deutlich schneller und eleganter ans Ziel. Hierfür ist auch das cmdlet "Compare-Object" ein sehr nützliches Werkzeug