A Windows PowerShell rejtelmei - 4. rész
A Windows PowerShellt bemutató cikksorozat negyedik részében megmutatom, hogyan tudják a rendszergazdák objektumok összehasonlításával megtalálni egy rejtélyesen működő PC hibáit.
Ígéretemhez híven ebben a részben tovább folytatom az objektumok összehasonlítását. A rendszergazdák gyakori problémája, hogy két ugyanolyannak feltételezett számítógép mégsem ugyanúgy viselkedik. Ennek számos oka lehet: eltérő hardver, eltérő szoftveres beállítások, más futó alkalmazások, más telepített szolgáltatások, melyek felderítése nem túl egyszerű feladat. Ebben a cikkben megmutatom, hogy a PowerShell ebben is tud nekünk segíteni, szerteágazó jelentéseket állíthatunk össze, akár távoli gépekről is, drága rendszerfelügyeleti szoftverek nélkül.
A vizsgálat módszere az lesz, hogy kiválasztok egy referencia-számítógépet, ami szerintem ideálisan működik, és ehhez hasonlítom a feltételezetten ugyanolyan, de mégis eltérő teljesítményt nyújtó gépet. Elsőként derítsük fel a két gépen futó folyamatok közti különbségeket! A futó folyamatokat a Get-Process cmdlet segítségével kérdezhetjük le, de ha megadjuk a ComputerName paramétert, nem csak arról a gépről, ahol futtatjuk a parancsot, hanem távoli gépről is. Ráadásul egyszerre több gépnevet adhatunk át ennek a ComputerName paraméternek, így a kimeneten az összes érintett gép folyamatait láthatjuk. Most itt próbaként csak a \"c\" betűvel kezdődő nevű folyamatokat térképezem fel a DC és a Member nevű számítógépeimen:
[1] PS C:\\> Get-Process c* -ComputerName DC, Member Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName - - - - - - - - 41 6 1616 4208 49 1156 conhost 35 5 1536 4048 47 0,20 2720 conhost 421 11 1768 3532 42 1,84 312 csrss 344 10 1792 3616 42 328 csrss 179 11 1768 6524 110 22,27 360 csrss 144 10 1564 4460 41 368 csrss
Természetesen ez csak akkor működik, ha rendszergazda jogosultságom van a megfigyelt gépeken. Hogy azt is lássuk, hogy ez eredmény egyes sorai melyik gépről érkeztek, testre szabom a megjelenő táblázatot a Format-Table cmdlettel, a folyamat neve mellett megjelenítem a gép nevét is:
[2] PS C:\\> Get-Process c* -ComputerName DC, Member | Format-Table ProcessName, machinename ProcessName MachineName - - conhost Member conhost DC csrss DC csrss Member csrss DC csrss Member
Látható tehát, hogy vegyesen vannak a két gépről érkezett adatok. Az egyszerűség kedvéért mindkét gép folyamatainak objektumait beteszem egy $all változóba, majd képzem az összehasonlításban résztvevő két gép szolgáltatás-kupacát egy-egy MachineName tulajdonságra való szűréssel. Ezt a két kupacot adom át az előző cikkben már látott Compare-Object-nek a Name paraméter alapján történő összehasonlításra:
[3] PS C:\\> $all = Get-Process -ComputerName DC, Member [4] PS C:\\> Compare-Object ($all | Where-Object {$_.machinename -eq \"DC\"}) ($all | Where-Object {$_.machinename -eq \"Member\"}) -Property Name name SideIndicator - - svchost => dfsrs <= dfssvc <= dns <= ismserv <= Microsoft.ActiveDirectory.WebServices <= ntfrs <= ScriptEditor <= vds <=
A vizsgált gépen (Member) tehát azok a folyamatok hiányoznak a referenciagéphez (DC) képest, amelyek neve mellett a \"<=\" SideIndicator áll, és azok a többlet folyamatok, melyek mellett a \"=>\" áll. Kérdezheti az olvasó, hogy vajon miért nem két külön Get-Process-t hívok meg a két gépre, és azokat egymással hasonlítom össze? A válasz az, hogy ebből a formából kiindulva sokkal könnyebb ezt a vizsgálatot általánosítani olyan irányba, hogy a referenciagép mellett ne is egy vizsgált gép legyen, hanem egyszerre sok, így egy kifejezéssel akár több gép elemzését is el tudjuk végezni.
Bonyolultabb a helyzet a rendszerszolgáltatások összehasonlításával. Azt, hogy mik a két gépre telepített szolgáltatások közti eltérések, az előzőek analógiájával ugyanilyen módon fel lehet deríteni, csak a Get-Process cmdlet helyett a Get-Service-t kell használni. Ami kicsit nehezebb, az annak megállapítása, hogy melyek azok a szolgáltatások, amelyek ugyan mindkét gépen telepítve vannak, de az egyik gépen el vannak indítva, míg a másikon nem. Induljunk el itt is mindkét gép összes szolgáltatásának összegyűjtésével:
[11] PS C:\\> $all = Get-Service -ComputerName DC, Member [12] PS C:\\> $all Status Name DisplayName - - - Running ADWS Active Directory Web Services Stopped AeLookupSvc Application Experience Stopped AeLookupSvc Application Experience Stopped ALG Application Layer Gateway Service Stopped ALG Application Layer Gateway Service ...
Ezekből hagyjuk meg azokat a szolgáltatásokat, amelyek mindkét gépen telepítve vannak. Ezt legegyszerűbben a szolgáltatásobjektumok név szerinti csoportosításával és az így kapott csoportok közül a kételeműek kiválogatásával tudjuk megkapni:
[16] PS C:\\> $all | Group-Object -Property servicename | where-object {$_.count -eq 2} Count Name Group - - - 2 AeLookupSvc {System.ServiceProcess.ServiceController, S... 2 ALG {System.ServiceProcess.ServiceController, S... 2 AppIDSvc {System.ServiceProcess.ServiceController, S... 2 Appinfo {System.ServiceProcess.ServiceController, S... 2 AppMgmt {System.ServiceProcess.ServiceController, S... ...
Emellett még azt is meg kell nézni, hogy a kételemű csoportok közül melyek azok, ahol a csoporton belüli két szolgáltatásobjektum státusza nem egyforma (group[0] és group[1] tulajdonságban tárolt elem):
[19] PS C:\\> $all | Group-Object -Property servicename | where-object {$_.count -eq 2 –and $_.group[0].status -ne $_.group[1].status} Count Name Group - - - 2 PolicyAgent {System.ServiceProcess.ServiceController, S... 2 TrkWks {System.ServiceProcess.ServiceController, S... 2 vds {System.ServiceProcess.ServiceController, S...
Ezután a csoportokat ki kell csomagolni, hogy újra szolgáltatásobjektumokat kapjunk, ne pedig csomagobjektumokat, és ezek közül ki kell szűrni csak a vizsgált gépre vonatkozó adatokat. A kicsomagolást a Select-Object cmdlet ExpandProperty kapcsolóval jelzett üzemmódjával tudjuk megtenni:
[18] PS C:\\> $all | Group-Object -Property servicename | where-object {$_.count -eq 2 –and $_.group[0].status -ne $_.group[1].status} | Select-Object -ExpandProperty group | where-object
{$_.machinename -eq \"Member\"} Status Name DisplayName - - - Running PolicyAgent IPsec Policy Agent Running TrkWks Distributed Link Tracking Client Stopped vds Virtual Disk
Ebben a végeredményben például az látható, hogy bár mindkét gépen szerepel a PolicyAgent szolgáltatás, ez a vizsgált gépemen fut, de a referencia gépemen ez nem fut, hiszen a szűrés miatt ott más kell legyen a szolgáltatás állapota. A másik összehasonlításra igazán jó terep a Windowsos számítógépek WMI adatbázisa, azaz a Windows Management Instrumentation szolgáltatás által kezelt, a gépek legfontosabb adatait tartalmazó adatbázisa. Ezen belül is a legalapvetőbb információt a Win32_ComputerSystem osztály (tábla) tartalmazza, amit PowerShell segítségével nagyon egyszerűen tudjuk kiolvasni. Nézzük egyelőre egy gépre hogyan is megy ez:
[23] PS C:\\> Get-WmiObject -Class Win32_ComputerSystem Domain : r2.dom Manufacturer : innotek GmbH Model : VirtualBox Name : DC PrimaryOwnerName : Windows User TotalPhysicalMemory : 1073274880
Az így látható kimenet nem tartalmazza az összes adatot, minden adat megjelenítéséhez használhatjuk a Format-List cmdletet a * paraméterrel:
[24] PS C:\\> Get-WmiObject -Class Win32_ComputerSystem | Format-List * ... Manufacturer : innotek GmbH Model : VirtualBox NameFormat : NetworkServerModeEnabled : True NumberOfLogicalProcessors : 1 NumberOfProcessors : 1 OEMLogoBitmap : OEMStringArray : PartOfDomain : True PauseAfterReset : -1 PCSystemType : 2 PrimaryOwnerContact : PrimaryOwnerName : Windows User ResetCapability : 1 ResetCount : -1 ResetLimit : -1 Roles : {LM_Workstation, LM_Server, Primary_Domain_C ontroller, Timesource...} SupportContactDescription : SystemStartupDelay : SystemStartupOptions : SystemStartupSetting : SystemType : x64-based PC TotalPhysicalMemory : 1073274880 ...
Ez elég sok tulajdonságot tett láthatóvá, így erősen megvágtam, hogy ne legyen túl hosszú. Az operációs rendszer és a számítógép legfontosabb adatait láthatjuk. Nekünk most nem is önmagában ez a sok tulajdonság az érdekes, hanem két számítógép ilyen jellegű tulajdonságai közti eltérések. Ezt az összehasonlítást nem annyira triviális elvégezni, mert míg objektumok kupacait a Compare-Object cmdlettel össze tudjuk hasonlítani, addig sajnos tulajdonságok összehasonlítására Compare-ObjectProperty cmdlet nincs. De készíthetünk ilyet! Az alapötlet az, hogy vesszük ezeket a tulajdonságokat egyesével, és egy-egy tulajdonságnév és érték párból új objektumokat építek össze. De nézzük ezt lépésről-lépésre!
[26] PS C:\\> $wmiobj = Get-WmiObject -Class Win32_ComputerSystem [28] PS C:\\> $wmiobj | Get-Member -MemberType properties | ForEach-Object {\"$($_.name) : $($wmiobj.($_.name))\"} AdminPasswordStatus : 3 AutomaticManagedPagefile : True AutomaticResetBootOption : True AutomaticResetCapability : True BootOptionOnLimit : BootOptionOnWatchDog : BootROMSupported : True BootupState : Normal boot Caption : DC ChassisBootupState : 2 CreationClassName : Win32_ComputerSystem ...
A fenti lépéssorral először képeztem a $wmiobj változóba a gépem WMI tulajdonságait, majd ezeknek vettem a tulajdonság (property) típusú tagjellemzőit a Get-Member cmdlettel. Ezeket a tagjellemzőket sorra kiírattam „tulajdonság neve: tulajdonság értéke” formátumban egy Foreach-Object \"ciklussal\". Mivel nekem nem az a célom, hogy kiírjam ezeket az adatokat, hanem össze szeretném hasonlítani ezeket egy másik gép hasonló adataival, ezért képezek inkább egy új, egyedi PSObject típusú objektumot belőlük a New-Object cmdlet segítségével. Ez azért jó, mert ezt a fajta objektumot én szabadon felruházhatom tulajdonségokkal:
[30] PS C:\\> $wmiobj | Get-Member -MemberType properties | ForEach-Object { new-object -TypeName PSObject -Property @{name = $_.name; DC = $wmiobj.($_.name)}} name DC - - AdminPasswordStatus 3 AutomaticManagedPagefile True AutomaticResetBootOption True AutomaticResetCapability True BootOptionOnLimit BootOptionOnWatchDog BootROMSupported True
Ezzel a Property paraméter mellett átadott kifejezéssel gyakorlatilag oszlopneveket képeztem az új objektumom tulajdonságaival: \"name\" lett a WMI tulajdonságok nevei felett, a \"DC\" pedig az elsőként vizsgált gép neve, és alatta vannak ennek a gépnek a WMI tulajdonságértékei. Ehhez majd hozzá lehet biggyeszteni a második gép WMI tulajdonságértékeit egy újabb oszlopba, így az összehasonlítás nagyon egyszerűvé válik. A második gép adatainak hozzáillesztése az Add-Member cmdlettel lehetséges: egy egyszerű címke típusú (noteproperty) adatot teszek hozzá az objekumomhoz, melynek értéke pont a másik gép ugyanolyan nevű WMI tulajdonsága.
Az eredményből azokat a sorokat (objektumokat) kell csak megjeleníteni, amelyeknél a két gépnél kiolvasott tulajdonságértékek nem egyformák, valamint érdemes a WMI belső adatábrázolásához szükséges mezőket is elrejteni. Ez utóbbiak neve két aláhúzás karakterrel kezdődik, így ez alapján szűrhetők. A teljes megoldásból készítettem egy függvényt, ami referencia- és tesztgép nevével paraméterezhető:
function compare-computersystem ([string] $referencia, [string] $teszt) { $c1 = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $referencia $c2 = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $teszt $ct = $c1 | Get-Member -MemberType Properties | ForEach-Object { new-object -typename PSObject -property @{ name = $_.name; $referencia = $c1.($_.name)}} $ct | ForEach-Object { add-member -inputobject $_ -MemberType NoteProperty ` -Name $teszt -Value $c2.($_.name)} $ct | where-object { $_.$teszt -ne $_.$referencia -and $_.name -notlike \"__*\"} }
A függvény fejléce után két külön változóba ($c1, $c2) képzem a gépek WMI adatait. Látható, hogy ezt is lehet távoli módon meghívni. Ezután képzem a korábban már mutatott táblázat első gép adatait tartalmazó részét a $ct változóba. Majd újra előveszem ezt a változót, és minden egyes sorához hozzáillesztem a másik gép WMI adatát. A legvégén megszűröm az eredményt olyan sorokra, ahol eltérő a két gép tulajdonságértéke és a tulajdonság neve nem két aláhúzás-karakterrel kezdődik. Nézzük, mi lesz az eredmény, ha ezt meghívom:
[50] PS C:\\> compare-computersystem dc member | Format-Table -Wrap name dc member - - - Caption DC MEMBER DNSHostName dc MEMBER DomainRole 5 3 Name DC MEMBER Roles {LM_Workstation, LM_Serve {LM_Workstation, LM_Serve r, Primary_Domain_Control r, NT, Server_NT} ler, Timesource...} TotalPhysicalMemory 1073274880 838393856
A kimenetből az látszik, hogy az eltérő neveken kívül a számítógépeim esetleges eltérő viselkedését az eltérő szerverszerepek (egyik tartományvezérlő, a másik tagkiszolgáló) és az eltérő fizikai memória mérete okozza. Természetesen ez csak egy kis demókörnyezet eredménye, nem lett volna nehéz ezt a különbséget szabad szemmel is észrevenni, de ugyanezzel a megoldással akár egy sokgépes környezetben is eredményhez jutunk, amit manuálisan csak nagyon időigényesen tehetnénk meg. Mindebből az látszik, hogy néhány soros PowerShell-kifejezésekkel összetett elemzéseket végezhetünk számítógép-parkunkban. Miután ezek a kifejezések távolról is meghívhatók, így akár az Active Directory adatbázisában tárolt gépnevek alapján teljes felmérést készíthetünk az eltérések felderítésére.
Várom javaslataikat, hogy a következő cikkek témájául mire látnának megoldást PowerShell segítségével!
Soós Tibor (PowerShell MVP, MCT), IQSOFT – John Bryce Oktatóközpont