Szerző: Soós Tibor

2010. május 4. 09:09

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.

HIRDETÉS

Í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

a címlapról