Akármennyire sok lehetőségünk van a hibák jelzésére (write-warning, write-error és trap), kezelésére mégsem minden esetben elég ez. Főleg akkor, ha a szkriptünk a maga módján jól működik, csak éppen mi rossz programot írtunk. Ilyenkor van szükség a „dibággolásra”, „bogártalanításra”, azaz a hibakeresésre. Erre is lehetőséget biztosít a PowerShell, bár valószínű egy bizonyos bonyolultság felett már inkább használunk erre a célra valamilyen grafikus szkriptszerkesztőt, mint például a PowerShell Integrated Scripting Environmentjét vagy a PowerGUI Script Editorát.
Az egyik „bogártalanítási” módszer, hogy jól teletűzdeljük a programunkat kiíratási parancsokkal, amelyek segítségével a változók aktuális állapotát írhatjuk ki, illetve jelezhetjük, hogy éppen milyen ágon fut a programon. Erre használhatjuk akár a write‑host cmdletet, csak az a baj ezzel, hogy a végleges, kijavított, tesztelt programból elég nehéz kiszedegetni ezeket. Ezért találtak ki másfajta kiíratási lehetőségeket is: a write-verbose, write-warning és a write-debug cmdleteket.
Nézzünk erre példát. Van egy faktoriálist számoló szkriptem, amit jól teletűzdelek write-verbose helyzetjelentésekkel:
Write-Verbose "Eleje"
$a = 10; $result = 1
Write-Verbose "Közepe $a"
for($i=1; $i -le $a; $i++)
{
$result = $result * $i
Write-Verbose
"`$i = $i, `$result =
$result"
}
Write-Verbose "Vége"
$result
Nézzük, mi történik, ha futtatom:
[7] PS C:\powershell2\scripts> .\verbose.ps1
3628800
Nem sok mindent látunk ezen, mintha ott sem lennének a write-verbose parancsok. Merthogy a write-verbose hatását egy globális változóval, a $VerbosePreference -szel lehet ki- és bekapcsolni:
[9] PS C:\powershell2\scripts> $verbosepreference = "continue"
[10] PS C:\powershell2\scripts> .\verbose.ps1
VERBOSE: Eleje
VERBOSE: Közepe 10
VERBOSE: $i = 1, $result = 1
VERBOSE: $i = 2, $result = 2
VERBOSE: $i = 3, $result = 6
VERBOSE: $i = 4, $result =
24
VERBOSE: $i = 5, $result =
120
VERBOSE: $i = 6, $result =
720
VERBOSE: $i = 7, $result =
5040
VERBOSE: $i = 8, $result =
40320
VERBOSE: $i = 9, $result =
362880
VERBOSE: $i = 10, $result =
3628800
VERBOSE: Vége
3628800
Nyomtatásban esetleg nem látszik, de a VERBOSE: kimenetek szép sárgák, hasonlóan a „Warning” jelzésekhez. Ennek további értékei lehetnek – hasonlóan az $ErrorActionPreference változóhoz – SilentlyContinue (alapérték), Inquire, Stop, valamint PowerShell 3.0-tól kezdődően van még az Ignore lehetőség is.
Ugyanígy működik a write-debug cmdlet is, csak az ő hatását a $DebugPreference változó szabályozza.
Azaz van két, egymástól függetlenül használható kiírató cmdletünk, amelyekkel a szkriptjeink futásának státusát írathatjuk ki a fejlesztés során, majd ha készen vagyunk és meggyőződtünk, hogy minden jól működik, akkor anélkül, hogy a szkriptből ki kellene szedegetni ezeket a státusjelzéseket, egy globális változó beállításával ezek hatását ki tudjuk kapcsolni.
A másik hibaűző módszer, ha lépésenként hajtatjuk végre a szkriptünket és így jobban mg tudjuk figyelni, hogy merre jár. Azaz nem kell minden sor után egy write-debug sort beiktatni. Erre is lehetőséget biztosít a PowerShell a Set-PSDebug cmdlettel. Nézzük meg a szintaxisát:
[22] PS C:\powershell2\scripts> (get-help set-psdebug).syntax
Set-PSDebug [-Off] [<CommonParameters>]
Set-PSDebug [-Step] [-Strict] [-Trace <int>] [<CommonParameters>]
A második változatban kapcsolhatjuk be a hibakeresési üzemmódot. A –trace paraméter lehetséges értékei:
0: kikapcsoljuk a nyomkövetést
1: csak a végrehajtás során az aktuális sor számát jelzi
2: a sorok száma mellett a változók értékadását is jelzi
Az előző szkriptet fogom használni, kicsit átalakítva:
$a = 3; $result = 1
for($i=1; $i
-le $a; $i++)
{
$result
=
$result * $i
}
$result
És akkor nézzük a –Trace 1 opcióval a futását:
PS C:\> Set-PSDebug -Trace 1
DEBUG: 2+
$foundSuggestion = <<<<
$false
DEBUG: 4+
if <<<< ($lastError -and
DEBUG: 15+ $foundSuggestion <<<<
Érdekes módon, saját magát is „megnyomkövette”!
PS C:\> .\_munka\powershell2\scripts\psdebug.ps1
DEBUG: 1+
<<<< .\_munka\powershell2\scripts\psdebug.ps1
DEBUG: 1+ $a = <<<< 3; $result = 1
DEBUG: 1+ $a = 3; $result = <<<< 1
DEBUG: 2+ for <<<< ($i=1; $i -le $a;
$i++)
DEBUG: 4+
$result = <<<<
$result * $i
DEBUG: 4+
$result = <<<<
$result * $i
DEBUG: 4+
$result = <<<<
$result * $i
DEBUG: 6+ $result <<<<
6
DEBUG: 2+
$foundSuggestion = <<<<
$false
DEBUG: 4+
if <<<< ($lastError -and
DEBUG: 15+ $foundSuggestion <<<<
Láthatjuk, hogy szépen kiírja az éppen végrehajtott sorok számát, meg az ottani parancssor tartalmát is. Nézzük a –Trace 2 opció hatását:
PS C:\> Set-PSDebug -Trace 2
DEBUG: 1+
<<<< Set-PSDebug -Trace 2
DEBUG: 2+
$foundSuggestion = <<<<
$false
DEBUG: ! SET $foundSuggestion = 'False'.
DEBUG: 4+
if <<<< ($lastError -and
DEBUG: 15+ $foundSuggestion <<<<
PS C:\> .\_munka\powershell2\scripts\psdebug.ps1
DEBUG: 1+
<<<< .\_munka\powershell2\scripts\psdebug.ps1
DEBUG: ! CALL function 'psdebug.ps1' (defined in file
'C:\_munka\powershell2\scripts\psdebug.ps1')
DEBUG: 1+ $a = <<<< 3; $result = 1
DEBUG: ! SET $a = '3'.
DEBUG: 1+ $a = 3; $result = <<<< 1
DEBUG: ! SET $result = '1'.
DEBUG: 2+ for <<<< ($i=1; $i -le $a;
$i++)
DEBUG: ! SET $i = '1'.
DEBUG: 4+
$result = <<<<
$result * $i
DEBUG: ! SET $result = '1'.
DEBUG: ! SET $i = '2'.
DEBUG: 4+
$result = <<<<
$result * $i
DEBUG: ! SET $result = '2'.
DEBUG: ! SET $i = '3'.
DEBUG: 4+
$result = <<<<
$result * $i
DEBUG: ! SET $result = '6'.
DEBUG: ! SET $i = '4'.
DEBUG: 6+ $result <<<<
6
DEBUG: 2+
$foundSuggestion = <<<<
$false
DEBUG: ! SET $foundSuggestion = 'False'.
DEBUG: 4+
if <<<< ($lastError -and
DEBUG: 15+ $foundSuggestion <<<<
Láthatjuk, hogy az előzőek mellett még a szkripthívást is látjuk, és minden egyes változó értékadását is az új értékekkel együtt. Ha még a –step paramétert is használjuk, akkor minden sor végrehajtására külön rákérdez:
[37] PS C:\powershell2\scripts> Set-PSDebug -Trace 1 -step
[38] PS C:\powershell2\scripts> .\psdebug.ps1
Continue with this operation?
1+ .\psdebug.ps1
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help
(default is "Y"):y
DEBUG: 1+ .\psdebug.ps1
Continue with this operation?
1+ $a = 3; $result = 1
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help
(default is "Y"):y
DEBUG: 1+ $a = 3; $result = 1
Continue with this operation?
1+ $a = 3; $result = 1
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help
(default is "Y"):s
Ekkor választhatunk, hogy tovább lépünk egy sorral (Yes), folytatjuk a futtatást végig (Yes to All), megszakítjuk a futtatást (No és No to All). A Suspend „némán” állítja meg a futtatást, nem lesz hibajelzés a rendkívüli programmegszakításról.
A –strict kapcsoló használata után csak olyan változókra hivatkozhatunk, amelynek korábban adtunk kezdőértéket:
[41] PS C:\powershell2\scripts> Set-PSDebug -strict
[42] PS C:\powershell2\scripts> $c=1
[43] PS C:\powershell2\scripts> $c
1
[44] PS C:\powershell2\scripts> $d
The variable
'$d' cannot be retrieved because it has not been set.
At line:1
char:3
+ $d
<<<<
+ CategoryInfo : InvalidOperation: (d:Token) [],
RuntimeExceptio
n
+ FullyQualifiedErrorId :
VariableIsUndefined
A fenti példában a $c értékadás után lekérdezhető volt, szemben a $d-vel, amire hibajelzést kaptunk, hiszen nem adtam neki kezdőértéket.
Ha már nincs szükségünk a Set-PSDebug-gal beállított hibafelderítő üzemmódokra, akkor az –Off kapcsolóval kapcsolhatjuk ki ezeket:
[45] PS C:\powershell2\scripts> Set-PSDebug -off
[46] PS C:\powershell2\scripts> $d
[47] PS C:\powershell2\scripts>
Itt látható, hogy már nem okozott hibajelzést az értékadás nélküli $d-re való hivatkozás.
A Set-PSDebug egy globális hatású cmdlet, azaz beállítása után minden futtatási környezet (scope) azonos módon „szigorú” lesz. A PowerShell 2.0-ban egy új cmdletünk is van, a Set-StrictMode . Ez már csak az adott futtatási környezetre és alkörnyezetekre hat, így sokkal könnyebb az éppen hibafelderítés alatt álló részekre beállítani a szigorúságot. A –Version 1 paraméterrel hasonlóan működik, mint a korábban látott szigorú üzemmód. De a –Version 2-vel még szigorúbb lesz:
PS C:\Users\tibi> Set-StrictMode -Version 2
PS C:\Users\tibi> $a="hkh"
PS C:\Users\tibi> $a.yyy
Property
'yyy' cannot be found on this object. Make sure that it exists.
At line:1
char:4
+ $a.
<<<< yyy
+ CategoryInfo : InvalidOperation: (.:OperatorToken)
[], Runtime
Exception
+ FullyQualifiedErrorId :
PropertyNotFoundStrict
Az első sorban beállítottam ezt a fajta szigorúságot a PowerShell 2.0 kompatibilis szintre. Ezután az $a sztringemnek nem létező yyy tulajdonságára történő hivatkozás hibát okozott. Ez nagyon nagy szolgálat a hibakeresésben az 1.0 verzióhoz képest!
Ezen kívül van még egy szolgáltatása ennek a Set-StrictMode beállításnak. Ez pedig a hagyományos, Visual Basic-szerű függvényhívás szintaxisra való figyelmeztetés:
PS C:\Users\tibi> function valami ($x,$y){$x+$y}
PS C:\Users\tibi> valami(1,2)
The function
or command was called as if it were a method. Parameters should b
e separated
by spaces. For information about parameters, see the about_Paramet
ers Help
topic.
At line:1
char:7
+ valami
<<<< (1,2)
+ CategoryInfo : InvalidOperation: (:) [],
RuntimeException
+ FullyQualifiedErrorId :
StrictModeFunctionCallWithParens
Itt a nagyon egyszerű függvényemet úgy próbáltam hívni, mintha például egy Excel függvény, vagy egy metódus lenne: nincs szóköz a függvény neve és a zárójel között, és eleve zárójel van. Ez valószínűsíthetően hiba, így megkapjuk a hibajelzést.
Ha még ez sem lenne elég, akkor még mélyebbre áshatunk a Trace-Command cmdlet segítségével. Igazából ez nem biztos, hogy a rendszergazdák mindennapos eszköze lesz, inkább azon fejlesztők tudnak profitálni használatából, akik cmdleteket fejlesztenek.
Nézzük a szintaxisát:
[5] PS I:\>(get-help trace-command).syntax
Trace-Command [-Command] <string> [-ArgumentList <Object[]>] [-Name] <string[]
> [[-Option] {None | Constructor | Dispose | Finalizer | Method | Property | D
elegates | Events | Exception | Lock | Error | Errors | Warning | Verbose | Wr
iteLine | Data | Scope | ExecutionFlow | Assert | All}] [-Debugger] [-FilePath
<string>] [-Force] [-InputObject <psobject>] [-ListenerOption {None | Logical
OperationStack | DateTime | Timestamp | ProcessId | ThreadId | Callstack}] [-P
SHost] [<CommonParameters>]
Trace-Command [-Expression] <scriptblock> [-Name] <string[]> [[-Option] {None
| Constructor | Dispose | Finalizer | Method | Property | Delegates | Events |
Exception | Lock | Error | Errors | Warning | Verbose | WriteLine | Data | Sc
ope | ExecutionFlow | Assert | All}] [-Debugger] [-FilePath <string>] [-Force]
[-InputObject <psobject>] [-ListenerOption {None | LogicalOperationStack | Da
teTime | Timestamp | ProcessId | ThreadId | Callstack}] [-PSHost] [<CommonPara
meters>]
Huh, nem túl egyszerű! Az látható, hogy alapvetően kétfajta dolgot lehet vele vizsgálni: parancsokat és kifejezéseket. Alapvetően mi (rendszergazdák) két esetben használhatjuk: az első eset, amikor a parancsok, kifejezések meghívásának körülményeit szeretnénk tisztázni, a másik, amikor a paraméterek átadásának körülményeit. Nézzünk példát az elsőre:
PS C:\> Trace-Command commanddiscovery {dir c:\} -pshost
DEBUG: CommandDiscovery
Information: 0 : Looking up command: dir
DEBUG: CommandDiscovery
Information: 0 : Alias found: dir
Get-ChildItem
DEBUG: CommandDiscovery
Information: 0 : Cmdlet found: Get-ChildItem
Microsoft.PowerShell.Commands.GetChildItemCommand,
Microsoft.PowerShell.Commands.Management,
Version=1.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35
Directory: C:\
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 2009.07.14. 5:20 PerfLogs
d-r-- 2010.02.13. 15:39 Program Files
d-r-- 2010.02.25. 14:31 Program Files (x86)
…
Itt a trace-command cmdletet a „commanddiscovery” névvel hívtam meg. Mögötte ott van az a kifejezés szkriptblokk formában, amit elemeztetni szeretnék, majd megadom, hogy az eredményt a konzolra írja ki.
Az elemzés felderíti, hogy a PowerShell a „dir” álnevet hogyan párosítja a Get‑ChildItem cmdlettel, megnézi, hogy nem tréfáltuk-e meg, és nem készítettünk-e ilyen névvel valamilyen függvényt vagy szűrőt (filter). Ha nem, akkor kiírja, hogy melyik PSSnapIn-ben van ez a cmdlet definiálva és utána már csak a futásának eredményét látjuk. A trace-command ezzel az üzemmódjával tehát akkor jön jól nekünk, ha mindenféle aliast, függvényt, szkriptet és egyebeket definiálunk, gyanítjuk, hogy esetleg többször is ugyanolyan névvel, és amikor futtatunk valamit, nem értjük, hogy mi is fut le valójában.
Nézzük a paraméterátadás felderítését:
PS C:\> Trace-Command –name parameterbinding –expression {get-process powershell} -pshost
DEBUG: ParameterBinding
Information: 0 : BIND NAMED cmd line args
[Get-Process]
DEBUG: ParameterBinding
Information: 0 : BIND POSITIONAL cmd line args
[Get-Process]
DEBUG: ParameterBinding
Information: 0 : BIND arg
[powershell] to
parameter [Name]
DEBUG: ParameterBinding Information:
0 : Binding collection parameter
Name: argument type
[String], parameter type [System.String[]], collection
type Array, element type
[System.String], no coerceElementType
DEBUG: ParameterBinding
Information: 0 : Creating array
with element
type [System.String] and 1
elements
DEBUG: ParameterBinding
Information: 0 : Argument type
String is not
IList, treating this as
scalar
DEBUG: ParameterBinding
Information: 0 : Adding scalar
element of type
String to array position 0
DEBUG: ParameterBinding
Information: 0 : Executing
VALIDATION
metadata:
[System.Management.Automation.ValidateNotNullOrEmptyAttribute]
DEBUG: ParameterBinding
Information: 0 : BIND arg
[System.String[]] to
param [Name] SUCCESSFUL
DEBUG: ParameterBinding
Information: 0 : MANDATORY PARAMETER CHECK on cmdlet
[Get-Process]
DEBUG: ParameterBinding
Information: 0 : CALLING BeginProcessing
DEBUG: ParameterBinding Information: 0 : CALLING EndProcessing
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
382 24 85184 90136 595 4,52 2912 powershell
Itt a trace-command név paramétere „parameterbinding”, mögötte megint a vizsgálandó kifejezés, majd az, hogy a konzolra küldje az eredményt. Látjuk, hogy a „PowerShell” karaktersorozatot a „Name” paraméterhez fűzte. Ez nekünk természetesnek tűnik, de azért ez nem ilyen triviális. Nézzük, vajon mit is vár a get‑process „Name” paraméter gyanánt:
[10] PS I:\>(get-help get-process).syntax
Get-Process [[-name] <string[]>] [<CommonParameters>]
Láthatjuk a súgóban, hogy egy sztringtömböt vár a get-process. Az elemzés további része annak lépéseit mutatja, hogy hogyan csinál a PowerShell a sima sztringből sztringtömböt, és hogy ezzel a paraméterrel sikeresen vette a cmdlet mindhárom feldolgozási fázisát is, a begin és az end részt is.
A másik eset, hogy nem értjük, hogy egy cmdletnek vagy szkriptnek átadott paraméter miért nem jó, miért nem úgy értelmezi a kifejezés, mint ahogy mi szeretnénk. Ez utóbbi demonstrálására nézzünk egy példát, amelyben annak nézünk utána, hogy ha egy gyűjteményt adunk át a get-member-nek, akkor az vajon miért a gyűjtemény elemeinek tagjellemzőit adja vissza, miért nem a gyűjteményét:
PS C:\> Trace-Command parameterbinding {1, "kettő" | gm} -pshost
DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [Get-Member]
DEBUG: ParameterBinding Information: 0 : BIND POSITIONAL cmd line args
[Get-Member]
DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet
[Get-Member]
DEBUG: ParameterBinding Information: 0 : CALLING BeginProcessing
DEBUG: ParameterBinding Information: 0 : BIND PIPELINE object to parameters:
[Get-Member]
DEBUG: ParameterBinding Information: 0 : PIPELINE object TYPE =
[System.Int32]
DEBUG: ParameterBinding Information: 0 : RESTORING pipeline parameter's
original values
DEBUG: ParameterBinding Information: 0 : Parameter [InputObject] PIPELINE
INPUT ValueFromPipeline NO COERCION
DEBUG: ParameterBinding Information: 0 : BIND arg [1] to parameter
[InputObject]
DEBUG: ParameterBinding Information: 0 : BIND arg [1] to param
[InputObject] SUCCESSFUL
DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet
[Get-Member]
DEBUG: ParameterBinding Information: 0 : BIND PIPELINE object to parameters:
[Get-Member]
DEBUG: ParameterBinding Information: 0 : PIPELINE object TYPE =
[System.String]
DEBUG: ParameterBinding Information: 0 : RESTORING pipeline parameter's
original values
DEBUG: ParameterBinding Information: 0 : Parameter [InputObject] PIPELINE
INPUT ValueFromPipeline NO COERCION
DEBUG: ParameterBinding Information: 0 : BIND arg [kettő] to parameter
[InputObject]
DEBUG: ParameterBinding Information: 0 : BIND arg [kettő] to param
[InputObject] SUCCESSFUL
DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet
[Get-Member]
DEBUG: ParameterBinding Information: 0 : CALLING EndProcessing
…
Láthatjuk, hogy a get-member hogyan dolgozza fel a csővezetéken érkező különböző objektumokat, hogyan emelődnek át az argumentumok InputObject-té. Elsőként a parancsértelmező megnézi, hogy vannak-e név szerinti paraméterek a parancssorban, majd ellenőrzi a pozíció alapján történő paraméterátadást. Ezután ellenőrzi, hogy a kötelező paraméterek meg vannak-e adva, a get-member-nek nincs ilyenje. Mindezek után veszi észre, hogy csővezetékben adom át a paramétereket, így annak kezelése történik meg, először az „1” lesz behelyettesítve „InputObject” paraméterként, majd a „kettő”.
A PowerShell 2.0-ban jelent meg beépített, nyelvi szinten definiált (azaz nem a szerkesztő eszköz vagy a konzol szintjén definiált) megszakítási pontok létrehozásának lehetősége. Ilyen megszakítási pontokat a PSBreakPoint főnévvel rendelkező cmdletekkel lehet kezelni:
[16] PS C:\> Get-Command -Noun psbreakpoint
CommandType Name Definition
----------- ---- ----------
Cmdlet Disable-PSBreakpoint Disable-PSBreakpoint [-Brea...
Cmdlet Enable-PSBreakpoint Enable-PSBreakpoint [-Id] <...
Cmdlet Get-PSBreakpoint Get-PSBreakpoint [[-Script]...
Cmdlet Remove-PSBreakpoint Remove-PSBreakpoint [-Break...
Cmdlet Set-PSBreakpoint Set-PSBreakpoint [-Script] ...
Ezeket vagy szkriptek szintjén lehet kezelni, ha pozícióhoz (sor, oszlop) rendeljük a megszakítást, vagy a konzol ablakban is használhatjuk ad-hoc módon, ha parancsokhoz vagy változóhoz rendeljük ezeket.
Az első lépés a megszakítási pont létrehozása, ehhez a Set-PSBreakPoint cmdletet használhatjuk. Nézzük ennek szintaxisait:
[17] PS C:\> (get-help Set-PSBreakpoint).syntax
Set-PSBreakpoint -Command <string[]> [[-Script] <string[]>] [-Action <scriptbl
ock>] [<CommonParameters>]
Set-PSBreakpoint [-Script] <string[]> [-Line] <Int32[]> [[-Column] <int>] [-Ac
tion <scriptblock>] [<CommonParameters>]
Set-PSBreakpoint -Variable <string[]> [[-Script] <string[]>] [-Mode {Read | Wr
ite | ReadWrite}] [-Action <scriptblock>] [<CommonParameters>]
Nézzük az utolsó opciót, amikor is változót figyelünk. Látható, hogy három üzemmód van: olvasást, írást vagy mindkettőt figyeljük. Az alapérték az írás figyelése:
PS C:\> Set-PSBreakpoint -Variable a
ID Script Line Command Variable Action
-- ------ ---- ------- -------- ------
0 a
PS C:\> $a = 8
Entering debug mode. Use h
or ? for help.
Hit Variable breakpoint on
'$a' (Write access)
$a = 8
[DBG]: PS C:\>>> ?
s, stepInto Single step (step into functions, scripts, etc.)
v, stepOver Step to next statement (step over functions, scripts, etc.
)
o, stepOut Step out of the current function, script, etc.
c, continue Continue execution
q, quit Stop execution and exit the debugger
k, Get-PSCallStack Display call stack
l, list List source code for the current script.
Use "list" to start from the current line, "list <m>"
to start from line <m>, and "list <m> <n>" to list <n>
lines starting from line <m>
<enter> Repeat last command if it was stepInto, stepOver or list
?, h Displays this help message
For instructions about how to customize your debugger prompt, type "help about_
prompt".
[DBG]: PS C:\>>>
A fenti példában az „a” változó manipulálására kérek megszakítást a [13]-as sorban. Majd értéket adok $a-nak, erre rögtön debug, azaz hibakereső üzemmódba lép a prompt. Itt számos parancs áll rendelkezésünkre, amelyeket a kérdőjellel ki is listáztam. Látható, hogy megváltozott a prompt is, a sor elején a [DBG] előtag látható. Ebben a promptban bármit csinálhatunk, futtathatunk parancsokat vagy a fenti, speciális hibakezelő parancsokat alkalmazhatjuk.
Ha most kiolvasom a $a értékét, akkor a következőket látom:
[DBG]: PS C:\>>> $a
1
A [14]-es sorral ellentétben az $a értéke még a korábbi, azaz a megszakítási pontok mindig a feltétel megjelenése előtti ponton állítják le a futást. Kilépni a C, azaz Continue-val lehet ebből az üzemmódból. A megszakítási pontokat megszűntetni a következő kifejezéssel lehet:
[23] PS C:\> Get-PSBreakpoint | Remove-PSBreakpoint
A PSBreakPointok is „scope”-pal rendelkeznek, azaz ha egy olyan függvényt vagy szkriptet futtatok, amelyben szintén van $a változó, akkor a felsőbb scope-okban definiált megszakítási pontok erre is hatni fognak. Ennek bemutatására egy kis előkészítést végeztem:
[38] PS C:\> $a = 123
[39] PS C:\> function belső ($a){"Ez egy belső a: $a"}
[40] PS C:\> Set-PSBreakpoint -Variable a -Mode readwrite
ID Script Line Command Variable Action
-- ------ ---- ------- -------- ------
6 a
Van tehát egy $a külső változóm, egy „belső” nevű függvényem, amiben van egy belső $a változó, majd definiálok egy megszakítási pontot az „a” változóhoz mind írásra, mind olvasásra. Nézzük, mi történik, ha meghívom a belső függvényt:
[41] PS C:\> belső 8
Hit Variable breakpoint on
'$a' (ReadWrite access)
function belső ($a){"Ez egy belső a: $a"}
[DBG]: PS C:\>>> $a
8
[DBG]: PS C:\>>> $global:a
123
A megszakítási pont feléledt, beléptem a hibakeresési üzemmódba. Az $a-t kiolvasva megkaptam a paraméterátadás uráni 8-at, azaz a hibakeresés scope-ja a függvény. Viszont elérem akár a külső $a-t is a $global:a formátum segítségével.
Azt, hogy pontosan hol is vagyok, a „k”, azaz Get-PSCallStack belső paranccsal tudom megjeleníteni:
[DBG]: PS C:\>>> k
Command Arguments Location
------- --------- --------
belső {a=8} prompt
prompt {} prompt
Ezt a listát alulról fölfele kell értelmezni, azaz a prompton (azaz konzolon) belül vagyok, azon belül a belső függvényben. A Location oszlopban prompt szerepel, hiszen a függvényem is a promptban (konzolon) született, nem pedig egy fájlban van.
Nézzünk még érdekesebb megszakítási pontot, azt a fajtát, ahol az parancshoz van rendelve. Ennek kivesézéséhez nézzünk egy rekurzív függvényt, ami faktoriálist számol:
[43] PS C:\> function factor ($i)
>> {
>> if($i -gt 2){
>> $i* (factor ($i-1))
>> }
>> else {$i}
>> }
>>
[44] PS C:\> Set-PSBreakpoint -Command factor
ID Script Line Command Variable Action
-- ------ ---- ------- -------- ------
7 factor
[45] PS C:\> factor 6
Hit Command breakpoint on
'factor'
factor 6
[DBG]: PS C:\>>> $i
[DBG]: PS C:\>>>
Ugye a faktoriálist úgy lehet számolni, hogy ha a paraméter nagyobb, mint 2, akkor a faktoriális az úgy képezhető, hogy az aktuális szám szorozva az eggyel kisebb szám faktoriálisa. Ha pedig a szám 2 vagy kisebb, akkor maga a szám. Ezután felállítottam a megszakítási pontot, hozzárendelve a „factor” nevű parancshoz, ami a függvényem. Majd meghívtam ezt, 6 faktoriálisát keresem. Rögtön meg is jelent a megszakítás. Kiolvasva a függvényem belső $i változóját azonban nem kaptam semmit, hiszen én még „kívül” vagyok. Ezt a hívási lista ellenőrzésével is láthatom:
[DBG]: PS C:\>>> k
Command Arguments Location
------- --------- --------
prompt {} prompt
Azaz én még mindig a konzolon vagyok, ott meg nincs $i változó definiálva. Belelépni a függvénybe az „s”, azaz stepInto paranccsal lehet:
[DBG]: PS C:\>>> s
if($i -gt 2){
[DBG]: PS C:\>>> $i
6
Itt már van $i. Folytatni lehet a „c”, azaz „Continue”, folytatással:
[DBG]: PS C:\>>> c
Hit Command breakpoint on
'factor'
$i* (factor ($i-1))
[DBG]: PS C:\>>> k
Command Arguments Location
------- --------- --------
factor {i=6} prompt
prompt {} prompt
Ha hosszabb a szkriptünk és nem teljesen egyértelmű, hogy hol is vagyunk, akkor kérhetjük a forráskód megjelenítését az „l”, azaz List paranccsal:
[DBG]: PS C:\>>> l
1: function factor ($i)
2: {
3:* if($i -gt 2){
4: $i* (factor ($i-1))
5: }
6: else {$i}
7: }
Ha elég sokat folytatjuk a felderítést, akkor akár ilyen hívási mélységet is elérünk:
[DBG]: PS C:\>>> k
Command Arguments Location
------- --------- --------
factor {i=2} prompt
factor {i=3} prompt
factor {i=4} prompt
factor {i=5} prompt
factor {i=6} prompt
prompt {} prompt
Miután nagyon rövidke a függvényem, így a lépésenkénti végrehajtás: „s” – stepInto, a továbblépés az adott szinten: „v”, stepOver és a kilépés az adott szintről: „o”, stepOut lehetőségek között nincs sok látványbeli különbség.
Nézzünk még egy lehetőséget, a megszakítási pontokhoz valamilyen szkriptblokkot is rendelhetünk a debug prompt helyett:
[56] PS C:\> Set-PSBreakpoint -Command factor -Action {write-host "Most az i: $
i"}
ID Script Line Command Variable Action
-- ------ ---- ------- -------- ------
9 factor write-host "M...
[57] PS C:\> factor 5
Most az i:
Most az i: 5
Most az i: 4
Most az i: 3
120
Ezzel tehát nem szakad meg a futás, hanem a megszakítási feltétel teljesülésekor lefut az –Action paraméternek átadott szkriptblokk.
Utoljára hagytam a szerintem legkevésbé érdekes lehetőséget, amikor is a megszakítási pontot a szkriptünk valamely pozíciójához rendeljük. Íme, a szkriptem:
96 . ábra A fact.ps1 szkript
Ennek a szkriptnek az 5. sorához rendelem a megszakítási pontot:
[59] PS C:\> Set-PSBreakpoint -Script C:\munka\fact.ps1 -Line 5
ID Script Line Command Variable Action
-- ------ ---- ------- -------- ------
10 fact.ps1 5
[60] PS C:\> C:\munka\fact.ps1 5
Hit Line breakpoint on
'C:\munka\fact.ps1:5'
fact.ps1:5 $i* (factor ($i-1))
[DBG]: PS C:\>>> $i
5
[DBG]: PS C:\>>> v
Hit Line breakpoint on
'C:\munka\fact.ps1:5'
fact.ps1:5 $i* (factor ($i-1))
[DBG]: PS C:\>>> $i
4
[DBG]: PS C:\>>> o
Hit Line breakpoint on 'C:\munka\fact.ps1:5'
fact.ps1:5 $i* (factor ($i-1))
[DBG]: PS C:\>>> c
120
Látható, hogy ugyanúgy működik a folyamat, mint a többi esetben.
Egy szkripthez akár több és többfajta megszakítási pontot is rendelhetünk, illetve az –Action részbe intelligenciákat helyezhetünk, amivel önműködően tudjuk vezérelni a hibakeresést:
[62] PS C:\> Set-PSBreakpoint -Script C:\munka\fact.ps1 -Line 5 -Action {if($i
-gt 3){Write-Host "i: $i"}else{continue} }
ID Script Line Command Variable Action
-- ------ ---- ------- -------- ------
11 fact.ps1 5 if($i -gt 3){...
[63] PS C:\> C:\munka\fact.ps1 5
i: 5
i: 4
120
Ebben a példában automatikusan tovább lép a futás, ha az $i értéke kisebb egyenlő, mint 3. A PSBreakPoint-okat még hatástalaníthatjuk, és újra élesíthetjük a Disabe-PSBreakpoint és az Enable‑PSBreakpoint cmdletekkel.
Az „igazi” megszakítási pontokat csak elmentett szkriptek esetén használhatjuk. Azonban van még egy praktikus lehetőség a programunk megszakítására ad-hoc kifejezéseink tesztelésére, főleg a grafikus felületen:
$host.EnterNestedPrompt()
Például:
97 . ábra Egyedi megszakítási pont beillesztése
Látható, hogy az első sor után bejutottam egy alpromtba, ahol bármilyen műveletet végre tudunk hajtani, majd az Exit kulcsszóval lehet folytatni a kódunk futtatását.
A PowerShell 5.0-ban megjelent egy új cmdlet, a Wait-Debugger , amellyel az előző EnterNestedPrompt()-nál is fejlettebb, a megszakítási pontokkal azonosan működő hibakaresési lehetőséghez jutunk. Az alábbi példában egy hatsoros kifejezést másoltam be a konzolba, amelyben a 3. sorban van egy Wait-Debugger cmdlet:
PS C:\> Write-Host eleje
>>> $a = 1
>>> Wait-Debugger
>>> $a
>>> get-date
>>> Write-Host vége
eleje
At line:4 char:1
+ $a
+ ~~
[DBG]: PS C:\>>
Látható, hogy ennek hatására a 4. sornál megállt a végrehajtás és egy alpromptba jutottam, ahol a szokásos konzolbeli egybetűs parancsokat adhatom ki a hibafelderítéshez:
[DBG]: PS C:\>> l
1: Write-Host eleje
2: $a = 1
3: Wait-Debugger
4:* $a
5: get-date
6: Write-Host vége
[DBG]: PS C:\>> v
1
At line:5 char:1
+ get-date
+ ~~~~~~~~
[DBG]: PS C:\>> $a
1
[DBG]: PS C:\>> c
2015. szeptember 19. 17:27:53
vége
Ha már hibakeresésre fanyalodunk, akkor valószínű soksoros szkriptekkel dolgozunk, amelyeket a grafikus felületen könnyebb készíteni. Így érdemes megnézni, hogy itt hogyan lehet ezekkel a lehetőségekkel élni. A PowerShell ISE felületén a fenti lehetőségek egy része a menübe ki van vezetve:
98 . ábra A PowerShell ISE hibakeresési lehetőségei
A sorokhoz kötött megszakítási pontot könnyű elhelyezni és kezelni. Parancshoz vagy változóhoz ugyanúgy kell, mint a konzolon. Ha már belekerültünk a hibakeresési üzemmódba, akkor különböző gyorsbillentyűkkel lehet vezérelni a továbblépést.
PowerShell 5.0 ISE-ben jelent meg a „Break All” lehetőség (Ctr + B), amellyel megállítható az éppen futó szkript és ugyanúgy folyhat a hibafelderítés, mintha megszakítási pontot helyeztünk volna el. Ezzel a gyanúsan sokáig futó szkriptjeinket tudjuk megállítani és belepillantani az eseményekbe.
Ugyan ezzel a módszerrel elmentetlen „szkripteket” is meg tudunk állítani, természetesen itt is több szolgáltatást kapunk, ha a szkriptünk el van mentve, hiszen ilyenkor az éppen aktuális sor sárga hátteret kap:
99 . ábra Megállítás megszakítási pont nélkül Ctrl+B-vel
A fenti példában a végtelen ciklusomat elsőként még elmentetlenül állítottam meg, ilyenkor világoskékkel az adott sor megjelenik a kimeneti ablakban. Ellenben, ha a szkriptünk el van mentve, akkor látható, hogy a sárga háttérrel a szkriptszerkesztő részben az aktuális sor ki van emelve. Azaz Ctrl+B hatására a futás megállt, hibakereső üzemmódba került az ISE és mindaz a lehetőség rendelkezésünkre áll, mint ami a megszakítási pontok elérésekor is elérhető, azaz kiolvashatjuk az aktuális változókat, léptethetjük a futtatást soronként, stb.
Ugyanez a konzolon is elérhető, csak itt nem a Ctrl+B-t kell megnyomni, hanem a Ctrl+Break -et:
PS C:\> while($true){get-date;Start-Sleep 2}
2015. október 4. 18:23:50
2015. október 4. 18:23:52
Entering debug mode. Use h or ? for help.
At line:1 char:7
+ while($true){get-date;Start-Sleep 2}
+ ~~~~~
[DBG]: PS C:\>> $a
[DBG]: PS C:\>> exit
2015. október 4. 18:24:25
2015. október 4. 18:24:27
Visszatérni a futáshoz akár a hagyományos exit paranccsal tudunk.