Einleitung [ADSI]
1.1 Überblick über [ADSI]-Provider (=Moniker)
      Beispiel 1: [ADSI] TypeAccelerator mit LDAP-Provider 
      Beispiel 2: Ein Userobject mit Get-ADUser auslesen
1.2 Beschreibung des WinNT- und ADSI-Provider
1.2.1 WinNT Provider
         Beispiel 1: Neues Passwort auf einen bestehenden lokalen oder Domänenuser setzen
         Beispiel 2: Neuanlage eines lokalen oder DomänenUsers mit lokalen Eigenschaften
         Beispiel 3a: Hinzufügen eines lokalen User zu einer lokalen Gruppe
         Beispiel 3b: Hinzufügen eines DomänenUsers zu einer lokalen Gruppe
         Beispiel 4a: Entfernen eines lokalen Objekts (User oder Gruppe) aus einer lokalen Gruppe
         Beispiel 4b: Entfernen einer Domaingruppe und der "Authenticated Users" aus einer lokalen Gruppe
         Beispiel 5: Auslesen einer Usereigenschaft aus einem AD mittels WINNT-Provider
         Beispiel 6: Auslesen aller lokalen User
1.2.2 LDAP Provider
1.2.2.1 LDAP Connection Strings
            Beispiel 1: dynamisches Ermitteln des distinguishedNames der Domäne, des PDCEmulators und des FQDNs der Domäne
1.2.2.2 Connection auf die Domäne
            Beispiel 1: Verbindung auf die Domäne
            Beispiel 2: Anzeigen von Domäneneigenschaften
1.2.2.3 Connection auf RootDSE
            Beispiel 1: Zugriff auf RootDSE
            Beispiel 2a: Ermitteln einiger LDAP-Eigenschaften des RootDSE
            Beispiel 2b: Zugriff auf RootDSE mit Get-ADRootDSE
1.2.2.4 Kerberos / NTLM Authentifizierung
            Beispiel 1: Bindung an RootDSE über das Kerberosprotokoll
            Beispiel 2: Bindung an RootDSE über das NTLMProtokoll
1.3 Useraccountcontrol / UserFlags
            Beispiel 1: Umwandeln von HexZahlen in Dezimalzahlen und umgekehrt
            Beispiel 2: Setzen eines Usersettings über WinNT und LDAP
            Beispiel 3a: Setzen der Useraccountcontroll "Smartcard required" mit Hilfe des WinNT-Provider
            Beispiel 3b: Setzen der Useraccountcontroll "Smartcard required" mit Hilfe des LDAP-Provider
            Beispiel 4: Ermitteln aller User eines Directories, deren Account gesperrt ist und die sich gleichzeitig das Smartcardflag gesetzt haben

1.4 Accountverwaltung mit Net-* Befehlen


1 Einleitung

Wenn ihr auf der Suche nach LDAP-Abfrage gegen das ActiveDirectory seit, habe ich hier eine ganze Reihe von Beispielen zusammengestellt
Powershellpraxis: Queries mit .Net
Auf dieser Seite geht es mehr ums Prinzip, wobei ich dennoch viele Beispiele eingebaut hat, insbesondere zur Verwaltung von lokalen Computerkonten mittels WINNT://

In diesem und den folgenden Kapiteln werdet ihr oft Codezeilen finden, die so aussehen:

$OU = [ADSI]"LDAP://Ou=Scripting,Dc=Dom1,Dc=Intern"
$OU = [ADSI]"GC://Ou=Scripting,Dc=Dom1,Dc=Intern"
$Computer = [ADSI]"WinNT://$Computer"
$OU = [ADSISearcher]"LDAP://Ou=Scripting,Dc=Dom1,Dc=Intern"
$OU = [ADSISearcher]"GC://Ou=Scripting,Dc=Dom1,Dc=Intern"


Ebenso findet man diese Syntax, sobald man das Powershelluniversum verläßt und sich auf CSharp oder VisualBasic Seiten umsieht, wie dort Problemstellungen angegangen werden. Ich würde euch sehr empfehlen, sich mit dieser Schreibweise näher zu beschäftigen, auch wenn mittlerweile (im Jahr 2014) immer mehr die ActiveDirectory Cmdlets zum Einsatz kommen.

[ADSI] und [ADSISearcher] sind sogenannte TypeShortcuts (oder auch TypeAccelerator) der .Net Klasse "System.DirectoryServices.DirectoryEntry" und "System.DirectoryServices.Directorysearcher". Weitere solcher ShortCuts findet ihr unter .Net -> 3 TypeAccelearators/ TypeShortcuts .

Für die richtige Verwendung von [ADSI] und [ADSISearcher] benötigt man außerdem den passenden Provider, der mittlerweile (Jahr 2014) immer häufiger Moniker genannt wird.

Gleich im nächsten Kapitel stelle ich die vorhandenen Provider vor und zeige im Beispiel 1, dass der TypeShortcut [ADSI] und "System.DirectoryServices.DirectoryEntry" identisch verwendet werden können, um ActiveDirectory-Objekte (User, Gruppen, Computer) als Objekte in der Powershell zu bearbeiten.

Die weiteren Kapitel bieten dann einige Beispiele, um insbesondere den Unterschied zwischen dem LDAP- und WINNT-Moniker zu verstehen.

 

1.1 Überblick über [ADSI]-Provider (=Moniker)

Der erste wichtige Schritt beim Programmieren im AD ist es einem Powershellobjekt ein AD-Objekt zuzuordnen.

Danach kann man die Eigenschaften dieses Objekts verändern (Usernamen ändern), diesem Objekt ein Unterobjekt hinzufügen (User in einer OU erstellen) und viele Dinge mehr. Kennt man den Pfad des Objects bereits, welches verändert oder geprüft werden soll, so benutzt man zur Objekterstellung den [ADSI] Type-Accelerator, gefolgt vom passenden Provider (LDAP://, WinNT://, GC und ein paar mehr) und dem ADPfad (Ou=Scripting,Dc=Dom1,Dc=Intern) zu dem Objekt.

In Kapitel "Queries mit .Net" werden die Schritte beschrieben, wenn das Directory erst nach bestimmten Kriterien mit dem [ADSISearcher] Type-Accelerator durchsucht werden muss, um eines oder mehrere AD-Objekte zu finden, die danach im weiteren Skript bearbeitet werden können.

 

Die möglichen Provider (oder Moniker oder Protokolle) von [ADSI] und [ADSISearcher], sowie weitere Hintergründe findet man im ScriptingGuide:
Technet: Windows Scripting Guide 2000 Table 5.5 ADSI Providers and the Directory Services They Access

Provider

To Access

Beispiel

LDAP provider

LDAP Version 2 - and Version 3-based directories, including Active Directory.

[ADSI]"LDAP://dc=NA,dc=fabrikam,dc=com"

GC provider

The Global Catalog on Active Directory domain controllers designated as Global Catalog servers. The GC provider is similar to the LDAP provider but uses TCP port number 3268 to access the Global Catalog.

[ADSI]"GC://dc=NA,dc=fabrikam,dc=com"

ADSI OLE DB provider

Active Directory to perform search operations.

ADO.Net -> Technet: Searching Active Directory

WinNT provider

Windows NT domains and Windows NT/Windows 2000/Windows XP local account databases.

[ADSI]"WinNT://NA"

IIS provider

The Internet Information Services (IIS) metabase.

[ADSI]"IIS://sea-dc-01.na.fabrikam.com"

NDS provider

Novell NetWare Directory Services.

[ADSI]"NDS://server01/o=org01/dc=com/dc=fabrikam/dc=na"

NWCOMPAT provider

The Novell Netware Bindery.

[ADSI]"NWCOMPAT://server01"

In der Praxis habe für Active Directory bisher nur die gelb gefärbten Provider benutzt.

Beispiel 1: [ADSI] TypeAccelerator mit LDAP-Provider 

Zum besseren Verständnis möcht ich drei Schreibweisen dieser Klasse zeigen. Meistens benutze ich auch die letzte, kompakte [ADSI]-Schreibweise.

Set-StrictMode -Version "2.0"
Clear-Host
 
#Variablendefintionen
$DomainDN = ([ADSI]"LDAP://rootDSE").DefaultNamingContext #z.B. Dc=Dom1,Dc=Intern"
$UserDN = "CN=Administrator,CN=Users,$domainDN"


# Powershellsyntax
$User = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$UserDN")
$User

# DotNet Schreibweise
$User = [System.DirectoryServices.DirectoryEntry]"LDAP://$UserDN"
$User

# Type-Accelerator ADSI und LDAP-Provider
$User=[ADSI]"LDAP://$UserDN"
$User
# Ausgabe für alle drei Schreibweisen

distinguishedName : {CN=Administrator,CN=Users,DC=Dom1,DC=intern}
Path              : LDAP://CN=Administrator,CN=Users,DC=Dom1,DC=intern

Anmerkungen:

1. Die Syntax in VB2005/2008 lautet:

Dim UserDN as String ="CN=Administrator,CN=Users,DC=dom1,DC=Intern"
Dim User As New DirectoryServices.DirectoryEntry("LDAP://" & UserDN)

2. anstelle des LDAP-Providers könnte man auch den GC-Provider nutzen. Die Anfrage würde dann nicht gegen einen beliebigen DC über Port 389 laufen, sondern gegen einen GlobalCatalogServer auf Port 3268

# Type-Accelerator ADSI und GC-Provider
$User=[ADSI]"GC://$UserDN"

 
Beispiel 2: Ein Userobject mit [ADSI] auslesen

# Type-Accelerator ADSI und GC-Provider
Clear-Host
Set-StrictMode -Version "2.0"

Function ConvertADSLargeInteger ([object] $adsLargeInteger){
  $HighPart = $adsLargeInteger.GetType().InvokeMember("HighPart", [System.Reflection.BindingFlags]::GetProperty, $null, $adsLargeInteger, $null)
  $LowPart = $adsLargeInteger.GetType().InvokeMember("LowPart", [System.Reflection.BindingFlags]::GetProperty, $null, $adsLargeInteger, $null)


  $Bytes = [System.BitConverter]::GetBytes($HighPart)
  $Tmp = [System.Byte[]]@(0,0,0,0,0,0,0,0)
  [System.Array]::Copy($Bytes, 0, $Tmp, 4, 4)
  $HighPart = [System.BitConverter]::ToInt64($Tmp, 0)

  $Bytes = [System.BitConverter]::GetBytes($LowPart)
  $LowPart = [System.BitConverter]::ToUInt32($Bytes, 0)

  Return $lowPart + $highPart
  #Funktion von bsonposh.com
}


$UserDN = "CN=Administrator,CN=Users,DC=Dom1,DC=intern"

$User = [ADSI]"GC://$UserDN"
Write-Host "SamAccountName: $($User.Properties.SamAccountName)"
Write-Host "Description: $($User.Properties.Item('Description'))"
Write-Host "WhenCreated: $($User.WhenCreated)"
Write-Host "AccountDisabled: $($User.InvokeGet('AccountDisabled'))"

$LastLogonTimeStamp=[DateTime]::FromFileTimeUtc((ConvertADSLargeInteger $User.InvokeGet("LastLogonTimestamp")))
Write-Host "LastLogonTimeStamp: $LastLogonTimeStamp"
# mögliche Ausgabe:

SamAccountName: Administrator
Description: Built-in account for administering the computer/domain
WhenCreated: 10/01/2013 20:57:02
AccountDisabled: False
LastLogonTimeStamp: 03/19/2014 22:06:48

 

Beispiel 3: Ein Userobject mit Get-ADUser auslesen

Mit einem Powershell-Cmdlet wird die Syntax einfacher. Allerdings verliert man damit vielleicht andere nützliche Vorteile. 

Set-StrictMode -Version "2.0"
Clear-Host
 
$User = Get-Aduser "Administrator"
$User
#Ausgabe 
 
DistinguishedName : CN=Administrator,CN=Users,DC=dom2,DC=intern
Enabled           : True
GivenName         : 
Name              : Administrator
ObjectClass       : user
ObjectGUID        : 40e0c332-db20-4358-a952-9860c56fc27b
SamAccountName    : Administrator
SID               : S-1-5-21-539237003-4186710169-445577499-500
Surname           : 
UserPrincipalName : 

 

1.2 Beschreibung des WinNT- und LDAP-Providers

Die häufigsten Provider (=Moniker) aus der Tabelle in 3.1 oben sind LDAP:// und identisch in der Handhabe GC://. Da ich den WinNT-Provider dennoch nicht uninteressant finde, man aber im Internet deutlich weniger dazu findet, habe ich unter Kapitel 3.2.1 einige Beispiele zusammengestellt. In Kapitel 3.2.2 gehe ich dann eher allgemein auf den LDAP-Provider ein und vergleiche LDAP- und WinNT Provider miteinander.
Konkrete Beispiele zu ADObjekten wie Useranlage, Gruppenzugehörigkeiten auslesen und ähnliches kommen in Kapitel 4

 

1.2.1 WinNT Provider

WinNT ist der Provider, den man in erster Linie für den Umgang mit lokalen Usern oder Gruppen, also der lokalen SAM,  benutzt. Auch der Zugriff auf NT4.0 Domaincontroller ist damit möglich.
WinNT ist nicht ganz so verbreitet wie LDAP für das Verwalten eines ActiveDirectories, dennoch kann man ihn auch dafür nutzen. Das Setzen von Usereigenschaften oder das Verschieben von Usern in Gruppen, also Aktionen die keine reinen AD-Funktionalitäten wie den Distinguishedname nutzen, kann man mit WinNT bequem und kompakt skripten.


Beispiel 1: Neues Passwort auf einen bestehenden lokalen oder Domänenuser setzen

Set-StrictMode -Version "2.0"
Clear-Host

#Variablendefinition
$UserName="KarlNapf"
$Computer="Dom2Dc01"

#UserObjekt auf die Variable $user binden
$ADSIUser=[ADSI] "WinNT://$Computer/$UserName"

#Eigenschaft anpassen
$ADSIUser.Setpassword("Hurra123")

#Variableninhalt zurückschreiben
$ADSIUser.SetInfo()

Für die Variable "Computer" kann ein Domaincontrollername, ein lokaler Workstationname oder ein "." für den verwendeten lokalen Rechner verwendet werden. Jenachdem greift das Sript auf eine SAM oder ein ActiveDirectory zu.

Ab PowershellnV3.0 ist der Abschluss mit SetInfo() nicht mehr notwendig

 

Beispiel 2: Neuanlage eines lokalen oder DomänenUsers mit lokalen Eigenschaften

Set-StrictMode -Version "2.0"
Clear-Host

$UserName="KarlNapf"
$Computer="Dom2Win8"
$ADSIComputer=[ADSI]"WinNT://$Computer"

 
#Useranlage
$NewADSIUser = $ADSIComputer.Create("User", $UserName)

#Das Password muss unmittelbar nach Erstellen des Accounts gesetzt werden
$NewADSIUser.SetPassword("Hurra333")

#Nach der Kontoanlage ist ein SetInfo notwendig
$NewADSIUser.SetInfo()  

#Definition einiger Kontoeigenschaften
$Beschreibung = "meine Beschreibung"
$HomeDirectory = "c:\basisordner" #oder \\Server1\Folder1
$LoginScript = "loginscript.vbs"
$Profile = "c:\profilepath"

#Setzen der Kontoeigenschaften
$NewADSIUser.InvokeSet("Description",$Beschreibung) #ab PS V2.0: psbase vor InvokeSet nicht mehr nötig
$NewADSIUser.Description=$Beschreibung
$NewADSIUser.HomeDirectory = $HomeDirectory
$NewADSIUser.LoginScript = $LoginScript
$NewADSIUser.Profile = $Profile


#User muss Password bei der nächsten Anmeldung ändern
$NewADSIUser.InvokeSet("PasswordExpired", 1)
 
#Account enablen
$NewADSIUser.InvokeSet('AccountDisabled', $False)
 
#zurückschreiben in die Userdatenbank AD oder SAM 
$NewADSIUser.SetInfo()

MSDN: DirectoryEntries.Add Method

Anstelle der Methode $AdsiOU.Children.Add(...) habe ich hier kürzer $AdsiOU.Create(...) geschrieben.

Ergebnis bei Anlage auf einem lokalen Rechner:

Anmerkungen:

  • Bei Powershell V1.0 musste man zwischen $NewADSIUser.InvokeSet noch ein psbase setzen. also beispielsweise $NewADSIUser.psbase.InvokeSet('AccountDisabled', $False)
  • Für die Variable "Computer" kann ein Domaincontrollername oder ein lokaler Workstationname  für den verwendeten lokalen Rechner verwendet werden. Jenachdem greift das Sript auf eine SAM oder ein ActiveDirectory zu. Von der Verwendung des "." würde ich aber hier abraten, da dies oft zu Fehlern führt, weil der Punkt im String als RegularExpression interpretiert wird.
     

Beispiel 3a: Hinzufügen eines lokalen Users zu einer lokalen Gruppe

Set-StrictMode -Version "2.0"
Clear-Host

$UserName="KarlNapf"
$LocalComputerName="Dom2Win8"
$GruppenName="Test01"

$ADSIUser=[ADSI]("WinNT://$LocalComputerName/$UserName")
$ADSIUser
$ADSIGroup =[ADSI]("WinNT://$LocalComputerName/$GruppenName")
$ADSIGroup.Invoke("Add",$ADSIUser.Path)

$ADSIUser.Setinfo()

wie oben schon angedeutet, würde die Verwendung von "." für den Computernamen zu einem Fehler führen

 

Beispiel 3b: Hinzufügen eines DomänenUsers zu einer lokalen Gruppe

Set-StrictMode -Version "2.0"
Clear-Host

#Lokale Variablen
$LocalComputerName="Dom2Win8" #hier könnte auch ein AD-Domaincontroller oder "." stehen
$Gruppenname="Test01"

#DomänenVariablen
$UserName="DomUser01"
$DCName="Dom2Dc01"

#Binden der Objekte
$ADSIUser=[ADSI]("WinNT://$DCName/$UserName")
$ADSIGroup =[ADSI]("WinNT://$LocalComputerName/$Gruppenname")

#ADSIObjekt der Gruppen hinzufügen
$ADSIGroup.Invoke("Add",$ADSIUser.Path)

#Bestätigen der Aktion
$ADSIUser.Setinfo()

Ab Powershell_V3 ist der Abschluss mit SetInfo() nicht mehr notwendig

 

Beispiel 4a: Entfernen eines lokalen Objekts (User oder Gruppe) aus einer lokalen Gruppe

Set-StrictMode -Version "2.0"
Clear-Host

#Variablendefinition
$GroupName="Test01"
$LocalComputerName="Dom2Win8"
$RemovingObjectName="KarlNapf" #Gruppe oder User

#Binden
$RemovingObject=[ADSI]("WinNT://$LocalComputerName/$RemovingObjectName")
$ADSIGroup =[ADSI]("WinNT://$LocalComputerName/$GroupName")

#Entfernen des lokalen Benutzerkontos
$ADSIGroup.Invoke("remove",$RemovingObject.Path)

#Auflisten der Mitglieder der Gruppe "Test01"
$MemberList=@()
$MemberList=$ADSIGroup.Invoke("members")
$MemberList  | foreach {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}


Beispiel 4b: Entfernen einer Domaingruppe und der "Authenticated Users" aus einer lokalen Gruppe

Set-StrictMode -Version "2.0"
Clear-Host

#Variablendefinition
$LocalGroupname="Benutzer"
$LocalComputeName="Client01"

$RemovingObjectName="Authentifizierte Benutzer"
 
#DomänenVariablen
$DCName="Dom2Dc01"
$RemovingObjectName_2="Domain Users" #Gruppe oder User

#Binden
$ADSIRemovingObject_2=[ADSI]("WinNT://$DCName/$RemovingObjectName_2")
$ADSIRemovingObject=[ADSI]("WinNT://$RemovingObjectName")
$ADSIGroup =[ADSI]("WinNT://$LocalComputerName/$LocalGroupname")

#Entfernen der Gruppenmitglieder
$ADSIGroup.Invoke("remove",$ADSIRemovingObject.Path)
$ADSIGroup.Invoke("remove",$ADSIRemovingObject_2.Path)

#Auflisten der Mitglieder der Gruppe "Benutzer"
$MemberList=$ADSIGroup.Invoke("members")
$MemberList | ForEach {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}


Beispiel 5: Auslesen einer Usereigenschaft aus einem AD mittels WinNT-Provider

Set-StrictMode -Version "2.0"
Clear-Host

$UserName="KarlNapf" #SamAccountname
$DCName= "Dom2Dc01"

$ADSIUser=[ADSI] "WinNT://$DCName/$UserName"

"Account Disabled: {0}" -f $ADSIUser.InvokeGet("AccountDisabled")
"Description: {0}" -f $($ADSIUser.Description)
#mögliche Ausgabe

Account Disabled: False
Description: meine Beschreibung

In Kapitel "4.1.2 Auslesen und Verändern von Userproperties" folgen umfangreichere Beispiele, um Usereigenschaften auszulesen und zu exportieren. Dort benutze ich den LDAP-Provider

Beispiel 6: Auslesen aller lokalen User

Set-StrictMode -Version "2.0"
Clear-Host

$Machine=([ADSI]"WinNT://.")
$Users=$Machine.Children | Where-Object {$_.SchemaClassName -eq "User"}
$Users | select -expand name
#mögliche Ausgabe

MickeyMouse
ASPNET
Hilfeassistent
KarlNapf
SUPPORT_482945a0

#Details: blogs.technet.com/b/heyscriptingguy/archive/2008/05/22/how-can-i-use-windows-powershell-to-determine-whether-a-local-user-account-exists.aspx


Der WinNT bietet eine Reihe von Methoden und Eigenschaften. In der MSDN sind diese aufgeführt 

 

setpassword-Methode: 

MSDN: IADsUser::SetPassword Method

 

Unterstützte und nicht unterstützte Methoden und Eigenschaften:

Beispiel zum Verändern einer lokalen Gruppenmitgliedschaft:

Eine Anmerkung zur Sicherheit lokaler Passwörter, speziell des lokalen Administrators:

Hat ein Angreifer physikalischen Zugriff auf einen Windowsrechner (XP, Vista, Windows7, Server2008), so ist es mit den geeigneten Tools kinderleicht, in weniger als 3 Minuten das Passwort des Administrators auf ein beliebiges Passwort zurückzusetzen und sich damit Vollzugriff auf die Maschine zu verschaffen. Gleiches gilt für Domaincontroller.
Gegen diese "Aufsperr-" Tools hilft nur eine Verschlüsselung der Festplatte und physikalischer Zugriffsschutz.
Lange Passwörter oder umbenannte Administratorenkonten verzögern einen Angriffserfolg um keine einzige Zehntelsekunde!

 

1.2.2 LDAP-Provider

In diesem Kapitel 3 liegt der Schwerpunkt auf dem LDAP-Provider ansich.
In Kapitel 4 liegt dann der Schwerpunkt auf den Objekte im Activedirektory (User, Gruppen, OUs) und wie diese mit LDAP und Powershell erstellt, verändert oder gelöscht werden können.

 

1.2.2.1 LDAP Connection Strings

Unter Kapitel 3.1 wurde bereits kurz gezeigt, wie man ein Powershellobjekt auf ein LDAPObjekt im AD "bindet". Ohne große Erklärung habe ich dazu schon einen LDAP-Connectionstring in den Beispielen verwendet. In diesem Teilkapitel möchte ich kurz darstellen, aus welchen Komponenten ein solcher String bestehen muss und kann und welche Auswirkungen die Verwendung der teilweise optionalen Komponenten hat.

Allgemein sieht in einem Powershellskript ein vollständiger Connectionstring so aus:

[TypeAccelerator][Provider]://[FQDN oder CN eines DCs]:[Portnumber]/[DN des Objects]

Wobei [TypeAccelerator], [Provider] und [DN des Objects]  unbedingt notwendig und [FQDN oder CN eines DCs] oder [Portnumber] optional sind.

Beispiele für einen ConnectionString aus  MSDN: LDAP ADsPath

LDAP ADsPath example

Description

LDAP:

Bind to the root of the LDAP namespace.

LDAP://server01

Bind to a specific server.

LDAP://server01:390

Bind to a specific server using the specified port number.

LDAP://CN=Jeff Smith,CN=users,DC=fabrikam,DC=com

Bind to a specific object.

LDAP://server01/CN=Jeff Smith,CN=users,DC=fabrikam,DC=com

Bind to a specific object through a specific server.

Wieso im dritten Beispiel der Port auf 390 gesetzt ist, kann ich nicht sagen. Die normalen Portnummern sind natürlich 389 für normales LDAP, 636 für SecureLDAP und 3268, 3269 für GlobalCatalog Bindungen

Als Server benutze ich meistens den PDCEmulator der Domäne. Damit vermeidet man Replikationsfallen auch beim Entwickeln und Troubleshooten des Skripts.
Am besten ermittelt man den DomainDN und den PDCe am Anfang des Skripts, oder in einer eigenen Funktion, einmal dynamisch, was die System.DirectoryServices-Klasse leicht hergibt. Beide Variablen benötigt man in ActiveDirectory-Skripten recht häufg. 


Beispiel 1: dynamisches Ermitteln des distinguishedNames der Domäne, des PDCEmulators und des FQDNs der Domäne

Set-StrictMode -Version "2.0"
Clear-Host

$RootDSE = [ADSI]"LDAP://RootDSE"
$DomainDN = $($RootDSE.DefaultNamingContext)
"DN: {0}" -f $DomainDN

$CurrentDomain=[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$PDCOwner=$CurrentDomain.PdcRoleOwner.Name
"PDCOwner: {0}" -f $PDCOwner

$DomFQDN=[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Name
#$DomFQDN=$env:USERDNSDOMAIN #alternativ
"DomFQDN: $DomFQDN"
#mögliche Ausgabe

DN: DC=dom2,DC=intern
PDCOwner: dom2DC01.dom2.intern
DomFQDN: dom2.intern

oder als Funktion

Set-StrictMode -Version "2.0"
Clear-Host

Function getRootDSEandPDCe {
   $RootDSE = [ADSI]"LDAP://RootDSE"
   $fnDomainDN = $RootDSE.DefaultNamingContext
   $fnDomainDN

   $fnCurrentDomain=[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
   $fnPDCOwner=$fnCurrentDomain.PdcRoleOwner.Name
   $fnPDCOwner

   $fnDomFQDN=[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Name
   $fnDomFQDN
}

$Result=getRootDSEandPDCe

$DomainDN=$Result[0]
$PDCOwner=$Result[1]
$DomFQDN=$Result[2]

Den FQDN der Domäne habe ich zur Vollständigkeit dazugenommen, obwohl er erstmal nichts mit LDAP-Connectionstrings zu tun hat

Einige Spezialstrings beschreibe ich im folgenden Kapitel näher:

 

1.2.2.2 Connection auf die Domäne

Beispiel 1:Verbindung auf die Domäne

[ADSI]""
#alternativ: [ADSI]LDAP://<FQDN der Domäne>
#mögliche Ausgabe

distinguishedName : {DC=dom2,DC=intern}
Path  

 

Beispiel 2: Anzeigen von Domäneneigenschaften
Einige der DomäneneEigenschaften sind ganz interessant, am besten lasst ihr euch mit dem nächsten Zweizeiler diese mal komplett ausgeben.

$DomRoot=[ADSI]""
$DomRoot| Format-List *
#Ausgabe gekürzt

lockoutDuration                  : {System.__ComObject}
lockOutObservationWindow         : {System.__ComObject}
lockoutThreshold                 : {0}
maxPwdAge                        : {System.__ComObject}
minPwdAge                        : {System.__ComObject}
minPwdLength                     : {7}

Unter dem Kapitel "Grundlagen - Automation Interfaces - COM -> 2 System.__ComObject" habe ich beschrieben, wie man einen Teil dieser 8-bit LargeIntegerWerte trotzdem lesen kann.Etwas mühsam ist es, dass diverse Eigenschaften nicht als lesbare Strings oder Integerwerte, sondern in verschiedenen Formaten, wie 8 Byte Integerwerten (LargeIntegerValues) gespeichert sind, auf die man mit Powershell nur über einen Umweg Zugriff hat

 

1.2.2.3 Connection auf RootDSE

RootDSE ist ein Standard X.500 Objekt, auf das recht einfach zugegriffen werden kann, um Attribute mit Informationen des Directories zu erhalten.

 

Beispiel 1: Zugriff auf RootDSE

[ADSI]"LDAP://RootDSE" | Format-List *  #Serverless binding
#oder mit Angabe eines DCs
[ADSI ]"LDAP://DC1.Dom1.intern/RootDSE " | Format-List*

Die beste Definition der RootDSE habe ich hier MSDN: RootDSE gefunden:
"In LDAP 3.0, rootDSE is defined as the root of the directory data tree on a directory server. The rootDSE is not part of any namespace. The purpose of the rootDSE is to provide data about the directory server. For more information about rootDSE, see  Serverless Binding and RootDSE in the Active Directory SDK documentation."
Zusätzlich werden hier alle Attribute des RootDSE aufgelistet

Abfragen über das verwendete Domänen- und Forestlevel können hilfreich sein, um potentielle Scriptfehler durch zu hoch oder zu niedrig eingestellte Levels abzufangen.
Den defaultNamingContext verwendet man, um sein Script möglichst flexibel in verschiedenen Umgebungen einsetzen zu können
MSDN: Serverless Binding and RootDSE

Aus Gründen der Ausfallsicherheit sollte bei produktive eingesetzten Skripten möglichst kein fester Server (=serverless) für die LDAP-Bindung  verwendet werden. Andererseits ist für das Troubleshooting mittels Netzwerkanalyse bequemer, wenn man genau den Domaincontroller ansteuert, auf dem Tools wie Netmon oder Wireshark laufen.


Beispiel 2a: Ermitteln einiger LDAP-Eigenschaften des RootDSE

Set-StrictMode -Version "2.0"
Clear-Host

$DSE=[ADSI]"LDAP://RootDSE"

#Ausgabe
$DSE.CurrentTime
$DSE.DefaultNamingContext
$DSE.DomainFunctionality
#mögliche Ausgabe

20121217221323.0Z
DC=dom2,DC=intern
5

Die weiteren Attribute findet man in dem bereits erwähnten Link MSDN: RootDSE

Möchte man mit der Zeit weiterarbeiten, so kann man den Zeitstring mit $DSE.substring(0,4) etc. nach Jahr, Datum und Uhrzeit aufsplitten und mit

Set-StrictMode -Version "2.0"
Clear-Host

$DSE=[ADSI]"LDAP://RootDSE"

#Ausgabe
$DSE.CurrentTime

$Year= $DSE.CurrentTime.SubString(0,4)
$Month =  $DSE.CurrentTime.SubString(4,2)
$Day =  $DSE.CurrentTime.SubString(6,2)

Get-Date "$Day,$Month,$Year"

in ein ZeitDatumsobject umwandeln. Man muss nur mit der von Windows und LDAP verwendeten Zeitzone [UTC] aufpassen, die eine Verschiebung der Zeit um eine Stunde im Winter und zwei Stunden im Sommer zur Folge hat.
 

Beispiel 2b: Zugriff auf RootDSE mit Get-ADRootDSE

Set-StrictMode -Version "2.0"
Clear-Host

$DSE = Get-ADRootDSE

$DSE.CurrentTime
$DSE.DefaultNamingContext
$DSE.DomainFunctionality
#mögliche Ausgabe

Montag, 17. Dezember 2012 23:20:36
DC=dom2,DC=intern
Windows2012Domain

Man sieht unter anderem an den unterschiedlichen Ausgabeformaten, dass hinter Get-ADRootDSE eine andere Klasse steht, als die in Beispiel 2a verwendete DirectoryEntry-Klasse.

 

1.2.2.4 Kerberos / NTLM Authentifizierung

Zur Authentifizierung an einer Domäne ist bereits seit Windows 2000 Kerberos das Standardprotokoll. NTLMv2 kann nach wievor genutzt werden, hat aber den ein- oder anderen Nachteil, wie zum Beispiel, dass es nicht ticketbasiert arbeitet und damit eine Authentifizierung bei jedem ResourcenZugriff übers Netz neu erfolgen muss.

Wenn nichts dagegen spricht, sollte man seine Skripte also so verfassen, dass diese Kerberos benutzen

 

Beispiel 1: Bindung an RootDSE über das Kerberosprotokoll
Will man sicher gehen, dass das Powershellskript Kerberos zur Domänenauthentifizierung benutzt, verwendet man den FQDN eines Domaincontrollers im LDAP-Pfad

Set-StrictMode -Version "2.0"
Clear-Host

$DcNameFQDN = "Dom2Dc01.dom2.intern"
$DcNameCN = "Dom2Dc01"

$DSE=[ADSI]"LDAP://$DcNameFQDN/RootDSE"
# oder wenn Windows den einfachen Namen zum FQDN ergänzt
$DSE=[ADSI]"LDAP://DCNameCN/RootDSE" 


Beispiel 2: Bindung an RootDSE über das NTLMProtokoll
Gibt man im Connectionstring keinen Server an, so sucht sich der Client einen zufälligen Domaincontroller über DNS-Mechanismen und authentifiziert sich am DC über NTLM. Ebenso erfolgt eine NTLM-Authentifizeirung, wenn nicht der Rechnername sondern die IPAdresse verwendet wird.

Set-StrictMode -Version "2.0"
Clear-Host

$DSE=[ADSI]"LDAP://RootDSE"
#oder
$DSE=[ADSI]"LDAP://192.168.1.2/RootDSE"

In einem Netzwerktrace, den man beispielsweise mit Wireshark oder dem MS-Netzwerkmonitor erstellen kann, sieht man den Effekt, wenn man die LDAP-Pakete "Bindrequest" und "Bindresponse" öffnet. Um LDAP-Pakete anzusehen, muss man im Netmon zuerst einen geeigneten Parser installieren, was in Kapitel 5.5.2 beschrieben ist.

Diesen Hintergrund zu kennen, ist beim Troubleshooting hilfreich: Eine Abfrage funktioniert mit IPAdresse einwandfrei, eine Abfrage auf den Servernamen schlägt hingegen fehl (oder umgekehrt). Meist wird auf ein DNS-Namensauflösungsproblem getippt, die Ursache kann aber auch in den unterschiedlichen Authentisierungsprotokollen liegen.

 

1.3 Useraccountcontrol / UserFlags

Im oberen Beispiel aus 3.2.1 zur Userneuanlage mit dem WinNT Provider habe ich gezeigt, wie man einige Usersettings (AccountDisabled, PasswordExpires) mit Powershellmitteln setzen kann.
Meist spriocht  man die Usereigenschaften im ActiveDirectory User genauso an wie lokale User auf einem Client. Jedoch über nutzt der WinNT-Provider die

sogenannten UserFlags, der  LDAP-Provider die Useraccountcontrols. Zum Setzen verwendet man oft hexadezimale Werte, was am Anfang etwas kryptisch erscheint.

Mit ein bischen Übung und Erfahrung erkennt man jedoch die Vorteile.

Die Ziffern einer Hexadezimalenzahl können im Gegensatz zu einer Dezimalzahl 16 (0 bis F) statt 10 (0 bis 9) Werten annehmen. In Powershell werden Hexzahlen durch das Prefix "0x" gekennzeichnet.

 

Beispiel 1: Umwandeln von HexZahlen in Dezimalzahlen und umgekehrt

$myFlag=0x10
$myFlag
#Ausgabe
16 #Dezimal

und umgekehrt

$myFlag=32
"{0:x}" -f $myFlag
#[convert]::tostring(32,16) 
#Ausgabe
20 #Hexadezimal

Der UserAccountcontrol Eigenschaft eines Users ist es vollkommen egal, ob er vom Programmierer mit dezimalen oder hexadezimalen Zahlen versorgt wird. Einem Flag in Hexschreibweise sieht man nur wesentlich leichter an, welche Kontstanten in ihm enthalten sind.

Die MSDN listet folgenden Werte auf, die sowohl für die Userflags Property des WinNT-Providers, wie auch der Useraccountcontrol Property des LDAP-Providers gelten.
MSDN: ADS_USER_FLAG_ENUM Enumeration

Konstante

Dez

Hex

ADS_UF_SCRIPT

1

 0x1

ADS_UF_ACCOUNTDISABLE

2

 0x2

ADS_UF_HOMEDIR_REQUIRED

8

 0x8

ADS_UF_LOCKOUT

16

 0x10

ADS_UF_PASSWD_NOTREQD

32

 0x20

ADS_UF_PASSWD_CANT_CHANGE

64

 0x40

ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED

128

 0x80

ADS_UF_TEMP_DUPLICATE_ACCOUNT

256

 0x100

ADS_UF_NORMAL_ACCOUNT

512

 0x200

ADS_UF_INTERDOMAIN_TRUST_ACCOUNT

2048

 0x800

ADS_UF_WORKSTATION_TRUST_ACCOUNT

4096

 0x1000

ADS_UF_SERVER_TRUST_ACCOUNT

8192

 0x2000

ADS_UF_DONT_EXPIRE_PASSWD

65536

 0x10000

ADS_UF_MNS_LOGON_ACCOUNT

131072

 0x20000

ADS_UF_SMARTCARD_REQUIRED

262144

 0x40000

ADS_UF_TRUSTED_FOR_DELEGATION

524288

 0x80000

ADS_UF_NOT_DELEGATED

1048576

 0x100000

ADS_UF_USE_DES_KEY_ONLY

2097152

 0x200000

ADS_UF_DONT_REQUIRE_PREAUTH

4194304

 0x400000

ADS_UF_PASSWORD_EXPIRED

8388608

 0x800000

ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION

16777216

 0x1000000

 

Beispiel 2: Setzen eines Usersettings über WinNT und LDAP

Alle User einer ActiveDirectoryGruppe sollen das Usersetting bekommen, ein Smartcard benutzen zu müssen. Bereits bestehende Usersettings wie Accountdisabled sollen nicht verändert werden.
Ohne Skript muss ein Administrator jeden User einzeln aufrufen und die Eigenschaft "User muss sich mit Smartcard anmelden" zusätzlich auswählen:

Der Lösungsweg besteht darin, den bestehenden Wert des Userflags- oder UserAccountControl Attributs auszulesen und -falls noch nicht vorhanden- den Value ADS_UF_SMARTCARD_REQUIRED 0x40000 hinzuzufügen. Was sich nach fehleranfälligen IF-Abfragen anhört, lässt sich mittels einem Booleanschen BinaryOR (-bor) sehr einfach lösen.

Die beiden nächsten Skripte arbeiten identisch. Beim WinNT Provider genügt die Angabe des SAMAccountnames zur Bindung an das Userobjekt, der LDAP-Provider verlangt den ausführlichen DistinguishedName. Dafür ist der LDAP-Provider schneller und bietet mehr Möglichkeiten, wie zum Beispiel das Verwenden von Credentials oder das Verwenden der Kerberosauthentifizierung.

 

Beispiel 2a: Setzen der Useraccountcontroll "Smartcard required" mit Hilfe des WinNT-Provider

Set-StrictMode -Version "2.0"
Clear-Host

#Definieren des Wertes der UAC (aus der MSDN)
$ADS_UF_SMARTCARD_REQUIRED = 0x40000

#Variablendefinition (in einer Domäne am besten den PDC-Emulator als Computernutzen)
$UserName="KarlNapf"
$ComputerName="Dom2DC01"

#Binden
$ADSIUser=[ADSI] "WinNT://$ComputerName/$UserName"

 
#Flags auslesen, verändern und neu setzen
$oldFlags = $ADSIUser.InvokeGet("UserFlags") #Eigenschaft des WinNT-Providers
$newFlags = $oldFlags -bor $ADS_UF_SMARTCARD_REQUIRED
$ADSIUser.Invokeset("UserFlags",$newFlags)

#zurückschreiben der Eigenschaften (ab PS 3.0 nicht mehr nötig)
$ADSIUser.SetInfo()

#Ausgabe
"Alter Useraccountwert: {0:x}" -f $oldFlags
"Neuer Useraccountwert: {0:x}" -f $newFlags
#Ausgabe

Alter Useraccountwert: 201
Neuer Useraccountwert: 40201

 

Beispiel 3b: Setzen der Useraccountcontroll "Smartcard required" mit Hilfe des LDAP-Provider

Set-StrictMode -Version "2.0"
Clear-Host

#Definieren des Wertes der UAC (aus der MSDN)
$ADS_UF_SMARTCARD_REQUIRED = 0x40000 # aus der MSDN

#Credentials
$CredentialUser="Dom2\Administrator"
$CredentialPW='Hurra123'

#Zielobjekt
  #in einer Domäne am besten den PDC-Emulator nutzen
  #der DNS-Name erlaubt eine Kerberosauthentifizierung
$ComputerName="Dom2Dc01.Dom2.Intern"
$UserDN = "CN=KarlNapf,OU=Scripting,DC=dom2,DC=intern"

#Binden mit Credentials
$ADSIUser=[ADSI] "LDAP://$ComputerName/$UserDN"
$ADSIUser.Username=$CredentialUser
$ADSIUser.Password=$CredentialPW

#UAC auslesen, verändern und neu setzen
$oldUAC = $ADSIUser.InvokeGet("UserAccountControl") #Eigenschaft des LDAP-Providers
$newUAC = $oldFlags -bor $ADS_UF_SMARTCARD_REQUIRED
$ADSIUser.Invokeset("UserAccountControl",$newUAC)

#zurückschreiben der Eigenschaften (ab PS 3.0 nicht mehr nötig)
$ADSIUser.SetInfo()

#Ausgabe
"Alter UAC: {0:x}" -f $oldUAC
"Neuer UAC: {0:x}" -f $newUAC
#Ausgabe bei beiden Providern

Alter Useraccountwert: 10241
Neuer Useraccountwert: 50241


Die Bedeutung dieser Hex-Werte lässt sich anhand der MSDN-Tabelle zu Beginn dieses Kapitels 3.3 leicht entschlüsseln.

 

Beispiel 4a: Ermitteln aller User eines Directories, deren Account gesperrt ist und die sich gleichzeitig das Smartcardflag gesetzt haben

In diesem Beispiel wird gezeigt, wie man mit einem BinaryAnd "-band" sehr einfach ein oder mehrere Usersettings auslesen kann.

Set-StrictMode -Version "2.0"
Clear-Host

$ADS_UF_SMARTCARD_REQUIRED = 0x40000
$ADS_UF_ACCOUNTDISABLE = 0x2

$Filter = "objectClass=user"
([ADSISearcher]$Filter).Findall() | Foreach-Object {
 $UAC = ([ADSI]$_.Path).Invokeget("UserAccountControl")
 if(($UAC -band $ADS_UF_ACCOUNTDISABLE) -and ($UAC -band $ADS_UF_SMARTCARD_REQUIRED)){
  Write "$($_.Properties.Item("DistinguishedName")) ist gesperrt und Smartcardanmeldung ist erforderlich"
 }
}
#Ausgabe
CN=User1,OU=User,OU=scripting,DC=Dom1,DC=intern ist gesperrt und Smartcardanmeldung ist erfoderlich

Die entscheidende Zeile des Skripts oben ist:

if(($UAC -band 0x2) -and ($UAC -band $ADS_UF_SMARTCARD_REQUIRED)){

die bestehende Eigenschaft "UserAccountControl" eines jeden Benutzers wird sowohl mit dem HexWert 2 und dem HexWert 40000 binär verglichen. Natürlich kann man je nach Vorliebe ebenso Dezimalzahlen verwenden und statt der Variablen $ADS_UF_SMARTCARD_REQUIRED auch den Dezimalwert 262144 in dieser Zeile schreiben.

Anmerkung 1: Auf den [ADSISearcher] gehe ich weiter unten in Kapitel 7 Queries noch tiefer ein.

Anmerkung 2: Anstelle der Booleschen Operatoren -band und -bor wird oft mit dem LDAPControl LDAP_MATCHING_RULE_BIT_AND 1.2.840.113556.1.4.803 und LDAP_MATCHING_RULE_BIT_OR 1.2.840.113556.1.4.804 gearbeitet. Siehe dazu Kapitel 7.2.2.3.4 LDAP Controls

 

Beispiel 4b: Ermitteln aller User einer OU mit PasswordNeverExpires

Set-StrictMode -Version "2.0"
Clear-Host

Function Main{

  $OuDn = "DC=myOU,DC=Dom1,DC=net"
  $Users = GetUsersbyUAC $OUDn
  $Users
  }

Function GetUsersbyUAC{
  Param ($OUDn)

  $myUsers = @()
  #UserAccountControl
  $ADS_UF_NeverExpires = 0x10000
  $ADS_UF_SMARTCARD_REQUIRED = 0x40000
  $ADS_UF_ACCOUNTDISABLE = 0x2
    
  $SearchScope = "SubTree"
  $DirectorySearcher = ([ADSISearcher]"LDAP://$OUDn")
  $DirectorySearcher.Filter="(&(ObjectCategory=User)(Objectclass=User))"
  $DirectorySearcher.SearchScope= $SearchScope
  $DirectorySearcher.SearchRoot="LDAP://$OuDn"
  $DirectorySearcher.PageSize=1000  #normally not to change

  $DirectorySearcher.FindAll() | Foreach-Object {
  $UAC = ([ADSI]$_.Path).Invokeget("UserAccountControl")
# if(($UAC -band $ADS_UF_ACCOUNTDISABLE) -and ($UAC -band $ADS_UF_SMARTCARD_REQUIRED)){
  if($UAC -band $ADS_UF_NeverExpires) {
    $myUsers += $($_.Properties.Item("DistinguishedName"))
   }#if
  }#Foreach
  Return ,$myUsers
} #end Function
 
Beispiel 5: Ermitteln aller UAC-Usersettings + GUID
Set-StrictMode -Version "2.0"
Clear-Host

Function ConvertADSLargeInteger([object] $adsLargeInteger)
{
  $highPart = $adsLargeInteger.GetType().InvokeMember("HighPart", [System.Reflection.BindingFlags]::GetProperty, $null, $adsLargeInteger, $null)
     $lowPart  = $adsLargeInteger.GetType().InvokeMember("LowPart",  [System.Reflection.BindingFlags]::GetProperty, $null, $adsLargeInteger, $null)

  $bytes = [System.BitConverter]::GetBytes($highPart)
  $tmp   = [System.Byte[]]@(0,0,0,0,0,0,0,0)
  [System.Array]::Copy($bytes, 0, $tmp, 4, 4)
  $highPart = [System.BitConverter]::ToInt64($tmp, 0)
  $bytes = [System.BitConverter]::GetBytes($lowPart)
  $lowPart = [System.BitConverter]::ToUInt32($bytes, 0)
  return $lowPart + $highPart
}

#allgemeine Informationen bestimmen
$DomainDN = ([ADSI]"LDAP://rootDSE").defaultNamingContext
$Domain=[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$PDCe=$Domain.PdcRoleOwner.Name
Try{
    $ThisScriptPath = Split-Path $($MyInvocation.InvocationName) -Parent
   }Catch{
     Write-Host "Skript vor dem ersten Einsatz bitte abspeichern!" -BackGroundColor Red
     Break
   }

#in diesem Block das Skript anpassen und die Variablen wie gewünscht setzen (Erklärung unten)
$OuDn = "OU=BenutzerA,OU=Scripting,$DomainDN"
$OuDn = "$DomainDN"
$SearchScope = "SubTree"
$ADProperties = @("SamAccountName","ObjectGuid","UserAccountControl")
$UACs = @("0x1","0x2","0x8","0x10","0x20","0x40","0x80","0x100","0x200","0x800","0x1000","0x2000",`
       "0x10000","0x20000","0x40000","0x80000","0x100000","0x200000","0x400000","0x800000","0x1000000")

$MyOFS = "#"
$SortedBy = "SamAccountName ASC"
$Filter = ""
$CsvExportPath = "$ThisScriptPath\ExportUser.csv"

$Properties = $ADProperties + $UACs

#Userobjekte in das Array $Users einlesen
$Users=@()
$DirectorySearcher=([ADSISearcher]"LDAP://$PDCe")
$DirectorySearcher.Filter="(&(ObjectCategory=User)(Objectclass=User))"
$DirectorySearcher.SearchScope= $SearchScope
$DirectorySearcher.SearchRoot="LDAP://$PDCe/$OuDn"
$DirectorySearcher.PageSize=1000  #normalerweise nicht verändern!
$DirectorySearcher.FindAll() | ForEach{
   $Users += [ADSI]($_.path)
   }  

#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)
  }
 
#Befüllen der DataTable mit Daten
ForEach ($User in $Users){
  $PropertiesInRow =@()
  Foreach($Property in $Properties) {
    $OFS=$MyOFS
     #$($User.InvokeGet($Property)).ToString()
    Try{
    
     If($Property -Match "0x*"){
       $UAC = $User.Invokeget("UserAccountControl")
          if($UAC -band $Property){
             $PropertiesInRow += "1"
          }else{
             $PropertiesInRow += "0"
          }
       }
    
     If($Property -eq "ObjectGuid"){
        $PropertiesInRow += [system.guid]$($User.ObjectGuid)
     }   
     ElseIf($($User.InvokeGet($Property).ToString()) -eq "System.__ComObject"){  #Datumswerten
       $Date = $([DateTime]::FromFileTimeUtc((ConvertADSLargeInteger $User.InvokeGet($Property))))
       $PropertiesInRow += $Date.ToString("d")
       
           }#if
     ElseIf($($User.InvokeGet($Property)) -eq $null){  #nicht existente Property
        $PropertiesInRow += ''
        
     }#ElseIf
     ElseIf($Property -eq "UserAccountControl"){
       $PropertiesInRow += $("0x{0:x}" -f $($User.InvokeGet($Property)) )
        }
     
       
     Else{
       Try{  #Strings, die Datumswerte enthalten
          $PropertiesInRow += $($User.InvokeGet($Property)).ToString("d")
          }
          Catch{
          $PropertiesInRow += $($User.InvokeGet($Property))
          }
     }#Else
   }#Try
     Catch{
     
        if ($Property -notmatch "0x*"){  #für leere Properties. UACs sind keine Properties
           $PropertiesInRow += ''
        }
     } #Notwendig für die TS-Eigenschaften
       
     $OFS="+"  #Zurücksetzen auf Default
   }#Foreach($Property in $Properties)
   $DataTable.Rows.Add($PropertiesInRow) | Out-Null
}#Foreach ($User in $Users)
 
#DataView erzeugen
$DataView = New-Object System.Data.DataView($DataTable)
#Sortieren
$DataView.Sort = "$SortedBy"
#Filtern
$DataView.RowFilter = $Filter

#Ausgabe
$TableFormat = "SamAccountName","ObjectGuid","UserAccountControl",`
  "0x1","0x2","0x8","0x10","0x20","0x40","0x80","0x100","0x200","0x800","0x1000",`
   "0x2000","0x10000","0x20000","0x40000","0x80000","0x100000","0x200000","0x400000","0x800000","0x1000000"
$DataView | Format-Table $TableFormat -auto
if([string]::isNullOrEmpty($CsvExportPath) -ne $true){  #CSV-Datei
  $DataView | Export-Csv -path $CsvExportPath -Delimiter ";"
  Write-Host "Die Datei $CsvExportPath wurde erfolgreich erstellt" -ForeGroundColor Red

  }#if
#mögliche Ausgabe (gekürzt in der Breite)

SamAccountName ObjectGuid                           UserAccountControl 0x1 0x2 0x8 0x10 0x20 0x40 0x80 0x100 0x200 0x800 0x1000 0x2000 0x10000 ...
-------------- ----------                           ------------------ --- --- --- ---- ---- ---- ---- ----- ----- ----- ------ ------ ------- ...
Napf00        1207dc8e-f27a-4257-9c18-8841d52f288a 0x50220            0   0   0   0    1    0    0    0     1     0     0      0      1      ...
Napf01        cedf79be-a0d4-4d01-bb63-2ccf74cb9c93 0x40220            0   0   0   0    1    0    0    0     1     0     0      0      0      ...
Napf02        6f543563-c435-46c4-9303-504bbd83f170 0x1402a2           0   1   0   0    1    0    1    0     1     0     0      0      0      ...  
Napf03        2cea7dfa-584d-4bd4-bd36-e24bdcf533c5 0x40220            0   0   0   0    1    0    0    0     1     0     0      0      0      ...
Napf04        75657cbe-07fd-418d-9d41-b0b5c94f8c63 0x40220            0   0   0   0    1    0    0    0     1     0     0      0      0      ...  
Napf05        25576945-7d21-427b-8ce4-a8b598a052dd 0x40222            0   1   0   0    1    0    0    0     1     0     0      0      0      ...
.

 
Die Datei C:\Users\SuperUser\Documents\ExportUser.csv wurde erfolgreich erstellt

 

1.4 Userverwaltung mit Net-* Befehlen

Wer von Euch schon zu NT4.0 Zeiten im Microsoft Umfeld administrativ tätig war, wird sich sicher an die Verwaltungstools der damaligen Zeit erinnern, wie beispielsweise "Net User" oder "AddUsers" um nur zwei zu nennen. Auch heute noch kann man diesen Tools sehr einfach einige Aufgaben erledigen.

Beispiel 1: Anlegen eines lokalen Users
Für diesen Befehl werden natürlich Adminrechte benötigt und Powershell muss "als Administrator" gestartet sein

Clear-Host
Set-StrictMode -Version "2.0"

Invoke-Command {net user myAdmin2 Hurra /add}
Invoke-Command {net user} 
#mögliche Ausgabe

Benutzerkonten fr \\ACERNB

---------------------------------------------------------------------
Administrator            Gast                     KaiADM  
Kaiusr                   myTest                   testadmin
UpdatusUser     
Der Befehl wurde erfolgreich ausgefhrt.

Technet: Invoke-Command
Technet: Verwenden des Net User-Befehls

Auch dieser Variante funktioniert noch einwandfrei. Und wenn ihr die entsprechenden Voraussetzungen für Remote-Powershell geschaffen habt, könnt ihr den Parameter "Computername" von Invoke-command einsetzen, um auch Remote User und Gruppen zu verwalten.
Da ich aber doch empfehle, solche Aufgaben mit LDAP oder WinNT zu erledigen, gehe ich auf diese Art der Administration nicht weiter ein.