A Select-Object a PowerShell svájci bicskája! Alapvetően három különböző funkciója van:
objektumokat csonkít meg és/vagy tesz hozzájuk újabb tulajdonságokat,
objektumokat nyel el vagy enged át magán,
tulajdonságként tárolt objektumokat emel fel és készít belőlük legmagasabb szinten levő objektumokat.
Nézzük ezeket a funkciókat egyesével!
A Select-Object a bemenetén kapott objektumokból képes levágni tulajdonságokat és csak azokat meghagyni, amire kifejezetten kérjük. Ezzel még azt is megtehetjük, hogy olyan tulajdonságot kérünk, amivel az objektum eredetileg nem is rendelkezik, de így ezeket ő fogja nekünk létrehozni.
Példaként alakítsuk át a Get-Process-től érkező objektumokat úgy, hogy csak a folyamat nevét, gyártóját és leírását tartalmazzák! A Select-Object cmdletnek egyszerűen azt kell megadnunk, hogy melyik tulajdonságokat szeretnénk megtartani:
PS C:\> Get-Process | Select-Object -Property name, company, description
Name Company Description
---- ------- -----------
ctfmon Microsoft Corporation CTF Loader
CTHELPER Creative Technology Ltd CtHelper MFC Application
CTSVCCDA Creative Technology Ltd Creative Service for ...
csrss
explorer Microsoft Corporation Windows Intéző
...
Mi is történik ebben az esetben? A Get-Process processz objektumokat dobál a csőbe, ezeket kapja meg a Select-Object, amely a paraméterlista által meghatározott tulajdonságokat egy újonnan létrehozott egyedi objektum azonos nevű tulajdonságaiba másolja. Ezek az objektumok fognak aztán tovább vándorolni a csövön.
Természetesen nemcsak azt mondhatjuk meg, hogy mely tulajdonságokra van szükségünk, hanem azt is, hogy melyekre nincs: az -excludeproperty paraméter után felsorolt tulajdonságok nem fognak szerepelni a kimenő objektumokban, ehhez viszont azt kell kérni a -property * paraméterrel, hogy a többi viszont jelenjen meg:
PS C:\> Get-Variable | Select-Object -Property * -ExcludeProperty attributes, d
escription, value, module
Name Visibility ModuleName Options
---- ---------- ---------- -------
$ Public None
? Public ReadOnly, AllScope
^ Public None
args Public None
ConfirmPreference Public None
ConsoleFileName Public ReadOnly, AllScope
DebugPreference Public None
Error Public Constant
…
A kimenő objektumok szerkezetét egy Get-Member hívás mutatja meg:
PS C:\> get-process | select-object -property name, description, company | get-
member
TypeName: Selected.System.Diagnostics.Process
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
Company NoteProperty System.String Company=Microsoft Corporation
Description NoteProperty System.String Description=Application Frame Host
Name NoteProperty string Name=ApplicationFrameHost
Látszik, hogy ez már nem az eredeti System.Diagnostics.Process objektum, hanem annak egy Selected változata. Ez azt jelenti, hogy a típusa már csak nyomokban emlékeztet az eredeti objektumra, valójában ez egy teljesen új objektum, csak a legalapvetőbb metódusokkal és a kiválasztott tulajdonságokkal, amelyek ráadásul mindenképpen NoteProperty-k lettek, függetlenül attól, hogy például eredetileg esetleg ScriptProperty-k voltak.
De nem csak csonkolhatjuk az objektumokat, hanem új tulajdonságokkal is kiegészíthetjük azokat:
PS C:\> Get-ChildItem C:\PowerShell | Select-Object Name, Length, @{n="NévHossz
"; e={$_.Name.Length}}
Name length NévHossz
---- ------ --------
20150923 8
Carbon 6
Ascii.txt 11 9
BigEndianUnicode.txt 24 20
BigEndianUTF32.txt 48 18
BreakAll.ps1 67 12
…
A fenti példában a Name és Length „gyári” tulajdonságok mellett egy újabb, NévHossz névre hallgató tulajdonságot hoztam létre, ami az eredeti Name tulajdonság hosszát adja meg. Ezt egy hashtáblával definiáltam, ahol az „n” kulcs értéke adja meg a tulajdonság nevét (a kulcs neve lehet „Name” is), az „e” kulcsban (lehet „Expression” is) pedig azt a szabályt adjuk meg szkriptblokk formájában, ami ennek az új tulajdonságnak megadja az értékét. Itt is a futószalagon érkező aktuális objektumot szimbolizálja a $_ változó.
Nézzünk egy összetettebb számolt tulajdonságot is. Itt most a jobb értelmezhetőség kedvéért áttördeltem a kódot:
dir c:\old | Select-Object
name,
@{ n = "Méret";
e = {switch ($m = $_.Length)
{ {$m -lt 1kb} {"Pici"; break}
{$m -lt 3kb} {"Közepes"; break}
{$m -lt 100kb} {"Nagy"; break}
default {"Óriási"}
}
}
}
A fenti példában a dir, azaz a get-childitem által a fájloknál megmutatott tulajdonságok helyett nekem kellene egy pici/közepes/nagy/óriási besorolása a fájloknak. Ehhez a Select-Object-nek a fájlok „igazi” name tulajdonsága mellett egy „képzett” tulajdonságot, a „Méret”-et is megadom.
És nézzük a kimenetet:
Name Méret
---- -----
alice.txt Pici
coffee.txt Pici
lettercase.txt Pici
numbers.txt Pici
presidents.txt Pici
readme.txt Közepes
skaters.txt Nagy
symbols.txt Pici
vertical.txt Pici
votes.txt Nagy
wordlist.txt Óriási
A fentieken kívül a Select-Object néhány más funkcióval is rendelkezik: nemcsak objektumokat képes átalakítani („vertikális” változtatás), hanem gyűjteményeket is átalakít („horizontális” változtatás). Például képes a bemenetként kapott tömb elejéről vagy végéről meghatározott számú elemet leválasztani (-first és ‑last paraméter):
PS C:\> "egy", "kettő", "három", "négy", "öt" | Select-Object -First 2 -Last 1
egy
kettő
öt
Vagy azt is megadhatjuk a -skip-pel, hogy mennyit hagyjon ki:
PS C:\> "egy", "kettő", "három", "négy", "öt" | Select-Object -Skip 3
négy
öt
Vagy akár a végéről is elhagyhatunk elemeket:
PS C:\> "egy", "kettő", "három", "négy", "öt" | Select-Object -SkipLast 4
egy
Illetve képes egy gyűjtemény elemei közül csak a különbözőket (vagyis az egyforma elemek közül csak egyet) továbbadni a –unique kapcsoló segítségével:
PS C:\> PS C:\> 1,1,3,56,3,1,1,1,3 | Select-Object -unique
1
3
56
Mindezen változatoknál a gyűjtemény, tömb egyes elemei természetesen megőrzik az eredeti típusukat.
De vajon mi számít a –unique kapcsoló használata mellett egyformának és mi különbözőnek? Nézzünk erre egy példát:
PS C:\> Get-Process s*
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
681 91 107680 134448 735 14,52 3640 ScriptEditor
145 14 4708 9080 67 1240 SDWinSec
1094 70 45188 33320 162 3008 SearchIndexer
225 13 5888 9120 42 524 services
348 26 40220 36316 191 18,19 2776 sidebar
87 10 3168 6484 72 0,14 2416 SJelite3Launch
30 2 456 1052 5 276 smss
322 20 7120 12052 94 1360 spoolsv
371 14 4440 9400 54 660 svchost
369 16 4744 8736 42 756 svchost
513 25 17420 19168 90 900 svchost
671 33 108432 115488 240 932 svchost
1564 80 32140 48064 481 960 svchost
286 20 6136 10852 49 1108 svchost
761 51 32136 38768 174 1220 svchost
306 33 12540 14112 69 1556 svchost
319 26 6580 13072 90 1704 svchost
95 8 1864 5164 33 1996 svchost
91 8 1640 4408 47 2496 svchost
769 0 124 6300 11 4 System
Nézzük ezeket „megegyelve”:
PS C:\> Get-Process s* | Select-Object -Unique
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
681 91 107680 134448 735 14,52 3640 ScriptEditor
145 14 4708 9080 67 1240 SDWinSec
1094 70 45188 33320 162 3008 SearchIndexer
225 13 5888 9120 42 524 services
348 26 40220 36316 191 18,16 2776 sidebar
87 10 3168 6484 72 0,14 2416 SJelite3Launch
30 2 456 1052 5 276 smss
322 20 7120 12052 94 1360 spoolsv
369 14 4388 9380 53 660 svchost
769 0 124 6300 11 4 System
Láthatólag a sok svchost lett összevonva, de vajon milyen alapon? Pusztán a nevük alapján? És az olyan objektumok esetében, amelyeknek nincs nevük, ott mi lesz? Az algoritmus nagyon egyszerű: az a két objektum egyforma, amelyeknek meghívva a ToString metódusát egyforma lesz a kimenet. Ez két különböző svchost-példány esetében is ugyanaz, ezért lettek összevonva:
PS C:\> Get-Process svchost | ForEach-Object {$_.tostring()}
System.Diagnostics.Process (svchost)
System.Diagnostics.Process (svchost)
System.Diagnostics.Process (svchost)
System.Diagnostics.Process (svchost)
System.Diagnostics.Process (svchost)
System.Diagnostics.Process (svchost)
System.Diagnostics.Process (svchost)
System.Diagnostics.Process (svchost)
System.Diagnostics.Process (svchost)
System.Diagnostics.Process (svchost)
System.Diagnostics.Process (svchost)
Sajnos itt csak azon az áron tudjuk testre szabni a „megegyelést”, hogy közben lemetsszük az egyformaságot meghatározó tulajdonságokat:
[4] PS C:\> Get-process | Select-Object -Unique -Property name, id | Format-Tab
le -AutoSize
Name Id
---- --
audiodg 596
conhost 2136
csrss 416
csrss 496
daemon 2744
dwm 2932
explorer 2992
FcsSas 1592
FlashUtil10e 1892
Idle 0
ielowutil 1484
iexplore 1444
iexplore 2104
IoctlSvc 1796
…
Ha a teljes eredeti objektumokat meg akarjuk őrizni, akkor erre a Sort-Object cmdlet fog majd megoldást nyújtani.
PowerShell 3.0-vel jelentős optimalizálás történt a „horizontális” szelektálás üzemmódban. Korábban, ha a következő kifejezést futtattam:
PS C:\> 1..1000000 | Select-Object -First 2
1
2
Jónéhány másodpercet kellett várni, míg az eredmény megjelent, mivel hiába kértem csak az 1.000.000 szám első két elemét, a háttérben az összes elem rákerült a futószalagra, csak a Select-Object elnyelte a második elem utáni összeset.
Az új üzemmódban viszont a harmadik és a későbbi elemek el sem indulnak, így sokkal gyorsabban megkapom az eredményt. Ha mégis az eredeti működés kell nekem, mert például van még egy korábbi futószalag-szakasz, ahol fontos, hogy az összes elem feldolgozásra kerüljön, akkor a -Wait kapcsoló használatával a korábbi működést is kérhetjük:
PS C:\> $összeg = 0
PS C:\> 1..400000 | %{$összeg += $_; $_} | Select-Object -First 2 -Wait
1
2
PS C:\> $összeg
80000200000
Látható a nagy $összeg-ből, hogy tényleg minden elem elindult és a Select-Object nyelte csak el őket. Összehasonlításuk, ugyanez a -Wait nélkül:
PS C:\> $összeg = 0
PS C:\> 1..400000 | %{$összeg += $_; $_} | Select-Object -First 2
1
2
PS C:\> $összeg
3
Itt csak tényleg az első két elem indult útnak.
Még tud a Select-Object az –Index paraméterrel valahányadik elemet kiválasztani a gyűjteményből:
PS C:\> 1..10 | Select-Object -Index 5,8
6
9
PS C:\> 1..10 | Select-Object -Index 8
9
Még egy gyakori felhasználási területe van a select-object-nek. Ennek felvezetésére nézzünk egy egyszerű példát:
PS C:\> get-service a* | Group-Object -Property status
Count Name Group
----- ---- -----
5 Running {AdobeARMservice, AppHostSvc, Appinfo, Audi...
8 Stopped {AJRouter, ALG, AppIDSvc, AppMgmt...}
Az első sorban lekérdezem az „a” betűvel kezdődő szolgáltatásokat, majd csoportosítottam őket a status paraméterük alapján. A kimeneten az egyes csoportokat alkotó szolgáltatások tömbbe rendezve (kapcsos zárójelek között) láthatók.
Ez kevés szolgáltatásnál akár jó is lehet, de már a fenti példában is a nem nagyon fért ki az egyes csoportok listája. A kibontásban segíthet a select‑object az ‑expandproperty paraméterrel:
PS C:\> get-service a* | Group-Object -Property status | Select-Object -ExpandP
roperty group
Status Name DisplayName
------ ---- -----------
Running AdobeARMservice Adobe Acrobat Update Service
Running AppHostSvc Application Host Helper Service
Running Appinfo Application Information
Running AudioEndpointBu... Windows Audio Endpoint Builder
Running Audiosrv Windows Audio
Stopped AJRouter AllJoyn Router Service
Stopped ALG Application Layer Gateway Service
Stopped AppIDSvc Application Identity
Stopped AppMgmt Application Management
Stopped AppReadiness App Readiness
Stopped AppXSvc AppX Deployment Service (AppXSVC)
Stopped aspnet_state ASP.NET State Service
Stopped AxInstSV ActiveX Installer (AxInstSV)
Itt a sor utolsó tagjában a select-object kifejti a group tulajdonságot, eredményeképpen visszakapjuk a szolgáltatások listáját, immár csoportosítás utáni sorrendben. Gyakorlatilag ugyanahhoz az eredményhez jutottunk, mint Get-Service | Sort-Object –property Status kifejezéssorral (lásd következő fejezet).
Másik gyakori példa az -ExpandProperty használatára, ha egy tulajdonság maga is egy összetettebb objektum, aminek több tulajdonsága van, mint például a fájlok verzióinformációi:
PS C:\> dir C:\Windows -Filter w*.exe | Select-Object -Property versioninfo
VersionInfo
-----------
File: C:\Windows\winhlp32.exe...
File: C:\Windows\Word to HTML Converter Uninstaller.exe...
File: C:\Windows\write.exe...
Ha a -Property üzemmóddal nézzük, akkor nem igazán látjuk a verziókat. Ezzel szemben, ha ‑ExpandProperty-vel futtatjuk, akkor mindjárt láthatóvá válnak a részletek:
PS C:\> dir C:\Windows -Filter w*.exe | Select-Object -ExpandProperty versionin
fo
ProductVersion FileVersion FileName
-------------- ----------- --------
10.0.10240.16384 10.0.10240.16... C:\Windows\winhlp32.exe
C:\Windows\Word to HTML Converter Uninsta...
10.0.10240.16384 10.0.10240.16... C:\Windows\write.exe
A cmdletek egyik u.n. common parameter-e a -PipelineVariable . Ez azt a célt szolgálja, hogy a $_ változó mellett milyen egyéb változóban is tegye elérhetővé a csőbe tett elemet. Miért jó ez? Nézzük a következő „kígyót”:
PS C:\> Get-Process p* | Where-Object {$_.path} | Get-Item | Select-Object -Exp
andProperty versioninfo | Select-Object -Property Language
Kiindulunk a P betűvel kezdődő processz objektumokból, majd azokból, amelyeknek van Path tulajdonsága, megragadjuk a fájl objektumát, majd kiemeljük a fájlokból azok VersionInfo tulajdonságát, majd ezeknek vesszük a Language tulajdonságát. De hogyan tudnánk a végeredményben a Language tulajdonság mellé berakni a processz ID tulajdonságát és a fájl méretét? Azaz ezek az adatok a csővezeték különböző szakaszain keresztülhaladó objektumokon állnak rendelkezésre. A legvégén elkapható $_ változóban a VersionInfo objektum áll csak rendelkezésre. Itt jön be minden szakasz cmdletjének a -PipelineVariable paramétere, amelyekkel a Get-Process eredményét a $Processz változóba, a Get-Item eredményét a $Fájl változóba tesszük és a végén ezekből szedegetjük össze a megfelelő tulajdonságokat:
PS C:\> Get-Process p* -PipelineVariable Processz | Where-Object {$_.path} | Ge
t-Item -PipelineVariable Fájl | Select-Object -ExpandProperty versioninfo | Sel
ect-Object -Property @{n= "Id"; e={$Processz.Id}}, Language, @{n="Mode"; e={$Fá
jl.Length}}
Id Language Mode
-- -------- ----
14592 Language Neutral 358128
5240 Language Neutral 346040
18468 English (United States) 492032