DPXone
Lieutenant
- Registriert
- Mai 2009
- Beiträge
- 554
Hi,
hier mal eine Funktion (kann als Script und Module eingebunden werden), das ich damals aus eigener Initiative heraus auf die schnelle geschrieben habe.
Mit dieser Funktion lassen sich AD-User-Accounts im gesamten Active Directory Forest finden (nicht nur in der aktuellen Domäne).
Profitiere ich von dieser Funktion?
Diese Funktion richtet sich aufgrund der Forest-Suche im Global Catalog hauptsächlich erstmal nur an Multi-Domain-Forests.
Aufgrund der Switches, die die Funktion bietet, sollte es sich aber auch verinzelt für Single-Domain(-Forests)-Suchen eignen.
Diese Funktion ist, so gut es bisher möglich war, auf Performance ausgerichtet.
Deshalb gibt es u.a. keine Pipelines.
Alles wird über GenericList's, ForEach-Statement Keywords (NICHT: ForEach-Object alias %) und Hashtable's abgearbeitet.
Nach was kann man suchen, ohne das AD(/LDAP)-Attribute anzugeben?
Welche Beispiele gibt es?
... folgt.
Bis dahin:
Im Multi-Domain-Forest mit dem Script einfach mal nach einem User anhand des Usernames oder der Mail-Adresse suchen, der nicht in der eigenen Domäne existiert.
Welche Einschränkungen gibt es?
Die größte Einschränkung liegt im "Global Catalog".
Mein Script fragt standardmäßg nur den Global Catalog ab!.
Ausnahme bildet der Switch -QueryCorrespondingDC
Mein Script kann, per Default, nur das finden, was im globalen Katalog (Global Catalog = GC) vorhanden ist!
Aber deshalb bitte nicht zu viele Eigenschaften durch den Forest replizieren!!!!.
Das produziert andere Probleme.
Minimaler Exkurs der Nachteile vom Global Catalog im Generellen:
Um für jeden User wirklich alle Details halbwegs abzufragen, gibt es den Switch $QueryCorrespondingDC.
Dadurch wird jeder User, am jeweils verantwortlichen DC per Domain, abgefragt, was bei größeren Abfragen, in größeren Forests, seine Zeit in Anspruch nimmt.
Was bringt mir das Script (in Forests/mehreren Domänen)?
Man kann auf leichte Art und Weise nach User-Accounts suchen anhand der/s UPN, GUID, SID, DisplayNames/CN, Mail-Address, etc... suchen und
bekommt zusätzlich auch die Info, in welcher Domäne sich der User-Account befindet und welchen DC man für detaillierte Informationen abfragen sollte.
Beispiele
Hinweise zu den Parametern [Nicht vollständig und richtig, da in Überarbeitung]:
Updates:
hier mal eine Funktion (kann als Script und Module eingebunden werden), das ich damals aus eigener Initiative heraus auf die schnelle geschrieben habe.
Mit dieser Funktion lassen sich AD-User-Accounts im gesamten Active Directory Forest finden (nicht nur in der aktuellen Domäne).
Profitiere ich von dieser Funktion?
Diese Funktion richtet sich aufgrund der Forest-Suche im Global Catalog hauptsächlich erstmal nur an Multi-Domain-Forests.
Aufgrund der Switches, die die Funktion bietet, sollte es sich aber auch verinzelt für Single-Domain(-Forests)-Suchen eignen.
Diese Funktion ist, so gut es bisher möglich war, auf Performance ausgerichtet.
Deshalb gibt es u.a. keine Pipelines.
Alles wird über GenericList's, ForEach-Statement Keywords (NICHT: ForEach-Object alias %) und Hashtable's abgearbeitet.
Nach was kann man suchen, ohne das AD(/LDAP)-Attribute anzugeben?
- CommonName (CN; i.d.R. auch der DisplayName; Kann aber auch anders sein!) (Wildcard * erlaubt)
- SamAccountName: (=Username ohne Domain, z.B. abcEXF1) (Wildcard * erlaubt)
- UserPrincipalName:(Username in Kombination mit der Domäne, z. B. MyUsernName@MyDomain.i) (Wildcard * erlaubt)
- Surname (e.g. Nachname der Person) (Wildcard * erlaubt)
- GivenName (e.g. Vorname der Person) (Wildcard * erlaubt)
- Mail (e.g. Nachname der Person) (Wildcard * erlaubt)
- SID (Kein Wildcard erlaubt!)
- GUID (Kein Wildcard erlaubt!)
- DistinguishedName (Kein Wildcard erlaubt!)
Welche Beispiele gibt es?
... folgt.
Bis dahin:
Im Multi-Domain-Forest mit dem Script einfach mal nach einem User anhand des Usernames oder der Mail-Adresse suchen, der nicht in der eigenen Domäne existiert.
Welche Einschränkungen gibt es?
Die größte Einschränkung liegt im "Global Catalog".
Mein Script fragt standardmäßg nur den Global Catalog ab!.
Ausnahme bildet der Switch -QueryCorrespondingDC
Mein Script kann, per Default, nur das finden, was im globalen Katalog (Global Catalog = GC) vorhanden ist!
Aber deshalb bitte nicht zu viele Eigenschaften durch den Forest replizieren!!!!.
Das produziert andere Probleme.
Minimaler Exkurs der Nachteile vom Global Catalog im Generellen:
- Gruppenmitgliedschaften, außer die von universellen Gruppen, werden nicht gesynct und sind im GC deshalb nicht abrufbar (betrifft diese Funktion nicht!)
- fehlgeschlagene Anmeldeversuch sind am GC NICHT abrufbar
- Anzahl der fehlgeschlagenen Anmeldeversuche sind am GC NICHT abrufbar
- der TimeStamp der letzten Anmeldung ist am GC NICHT abrufbar
- ....... und alles andere, was nicht in den GC gesynct wird.
Um für jeden User wirklich alle Details halbwegs abzufragen, gibt es den Switch $QueryCorrespondingDC.
Dadurch wird jeder User, am jeweils verantwortlichen DC per Domain, abgefragt, was bei größeren Abfragen, in größeren Forests, seine Zeit in Anspruch nimmt.
Was bringt mir das Script (in Forests/mehreren Domänen)?
Man kann auf leichte Art und Weise nach User-Accounts suchen anhand der/s UPN, GUID, SID, DisplayNames/CN, Mail-Address, etc... suchen und
bekommt zusätzlich auch die Info, in welcher Domäne sich der User-Account befindet und welchen DC man für detaillierte Informationen abfragen sollte.
Beispiele
- Find-AdUser MyLastName
- Find-AdUser han*
- Find-AdUser "XXX*@MyDomain.i"
- Find-Aduser "MyFisrtName.MyLastName@MyDomain.MyTLD"
- Find-AdUser [SID]
- Find-AdUser [objectGUID] -QueryCorrespondingDC
- Find-AdUser *@hanswurst.de
- Find-AdUser "MyFirstName MyLastName (MyCompanyName)"
- Find-AdUser "MyFirstName MyLastname *"
- "Hans*","Peter*",Gustav*" | Find-AdUser -QueryCorrespondingDC
Hinweise zu den Parametern [Nicht vollständig und richtig, da in Überarbeitung]:
- $UserState
Filter auf den Status des AD-User-Accounts (Enabled oder Disabled)
Default = EGAL
Mit "TRUE" kann man das Ergebnis auf aktive AD-Accounts beschränken. - $AddToDefaultProperties
Damit lassen sich zusätzliche AD-Properties zum Ergebnis hinzufügen.
Standardmäßig werden folgende Eigenschaften abgefragt und ausgegeben:
'DisplayName' , 'CN' , 'SamAccountName' , 'Surname' , 'GivenName' , 'company' , 'mail' , 'Enabled' , 'UserPrincipalName' , 'SID' , 'ObjectGUID - $QueryCorrespondingDC
Fragt für die Eigenschaften des AD-Accounts, den verantwortlichen Domain-Controller, der jeweiligen Domäne ab (nur in Forests/mehreren Domänen sinnvoll).
PowerShell:
Function Find-ADUser {
<#
.Synopsis
.DESCRIPTION
- The variables $CachedDomainsHashT and $CachedDCsHashT are created in global scope.
Therefore these variables are available for all subsequent calls and other scripts.
.EXAMPLE
.EXAMPLE
.NOTES
#>
[CmdletBinding(DefaultParameterSetName = 'Properties')]
Param (
[Alias("N")]
[Parameter(Mandatory = $true , Position = 0 , ValueFromPipeline = $true , ValueFromRemainingArguments = $true)]
[string[]] $Name ,
[Alias("US")]
[ValidateSet($True , $False)]
[String] $UserState = $null ,
[Alias("P")]
[Parameter(ParameterSetName = 'Properties')]
[System.Collections.Generic.List[string]] $Properties = ('DisplayName' , 'CN' , 'SamAccountName' , 'Surname' , 'GivenName' , 'company' , 'mail' , 'Enabled' , 'UserPrincipalName' , 'SID' , 'ObjectGUID') ,
[Alias("AP")]
[Parameter(ParameterSetName = 'AddToDefaultProperties')]
[System.Collections.Generic.List[string]] $AddToDefaultProperties ,
[Alias("P2k")]
[switch] $ShowPreWindows2000Name ,
[Alias("DC")]
[switch] $ShowDC ,
[Alias("Q")]
[switch] $QueryCorrespondingDC , # not GlobalCatalog
[Alias("FD")]
[switch] $ForceDcDiscovery # force domain controller discovery (clears cached domain controller information)
)
Begin {
If (-not $PSCmdlet.MyInvocation.ExpectingInput) { # If not Pipeline-Input
[string] $Name = $name -join ' '
}
$Properties.AddRange([System.Collections.Generic.List[String]]('CanonicalName' , 'DistinguishedName'))
$Properties = $Properties | Sort-Object -Unique
If (-not($AddToDefaultProperties -eq $null)) { $Properties.AddRange($AddToDefaultProperties) }
$GlobalCatalogDC = Get-ADDomainController -NextClosestSite -Discover -ForceDiscover -Service PrimaryDC
$ServerName = $GlobalCatalogDC.HostName
$port = 3268 # Global Catalog
$userList = [System.Collections.Generic.List[psobject]]::new()
If ($global:CachedDCsHashT -isnot [hashtable]) {
$global:CachedDCsHashT = @{ } # cache DCs in global scope for subsequent queries
}
If ($global:CachedDomainsHashT -isnot [hashtable]) {
$global:CachedDomainsHashT = @{ } # cache DCs in global scope for subsequent queries
}
If ($UserState -notin '' , $null) {
$Filter = {((DistinguishedName -eq $N) -or (ObjectGUID -eq $N) -or (UserPrincipalName -like $N) -or (sAMAccountName -like $N) -or (mail -like $N) -or (givenname -like $N) -or (sn -like $N) -or (Displayname -like $N) -or (cn -like $N)) -and (Enabled -eq $UserState) }
} Else {
$Filter = {((DistinguishedName -eq $N) -or (ObjectGUID -eq $N) -or (UserPrincipalName -like $N) -or (sAMAccountName -like $N) -or (mail -like $N) -or (givenname -like $N) -or (sn -like $N) -or (Displayname -like $N) -or (cn -like $N)) }
}
$QueryParameter = @{
Filter = $Filter
Server = "$ServerName`:$port"
}
}
Process {
Foreach ($N In $Name) {
$users = Get-ADUser @QueryParameter -Properties $Properties
$UniqueDomainsHashT = @{ }
$CanonicalNames = $users.CanonicalName
Foreach ($CanonicalName In $CanonicalNames) {
$DomainName = ($CanonicalName -split '/')[0]
If (! $UniqueDomainsHashT.ContainsKey($DomainName)) {
$UniqueDomainsHashT[$DomainName] = ''
}
}
$UniqueDomains = $UniqueDomainsHashT.Keys
If ($ShowPreWindows2000Name) {
If ($global:CachedDomainsHashT -notin '' , $null) { # caching domains avoids unnecessary subsequent queries for the same domain
$domainsDiff = Compare-Object -ReferenceObject $global:CachedDomainsHashT.Keys -DifferenceObject $UniqueDomains # hashtable key is domain name
$DomainsToQuery = Foreach ($diff In $domainsDiff) {
If ($diff.Sideindicator -eq '=>') {
$diff.InputObject
}
}
} Else {
$DomainsToQuery = $UniqueDomains
}
If ($DomainsToQuery -notin '' , $null) {
Foreach ($DomainName In $DomainsToQuery) {
$Domain = Get-ADDomain -Identity $DomainName
$global:CachedDomainsHashT[$DomainName] = $Domain
}
}
}
If ($ShowDC -or $QueryCorrespondingDC) {
If ($global:CachedDCsHashT -notin '' , $null) { # caching DCs of domains avoids unnecessary subsequent queries for the same domain
$dcDiff = Compare-Object -ReferenceObject $global:CachedDCsHashT.Keys -DifferenceObject $UniqueDomains # hashtable key is domain name
$DomainsToQueryForDCs = Foreach ($diff In $dcDiff) {
If ($diff.Sideindicator -eq '=>') {
$diff.InputObject
}
}
} Else {
$DomainsToQueryForDCs = $UniqueDomains
}
If ($DomainsToQueryForDCs -notin '' , $null) {
Foreach ($DomainName In $DomainsToQueryForDCs) {
$DC = Get-ADDomainController -DomainName $DomainName -Discover -ForceDiscover: $ForceDcDiscovery
$global:CachedDCsHashT[$DomainName] = $DC
}
}
If ($QueryCorrespondingDC) {
$users = Foreach ($user In $Users) {
$DomainName = ($user.CanonicalName -split '/')[0]
$DC = $global:CachedDCsHashT[$DomainName].Hostname[0]
Get-ADUser $user.DistinguishedName -Server $DC -Properties $Properties
}
}
}
Foreach ($user In $users) {
$DomainName = ($user.CanonicalName -split '/')[0]
Add-Member -InputObject $user -MemberType NoteProperty -Name 'Domain' -Value $DomainName -Force
If ($ShowPreWindows2000Name) {
$DomainNetBiosName = $global:CachedDomainsHashT[$DomainName].NetBiosName
$PreWindows2000Name = "$($DomainNetBiosName)\$($User.SamAccountName)"
Add-Member -InputObject $user -MemberType NoteProperty -Name 'preWindows2000Name' -Value $PreWindows2000Name -Force
}
If ($ShowDC -or $QueryCorrespondingDC) {
$DcHostName = $global:CachedDCsHashT[$DomainName].Hostname[0]
Add-Member -InputObject $user -MemberType NoteProperty -Name 'DomainController' -Value $DcHostName -Force
}
$userList.Add($user)
}
}
}
End {
If ($userList) {
$FinalPropertiesHashT = @{ }
Foreach ($prop In $Properties) {
If ($prop -notmatch '\*') {
If (! $FinalPropertiesHashT.ContainsKey($prop)) {
$FinalPropertiesHashT[$prop] = $null
}
}
}
Foreach ($item In $userList) {
$itemProps = (Get-Member -InputObject $item -MemberType Properties).Name
Foreach ($itemProp In $itemProps) {
If ($itemProp -notin @('PropertyNames' , 'AddedProperties' , 'RemovedProperties' , 'ModifiedProperties' , 'PropertyCount')) {
If (! $FinalPropertiesHashT.ContainsKey($itemProp)) {
$FinalPropertiesHashT[$itemProp] = $null
}
}
}
}
$FinalPropertiesName = $FinalPropertiesHashT.Keys | Sort-Object
Foreach ($item In $userList) {
Select-Object -InputObject $item -Property $FinalPropertiesName
}
}
}
}
Updates:
- 2018-03-29:
- Performance-Verbesserungen durch Verringerung der Domains- und DC-Abfragen mithilfe von zwischengespeicherten Hash-Tables.
- Switch ($ShowPreWindows2000Name) für Ausgabe des Usernames im PreWin2000-Format hinzugefügt.
- Diverse Anpassungen für Performance-Verbesserungen
- 2019-02-05:
- Überarbeitung der Paramter
- Diverse Performance-Verbesserungen
Zuletzt bearbeitet: