Keresés az AD-ben

A következő gyakori feladat a keresés az AD-ben. Az első módszer a Find metódus használata, ami szintén a PSBase nézetben érhető el:

PS C:\> $ou = [ADSI] "LDAP://ou=Demó,dc=iqjb,dc=w08"

PS C:\> $ou.psbase.children.find("cn=Csilla Fájdalom")

 

distinguishedName

-----------------

{CN=Csilla Fájdalom,OU=Demó,DC=iqjb,DC=w08}

Ez a keresés azonban csak az adott konténerobjektum gyerekei között keres, így nem biztos, hogy igazán jó segítség ez nekünk

Az igazán profi keresőhöz a .NET keretrendszer egyik osztályát, a System.DirectoryServices.DirectorySearcher -t hívjuk segítségül, ennek egy objektuma lesz a keresőnk, és ennek különböző tulajdonságait beállítva adjuk meg a keresésünk mindenféle feltételét. Elsőként nézzünk egy nagyon egyszerű feladatot, egy konkrét felhasználói fiókra keressünk rá:

[6] PS I:\>$objRoot = [ADSI] "LDAP://DC=IQJB,DC=dom"

[7] PS I:\>$objSearcher = New-Object System.DirectoryServices.DirectorySearc

her

[8] PS I:\>$objSearcher.SearchRoot = $objRoot

[9] PS I:\>$objSearcher.Filter = "(&(objectCategory=user)(displayName=Soós Tibor))"

[10] PS I:\>$objSearcher.SearchScope = "Subtree"

[11] PS I:\>$colResults = $objSearcher.FindAll()

[12] PS I:\>$colresults

 

Path                                  Properties

----                                  ----------

LDAP://CN=Soós Tibor,OU=Normal,OU=... {homemdb, distinguishedname, count...

Elsőként definiálom, hogy az AD adatbázis-elemek fastruktúrájában, hol is keresek majd ($objRoot). Majd elkészítem a keresőt ($objSearcher), aminek SearchRoot tulajdonságaként az előbb létrehozott keresési helyet adom meg. Majd definiálom az LDAP formátumú szűrőt, amely ebben az esetben a „Soós Tibor” nevű felhasználókat jelenti, és ezt betöltöm a kereső Filter tulajdonságaként. LDAP szűrőben a következő összehasonlító operátorokat használhatok:

Operátor

Jelentés

=

Egyenlő

~=

Közel egyenlő

<=

Kisebb egyenlő

>=

Nagyobb egyenlő

&

És

|

Vagy

!

Nem

Végül meghatározom a keresés mélységét, ami itt Subtree, azaz mélységi, mert nem pont közvetlenül a kiindulópontként megadott helyen van a keresett objektum. Nincs más hátra, ezek alapján ki kell listázni a feltételeknek megfelelő objektumokat a FindAll metódussal.

A $colResult változóban tárolt eredmény nem közvetlenül DirectoryEntry típusú elemek tömbje! Hanem tulajdonképpen egy hashtábla-szerűség, ahol a Path oszlop tartalmazza a megtalált objektum LDAP formátumú elérési útját, a Properties meg a kiolvasható tulajdonságait. Azaz ahhoz, hogy kiolvassuk például az én nevemet és beosztásomat egy kicsit trükközni kell:

[25] PS I:\>"$($colResults[0].properties.displayname) az én nevem, beoszt

ásom $($colResults[0].properties.title)"

Soós Tibor az én nevem, beosztásom műszaki igazgató

Megjegyzés

A PowerShell ténykedésem során ez a második eset, amikor kis-nagybetű érzékenységet tapasztaltam! (Az első az LDAP:: kifejezésnél volt, de ez félig-meddig betudható az ADSI örökségnek.) A második ez: ha $colResults[0].properties.displayName-et írok (nagy „N” az utolsó tagban), akkor nem kapok semmit. Ez azért is furcsa, mert eredetileg a címtárban nagy az „N”.

A következő példában kikeresem a sémából az összes olyan tulajdonság sémaelemet, amely a globális katalógusba replikálódik:

$strFilter = "(&(objectCategory=attributeSchema)(isMemberOfPartialAttributeSet=TRUE))"

 

$objRoot = [ADSI] "LDAP://CN=Schema,CN=Configuration,DC=iqjb,DC=w08"

$objSearcher = New-Object System.DirectoryServices.DirectorySearcher

$objSearcher.SearchRoot = $objRoot

