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()
}
}
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
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ú.