A PowerShell 2.0 egyik újdonsága, hogy képes kifejezéseket háttérben futtatni. Korábban, ha egy időigényes kifejezést futtattunk, akkor a konzolunk addig „használhatatlan” volt, amíg a futó program be nem fejezte a ténykedését. Most viszont nyithatunk külön munkamenetet a hosszadalmas tevékenységek számára és a konzolunkon tovább dolgozhatunk. Eközben lekérdezhetjük a háttérben futó programunk státusát, és megtekinthetjük a kimenetét. Nézzük mindezt meg a gyakorlatban!
Az ezzel kapcsolatos cmdletek főneve a „Job”:
PS C:\> Get-Command -noun job
CommandType Name Definition
----------- ---- ----------
Cmdlet Get-Job Get-Job [[-Id] <Int32[]>] ...
Cmdlet Receive-Job Receive-Job [-Job] <Job[]>...
Cmdlet Remove-Job Remove-Job [[-Id] <Int32[]...
Cmdlet Start-Job Start-Job [-ScriptBlock] <...
Cmdlet Stop-Job Stop-Job [[-Id] <Int32[]>]...
Cmdlet Wait-Job Wait-Job [[-Id] <Int32[]>]...
A munkát a Start-Job -bal kezdhetjük el:
PS C:\> start-job -Name Háttérmunka -ScriptBlock {get-process}
WARNING: column "Command" does not fit into the display and was removed.
Id Name State HasMoreData Location
-- ---- ----- ----------- --------
3 Háttérmunka Running True localhost
Látható, hogy adhatunk a munkamenetnek egy nevet és a ScriptBlock paraméterként átadott futtatható parancssort elindítja a PowerShell egy külön menetben. A Start-Job visszatérési értéke maga a „job” objektum. Ennek egyik legfontosabb tulajdonsága a State, azaz a státus. Rögtön indítás után természetesen ez még azt mutatja, hogy Running, azaz futó állapotban van.
Ha később újra le akarjuk kérdezni ennek a munkamenetnek az állapotát, akkor a get-job cmdlettel ezt megtehetjük:
PS C:\> get-job -Name Háttérmunka
WARNING: column "Command" does not fit into the display and was removed.
Id Name State HasMoreData Location
-- ---- ----- ----------- --------
3 Háttérmunka Completed True localhost
Ha azt látjuk, hogy a munkamenet tulajdonságai között a HasMoreData értéke $true, akkor megnézhetjük annak kimenetét is a Receive-Job cmdlet segítségével:
PS C:\> Receive-Job -Name Háttérmunka
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
34 2 568 2132 17 0,04 2548 conhost
87 4 2016 5976 59 1,18 3148 conhost
378 5 1160 1308 31 0,59 360 csrss
202 6 1468 2296 158 1,19 404 csrss
78 4 912 3320 20 2132 dllhost
72 4 1056 864 37 0,11 980 dwm
...
Ha ezután újra megnézzük a munkamenet tulajdonságait és az már korábban lefutott, és nem lett újabb kimenetünk, akkor a HasMoreData értéke $false lesz.
Ha egy futó munkamenethez kapcsolódunk a Receive-Job segítségével, akkor az aktuális kimenetet kapjuk meg folyamatosan a konzolra, de természetesen egy Ctrl+C billentyűzetkombinációval ez megszakítható – de a munkamenet maga fut tovább! - és később újra „belepillanthatunk” a folyamatba. Ilyenkor az előző Receive‑Job óta generálódott kimenetet kapjuk meg, szintén HasMoreData = $true mellett:
PS C:\> start-job -ScriptBlock { for($i=0; $i -lt 60; $i++){$i; Start-Sleep
-Seconds 1}}
WARNING: column "Command" does not fit into the display and was removed.
Id Name State HasMoreData Location
-- ---- ----- ----------- --------
13 Job13 Running True localhost
PS C:\> Receive-Job 13
0
1
2
3
4
5
6
7
PS C:\> Receive-Job 13
8
9
10
Ha lefutott a munkamenet, akkor a teljes kimenetet újra megnézhetjük a Receive‑Job segítségével. Szintén újra megkaphatunk egy már korábban kinyert kimenetet, ha a receive-job cmdletet a –keep kapcsolójával használjuk:
PS C:\> start-job -ScriptBlock { for($i=0; $i -lt 60; $i++){$i; Start-Sleep
-Seconds 1}}
WARNING: column "Command" does not fit into the display and was removed.
Id Name State HasMoreData Location
-- ---- ----- ----------- --------
38 Job38 Running True localhost
PS C:\> receive-job -id 38 -keep
0
1
2
PS C:\> receive-job -id 38 -keep
0
1
2
3
4
Ha kifejezetten az a célunk, hogy egy munkamenet végét megvárjuk, akkor használhatjuk a Wait-Job cmdletet:
PS C:\> start-job -Name Hosszú -ScriptBlock {Get-ChildItem c:\ -Recurse}
WARNING: column "Command" does not fit into the display and was removed.
Id Name State HasMoreData Location
-- ---- ----- ----------- --------
5 Hosszú Running True localhost
PS C:\> Wait-Job -Id 5
A Wait-Job után alaphelyzetben csak akkor kapjuk vissza a promptot, ha az adott munkamenet befejeződött.
Egy-egy munkamenet komoly memóriamennyiséget is lefoglalhat, mint például a fenti teljes C meghajtó fájljainak a kilistázása. Ha már nincs szükségünk az eredményre, akkor érdemes a Remove-Job cmdlettel eltávolítani a munkamenetet:
PS C:\> Remove-Job -Id 5
Ha futtatjuk a munkamenetet, de mégsem akarjuk kivárni a befejeződését, akkor megszakíthatjuk a működését a Stop-Job cmdlet segítségével:
PS C:\> start-job -Name Hosszú -ScriptBlock {Get-ChildItem c:\ -Recurse}
WARNING: column "Command" does not fit into the display and was removed.
Id Name State HasMoreData Location
-- ---- ----- ----------- --------
7 Hosszú Running True localhost
PS C:\> Stop-Job -id 7
PS C:\> Get-Job
WARNING: column "Command" does not fit into the display and was removed.
Id Name State HasMoreData Location
-- ---- ----- ----------- --------
7 Hosszú Stopped False localhost
Hogyan lehet paramétert átadni egy ilyen háttérben futó folyamatnak?
[3] PS C:\> $keresendőfájl = "szöveg.txt"
Létrehoztam egy változót, amiben egy keresendő fájl nevét tettem.
[4] PS C:\> $job = Start-Job -Name "Keres" -ScriptBlock {Get-ChildItem c:\
munka\$keresendőfájl -Recurse}
[5] PS C:\> $job
WARNING: column "Command" does not fit into the display and was removed.
Id Name State HasMoreData Location
-- ---- ----- ----------- --------
17 Keres Completed True localhost
[6] PS C:\> Receive-Job 17
Directory: C:\munka
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2009. 11. 11. 21:41 105837 ActiveDirectoryRecycleBin.po
werpack
-a--- 2009. 11. 21. 19:45 254 futók.txt
-a--- 2009. 11. 18. 17:46 40 script.ps1
-a--- 2009. 11. 17. 19:49 7 szöveg.txt
Ezután létrehoztam egy háttérben futó folyamatot, amiben a Get-ChildItem cmdlettel keresem a keresendő fájlt. A scriptblock részben átadtam szándékaim szerint a $keresendőfájl változót, ennek ellenére, a [6]-os sorban lekérdezett eredményben a „munka” könyvtár össze fájlja szerepelt, nem csak a keresett. Azaz a globális scope-ban létrehozott változóm nem látszott a job számára.
Szerencsére lehet azért paramétert átadni, ezt pedig az –inputobject paraméter használatával tehetjük meg:
[7] PS C:\> $job = Start-Job -Name "Keres" -ScriptBlock {Get-ChildItem c:\
munka\$input -Recurse} -InputObject $keresendőfájl
[8] PS C:\> Receive-Job $job
Directory: C:\munka
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2009. 11. 17. 19:49 7 szöveg.txt
Itt a [7]-es sorban az –inputobject paramétereként adom meg a $keresendőfájl változót, erre a szkriptblokkban a $input változóval tudok hivatkozni. És a [8]-as sor után látható eredményben már tényleg csak a keresett fájlt látjuk.
Az -InputObject paraméter tulajdonképpen a csővezetéket szimbolizálja, azaz a háttérfolymatom ezzel a csőből is betáplálható, akár az elemek egyesével feldolgozhatók, ha van processz blokkunk:
PS C:\> $job = 1,2,3,4 | Start-Job -ScriptBlock {process{$_ * 2}}
PS C:\> Get-Job
Id Name PSJobTypeName State HasMoreData Location
-- ---- ------------- ----- ----------- --------
2 Job2 BackgroundJob Completed True localhost
PS C:\> Receive-Job -Job $job
2
4
6
8
A másik módja az adat átadásának az -ArgumentList paraméter használata:
PS C:\> $job = Start-Job -ScriptBlock {$args[0] * 2} -ArgumentList Valami
PS C:\> Receive-Job -Job $job
ValamiValami
Az -ArgumentList paraméter meg a hagyományos paraméterátadást szimbolizálja, ami a job oldalán az $args tömbben jelenik meg. Ha több adatot is át akarunk adni, akkor viszonylag kényelmetlen az $args tömb indexeivel játszadozni és elég könnyű „félreindexelni”. Szerencsére használhatunk nevesített paramétereket is:
PS C:\> $job = Start-Job -ScriptBlock {param($egyik, $másik) "$egyik $másik"} -
ArgumentList "eleje", "vége"
PS C:\> Receive-Job -Job $job
eleje vége
Ugyan itt már nem kellett a háttérben futó szkriptblokkban indexekkel bajlódni, de a Start-Job-nál még mindig könnyen hibázhatunk, ha rossz sorrendben adjuk meg az adatokat az –ArgumentList-nél. Ennél még könnyebb a helyzetünk PowerShell 3.0-tól, hiszen ott lehetőségünk van a using: előtaggal még ezt is könnyebbé tenni:
PS C:\> $egyik = "eleje"
PS C:\> $másik = "vége"
PS C:\> $job = Start-Job -ScriptBlock {"$using:egyik $using:másik"}
PS C:\> Receive-Job -Job $job
eleje vége
Látható, hogy itt el is hagytam bármilyen paraméter használatát, a using: implicit módon adja át a paraméterértékeket a háttérben futó folyamatnak, ha azok már valamilyen változóban vannak.
Amit fontos tudni, mind az adatok átadásról és az eredmények átvételéről, hogy szerializáláson mennek keresztül az adatok, azaz olyan, mintha Export-CLIXML és Import-CLIXML cmdletekkel adtuk volna át az adatokat:
PS C:\> $p = Get-Process -Id $pid
PS C:\> $job = Start-Job -ScriptBlock {$using:p | gm}
PS C:\> Receive-Job -Job $job
TypeName: Deserialized.System.Diagnostics.Process
Name MemberType Definition
---- ---------- ----------
GetType Method type GetType()
ToString Method string ToString(), string ToString(...
Handles NoteProperty int Handles=640
Name NoteProperty string Name=powershell
…
PS C:\> $job = Start-Job -ScriptBlock {Get-Service -Name wuauserv}
PS C:\> Receive-Job -Job $job | gm
TypeName: Deserialized.System.ServiceProcess.ServiceController
Name MemberType Definition
---- ---------- ----------
GetType Method type GetType()
ToString Method string ToString(), string ToString(string ...
Name NoteProperty string Name=wuauserv
PSComputerName NoteProperty string PSComputerName=localhost
…
Látható, hogy mind az átküldött processz adat, mind az átvett szolgáltatás Deserialized objektum lett, azaz elvesztették az eredeti metódusaikat.