$objSearcher.PageSize = 1000

$objSearcher.Filter = $strFilter

$objSearcher.SearchScope = "Subtree"

 

$colProplist = "name"

foreach ($i in $colPropList){$objSearcher.PropertiesToLoad.Add($i)}

 

$colResults = $objSearcher.FindAll()

 

foreach ($objResult in $colResults)

    {

     $objItem = $objResult.Properties

     "$($objItem.item('name'))"

    }

 

$objSearcher.Dispose()

$colResults.Dispose()

Itt annyival gazdagítottam a keresőm tulajdonságait, hogy meghatároztam a maximális találatok számát (PageSize) és a találati listába betöltött elemek tulajdonságainak listáját (PropertiesToLoad), ami itt most csak az elem neve. A szkriptemben az első foreach ciklust tulajdonképpen feleslegesen használtam, hiszen csak egy tulajdonságot (name) töltöttem a kereső „PropertiesToLoad” képességébe, de én ezt a szkriptet sokszor olyankor is fel akarom használni kevés változtatással, amikor több tulajdonságot is meg akarok kapni a keresés eredményében.

A kiíratásnál most a hashtáblás stílussal hivatkoztam a „name” tulajdonságra, és a folyamat végén, memóriatakarékossági okokból, eldobom a kereső és a találati lista objektumokat.

A következő példa függvényével felhasználói fiókok tetszőleges attribútumát lehet tömegesen lecserélni valami másra. A kód megfejtését az előzőek ismeretében az olvasóra bízom. Csak egy kis segítséget adok: a középrészen található If vizsgálat Else ágában azt érem el, hogy ha a kicserélendő attribútum érték üres, azaz azt szeretnénk, hogy a ki nem töltött attribútumokat töltsük ki, akkor ez az LDAP filterben a !$Attr=* kifejezést kell szerepeltetni, ennek az a jelentése, hogy „az $Attr változó által jelzett attribútum nem egyenlő akármi, azaz van értéke”.

function ModifyUserAttrib

{

param (

$domain =

     [System.DirectoryServices.ActiveDirectory.Domain]::getcurrentdomain().Name,

$Attr = $(throw "Melyik attribútumot?"),

$sValue = $null,

$cValue = $(throw "Mire változtassam?")

)

 

$root= [ADSI] "LDAP://$domain"

$Searcher = New-Object DirectoryServices.DirectorySearcher

$Searcher.SearchRoot = $root

if($sValue)

{

     $buildFilter = "(&(objectClass=user)($Attr=$sValue))"

}

else

{

     $buildFilter = "(&(objectClass=user)(!$Attr=*))"

}

$Searcher.Filter = $buildFilter

$users = $searcher.findall()

Foreach ($i in $users)

{

     $dn=$i.path

     $user = [ADSI] $dn

     write-host $dn

     $user.Put($Attr,$cValue)

     $user.SetInfo()

}

}

Keresés idő típusú adatokra

A címtárban nem csak szöveges adatok vannak, hanem például dátum típusúak is. Ezekre nem triviális a keresés. Például keresem az utóbbi 2 napban módosított AD felhasználói objektumokat:

PS C:\> $tól=get-date ((get-date).AddDays(-2)) -Format yyyyMMddHHMMss.0Z

PS C:\> $tól

20080530110514.0Z

PS C:\> $searcher = New-Object directoryservices.directorysearcher

PS C:\> $searcher.searchroot = [ADSI] ""

PS C:\> $searcher.filter = "(&(objectCategory=person)(objectClass=User)(when

Changed>=$tól))"

PS C:\> $result = $searcher.findall()

PS C:\> $result

 

Path                                  Properties

----                                  ----------

LDAP://CN=János Vegetári,OU=Demó,D... {samaccounttype, lastlogon, dscore...

Az egészben a lényeg a $tól változó generálása. Látjuk, hogy egy speciális formátumra kell hozni a dátumot: ÉÉÉÉHHNNÓÓPPMM.0Z. Ilyen formázást szerencsére a get‑date format paraméterével könnyen elvégezhetünk.

Sajnos nem mindig ilyen egyszerű a helyzetünk, hiszen néhány dátumot jelző attribútum nem ilyen formátumban tárolja az időt, hanem un. „tick”-ekben, ketyegésekben. Ez 1601. január 1. 0:00 időponttól eltelt 100 nanoszekundumokat jelenti, mindez long-integer formátumban. Ilyen attribútum például a lastLogon, lastLogonTimestamp, lastLogoff, pwdLastSet. Ha ilyen attribútumok valamely értékére akarunk rákeresni, akkor elő kell tudnunk állítani ezt az értéket. Szerencsére a .NET keretrendszer ebben is segít. Elsőként nézzük, hogy dátumból hogyan tudunk ilyen long-integer-t előállítani:

[5] PS C:\> $most = get-date

[6] PS C:\> $most

 

2008. június 1. 20:49:41

[7] PS C:\> $most.ticks

633479501812656250

Látjuk, hogy elég a ticks tulajdonságát meghívni a dátumnak. Nézzük visszafele, azaz long-integer-ből hogyan kapunk dátumot? Ez sem nagyon bonyolult:

[8] PS C:\> $MyLongDate = 633479501812656250

[9] PS C:\> $mydate = New-Object datetime $MyLongDate

[10] PS C:\> $mydate

 

2008. június 1. 20:49:41

Egyszerűen kell kreálni egy dátum típusú objektumot, az objektum konstruktorának kell átadni ezt a számot és ebből a .NET létrehozza a dátumot. Azonban vigyázzunk! Az Active Directory-ban más az időszámítás kezdete a dátum típusú adatmezőknél:

135 . ábra Az AD ketyegései

A fenti ábrán látható, hogy itt egy 2010-es dátum hány ketyegésnek felel meg, ez eltér a PowerShell által kezelt dátumformátumétól. Az átváltást a következő függvénnyel oldhatjuk meg, ami a ToFileTime  és FromFileTime  metódusokat használja:

function convert-ADTicks{

    param ($object)

 

    if($object -is [datetime]){

        $object.ToFileTime()

    }

    elseif($object -is [system.int64]) {

        [datetime]::FromFileTime($object)

    }

}

Ez már oda-vissza jól számolja az AD ketyegéseket:

[55] PS C:\> convert-ADTicks 129110455862586250

 

2010. február 19. 10:33:06

 

 

[56] PS C:\> convert-ADTicks ([datetime]::parse("2010. február 19. 10:33:06"))

129110455860000000

Persze az dátumból történő konvertálásnál elveszítünk néhány ketyegést, de nem olyan vészes a helyzet, hiszen ez a különbség mindenképpen kevesebb lesz, mint 1 másodperc:

[59] PS C:\> ([timespan] (129110455862586250 - 129110455860000000)).totalsecond

s

0,258625

Keresés bitekre

A másik nem triviális eset, hogy bizonyos attribútumokban tárolt szám egyes bitjei jelentenek valamit. Például a felhasználói fiók userAccountControl attribútumának különböző bitjei a következőket jelentik:

Jellemző

userAccountControl bit

2^x

ACCOUNT DISABLED

 2

1

PASSWORD NOT REQUIRED

 32

5

PASSWORD NEVER EXPIRES

 65536

16

SMARTCARD REQUIRED

 262144

18

ACCOUNT TRUSTED FOR DELEGATION

 524288

19

ACCOUNT CANNOT BE DELEGATED

 1048576

20

Itt a feladat az, hogy olyan vizsgálatot végezzünk a szűrőben, amellyel csak egy adott bit értékére keresünk rá. Szerencsére van erre a célra két un. vezérlő (control) az AD-ben, ami ezt a célt szolgálja:

• 1.2.840.113556.1.4.803 – bit szintű AND szabály

• 1.2.840.113556.1.4.804 – bit szintű OR szabály

Hogyan kell ezeket használni? Nézzük például a hatástalanított felhasználói fiókokat:

(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=2))

Ebben a példában az utolsó feltétel azt jelenti, hogy a userAccountControl 2-es bitje helyén 1 van. Azaz „összeéselem” 2-vel az attribútum értékét, a többi bit nem érdekel engem, így az adott feltétel akkor értékelődik ki igazzá, ha ott 1-es szerepel.

Nézzünk egy olyan példát, hogy keresem a Global és Domain Local biztonsági csoportokat:

(&(groupType:1.2.840.113556.1.4.804:=6)(groupType:1.2.840.113556.1.4.803:=2147483648))

Itt az első feltételelem akkor értékelődik igazzá, ha vagy a 2-es, vagy a 4-es bit helyén szerepel 1-es, ez a két bit jelzi a Globális és Domain Local csoporttípust.

A második feltétel az már egy AND szabály, az azt vizsgálja, hogy a csoport biztonsági típusú.



Word To HTML Converter