While developing scripts and functions, you'll often find yourself running into behavior that you didn't intend. This is a natural part of software development, and the path to diagnosing these issues is the fine art known as debugging.
For the simplest of problems, a well-placed call
to Write-Host can answer many of your questions. Did
your script get to the places you thought it should? Were the variables
set to the values you thought they should be?
Once problems get more complex, print-style debugging quickly becomes cumbersome and unwieldy. Rather than continually modifying your script to diagnose its behavior, you can leverage PowerShell's much more extensive debugging facilities to help you get to the root of the problem.
becomes cumbersome and unweildy. <
PS > Set-PsBreakPoint .\Invoke-ComplexDebuggerScript.ps1 -Line 14
ID Script Line Command Variable Action
-- ------ ---- ------- -------- ------
0 Invoke-Comple... 14
PS > .\Invoke-ComplexDebuggerScript.ps1
Calculating lots of complex information
1225
89
Entering debug mode. Use h or ? for help.
Hit Line breakpoint on
'Z:\Documents\CookbookV2\chapters\current\PowerShellCookbook\Invoke-Comple
xDebuggerScript.ps1:14'
Invoke-ComplexDebuggerScript.ps1:14 $dirCount = 0
PS > ?
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 li
st
?, h Displays this help message
For instructions about how to customize your debugger prompt, type "help ab
out_prompt".
PS > k
Command Arguments Location
------- --------- --------
HelperFunction {} Invoke-ComplexDebugge...
Invoke-ComplexDebugge... {} Invoke-ComplexDebugge...
prompt {} promptBy
leveraging strict mode, you can often save yourself from writing bugs in
the first place. Once you discover an issue, script tracing can help you
get a quick overview of the execution flow taken by your script. For
interactive diagnosis, PowerShell's Integrated Scripting Environment (ISE)
offers full-featured graphical debugging support. From the command-line,
the *-PsBreakPoint cmdlets let you investigate your
script when it hits a specific line, condition, or error.
You want to have PowerShell warn you when your script contains an error likely to result in a bug.
Use the Set-StrictMode
cmdlet to place PowerShell in a mode that prevents many of the scripting
errors that tend to introduce bugs.
PS > function BuggyFunction
>> {
>> $testVariable = "Hello"
>> if($testVariab1e -eq "Hello")
>> {
>> "Should get here"
>> }
>> else
>> {
>> "Should not get here"
>> }
>> }
>>
PS > BuggyFunction
Should not get here
PS > Set-StrictMode -Version Latest
PS > BuggyFunction
The variable '$testVariab1e' cannot be retrieved because it has not been set.
At line:4 char:21
+ if($testVariab1e <<<< -eq "Hello")
+ CategoryInfo : InvalidOperation: (testVariab1e:Token) [], RuntimeException
+ FullyQualifiedErrorId : VariableIsUndefinedBy default, PowerShell allows you to assign data to variables you haven't yet created (thereby creating those variables). It also allows you to retrieve data from variables that don't exist—which usually happens by accident and almost always causes bugs. The solution demonstrates this trap, where the L in "variable" was accidentally replaced by the number 1.
To help save you from getting stung by this problem and others like it, PowerShell provides a strict mode that generates an error if you attempt to access a nonexisting variable. Example 14.1, “PowerShell operating in strict mode” demonstrates this mode.
Example 14.1. PowerShell operating in strict mode
PS > $testVariable = "Hello"
PS > $tsetVariable += " World"
PS > $testVariable
Hello
PS > Remove-Item Variable:\tsetvariable
PS > Set-StrictMode -Version Latest
PS > $testVariable = "Hello"
PS > $tsetVariable += " World"
The variable '$tsetVariable' cannot be retrieved because it has not been set.
At line:1 char:14
+ $tsetVariable <<<< += "World"
+ CategoryInfo : InvalidOperation: (tsetVariable:Token) [], RuntimeException
+ FullyQualifiedErrorId : VariableIsUndefinedIn addition to saving you from accessing non-existent variables, strict mode also detects:
Accessing non-existent properties on an object
Calling functions as though they were methods
One unique feature of the
Set-StrictMode cmdlet is the
-Version parameter. As PowerShell releases new
versions of the Set-StrictMode cmdlet, the cmdlet
will become more powerful and detect additional scripting errors.
Because of this, a script that works with one version of strict mode
might no longer work under a later version. If you won't have the
flexibility to modify your script to account for new strict mode rules,
use "-Version 2" as the value of the
-Version parameter.
The Set-StrictMode
cmdlet is scoped, meaning that the strict mode
set in one script or function doesn't impact the scripts or
functions that call it. To temporarily disable strict mode for a
region of script, do so in a new script block:
& { Set-StrictMode -Off; $tsetVariable }For the sake of your script debugging health and sanity, strict mode should be one of the first additions you make to your PowerShell profile.
What about to provide link to chapter about profile customizing.
Get-Help
Set-StrictMode
the section called “Customize Your Shell, Profile, and Prompt”
You want to review the flow of execution taken by your script as PowerShell runs it.
Use the -Trace parameter of
the Set-PsDebug cmdlet to have PowerShell trace your
script as it executes it.
PS > function BuggyFunction
>> {
>> $testVariable = "Hello"
>> if($testVariab1e -eq "Hello")
>> {
>> "Should get here"
>> }
>> else
>> {
>> "Should not get here"
>> }
>> }
>>
PS > Set-PsDebug -Trace 1
PS > BuggyFunction
DEBUG: 1+ <<<< BuggyFunction
DEBUG: 3+ $testVariable = <<<< "Hello"
DEBUG: 4+ if <<<< ($testVariab1e -eq "Hello")
DEBUG: 10+ "Should not get here" <<<<
Should not get hereWhen it comes to simple interactive debugging (as opposed to bug prevention), PowerShell supports several of the most useful debugging features that you might be accustomed to. For the full experience, the Integrated Scripting Environment (ISE) offers a full-fledged graphical debugger. For more information about debugging in the ISE, see the section called “Debug a Script”.
From the command-line, though you still have
access to tracing (through the Set-PsDebug
-Trace statement), stepping (through the Set-PsDebug -Step statement), and environment
inspection (through the $host.EnterNestedPrompt() call). While the
*-PsBreakpoint cmdlets support much more
functionality in addition to these primitives, the
Set-PsDebug cmdlet is useful for some simple
problems.
As a demonstration of these techniques, consider Example 14.2, “A complex script that interacts with PowerShell's debugging features”.
Example 14.2. A complex script that interacts with PowerShell's debugging features
############################################################################# Write-Host "Calculating lots of complex information" $runningTotal = 0 $runningTotal += [Math]::Pow(5 * 5 + 10, 2) Write-Debug "Current value: $runningTotal" Set-PsDebug -Trace 1 $dirCount = @(Get-ChildItem $env:WINDIR).Count Set-PsDebug -Trace 2 $runningTotal -= 10 $runningTotal /= 2 Set-PsDebug -Step $runningTotal *= 3 $runningTotal /= 2 $host.EnterNestedPrompt() Set-PsDebug -off
As you try to determine why this script isn't working as you expect, a debugging session might look like Example 14.3, “Debugging a complex script”.
Example 14.3. Debugging a complex script
PS > $debugPreference = "Continue" PS > Invoke-ComplexScript.ps1 Calculating lots of complex information DEBUG: Current value: 1225 DEBUG: 17+ $dirCount = @(Get-ChildItem $env:WINDIR).Count DEBUG: 17+ $dirCount = @(Get-ChildItem $env:WINDIR).Count DEBUG: 19+ Set-PsDebug -Trace 2 DEBUG: 20+ $runningTotal -= 10 DEBUG: ! SET $runningTotal = '1215'. DEBUG: 21+ $runningTotal /= 2 DEBUG: ! SET $runningTotal = '607.5'. DEBUG: 23+ Set-PsDebug -Step Continue with this operation? 24+ $runningTotal *= 3 [Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"):y DEBUG: 24+ $runningTotal *= 3 DEBUG: ! SET $runningTotal = '1822.5'. Continue with this operation? 25+ $runningTotal /= 2 [Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"):y DEBUG: 25+ $runningTotal /= 2 DEBUG: ! SET $runningTotal = '911.25'. Continue with this operation? 27+ $host.EnterNestedPrompt() [Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"):y DEBUG: 27+ $host.EnterNestedPrompt() DEBUG: ! CALL method 'System.Void EnterNestedPrompt()' PS > $dirCount 296 PS > $dirCount + $runningTotal 1207.25 PS > exit Continue with this operation? 29+ Set-PsDebug -off [Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"):y DEBUG: 29+ Set-PsDebug -off
Together, these interactive debugging features
are bound to help you diagnose and resolve simple problems quickly. For
more complex problems, PowerShell's graphical debugger (in the ISE) and
the *-PsBreakpoint cmdlets are here to help.
For more information about the Set-PsDebug cmdlet, type Get-Help Set-PsDebug. For more information
about setting script breakpoints, see the section called “Set a Script Breakpoint”.
You want PowerShell to enter debugging mode when it executes a specific command, line in your script, or updates a variable.
Use the Set-PsBreakpoint
cmdlet to set a new breakpoint:
Set-PsBreakPoint .\Invoke-ComplexDebuggerScript.ps1 -Line 21 Set-PSBreakpoint -Command Get-ChildItem Set-PsBreakPoint -Variable dirCount
When running a script, a breakpoint is a location (or condition) that causes PowerShell to temporarily pause execution of that script. When it does so, it enters debugging mode. Debugging mode lets you investigate the state of the script, and also gives you fine-grained control over the script's execution.
For more information about interacting with PowerShell's debugging mode, see the section called “Investigate System State While Debugging”.
The Set-PsBreakpoint cmdlet
supports three primary types of breakpoints:
Positional breakpoints (lines, and optionally columns) cause PowerShell to pause execution once it reaches the specified location in the script you identify.
PS > Set-PSBreakpoint -Script .\Invoke-ComplexDebuggerScript.ps1 -Line 21 ID Script Line Command Variable Action -- ------ ---- ------- -------- ------ 0 Invoke-ComplexDebuggerScript.ps1 21 PS > .\Invoke-ComplexDebuggerScript.ps1 Calculating lots of complex information Entering debug mode. Use h or ? for help. Hit Line breakpoint on '(...)\Invoke-ComplexDebuggerScript.ps1:21' Invoke-ComplexDebuggerScript.ps1:21 $runningTotal
When running the debugger from the command line, you can use the section called “Program: Show Colorized Script Content” to determine script line numbers.
Command breakpoints cause PowerShell to
pause execution before calling the specified command. This is
especially helpful for diagnosing in-memory functions, or for
pausing before your script invokes a cmdlet. If you specify the
-Script parameter, PowerShell only pauses when
the command is either defined by that script (as in the case of
dot-sourced functions), or called by that script. Although command
breakpoints do not support the -Line parameter,
you can get the same effect by setting a positional breakpoint on
the script that defines them.
PS > Show-ColorizedContent $profile.CurrentUserAllHosts
(...)
084 | function grep(
085 | [string] $text = $(throw "Specify a search string"),
086 | [string] $filter = "*",
087 | [switch] $rec,
088 | [switch] $edit
089 | )
090 | {
091 | $results = & {
092 | if($rec) { gci . $filter -rec | select-string $text }
093 | else {gci $filter | select-string $text }
094 | }
095 | $results
096 | }
(...)
PS > Set-PsBreakpoint $profile.CurrentUserAllHosts -Line 92 -Column 18
ID Script Line Command Variable
-- ------ ---- ------- --------
0 profile.ps1 92
PS > grep "function grep" *.ps1 -rec
Entering debug mode. Use h or ? for help.
Hit Line breakpoint on 'E:\Lee\WindowsPowerShell\profile.ps1:92, 18'
profile.ps1:92 if($rec) { gci . $filter -rec | select-string $text }
(...)By default, variable breakpoints cause PowerShell to pause execution before changing the value of a variable.
PS > Set-PsBreakPoint -Variable dirCount ID Script Line Command Variable Action -- ------ ---- ------- -------- ------ 0 dirCount PS > .\Invoke-ComplexDebuggerScript.ps1 Calculating lots of complex information 1225 Entering debug mode. Use h or ? for help. Hit Variable breakpoint on '$dirCount' (Write access) Invoke-ComplexDebuggerScript.ps1:23 $dirCount = @(Get-ChildItem $env:WINDIR).Count PS >
In addition to letting you break before it changes the value of a variable, PowerShell also lets you break before it accesses the value of a variable.
Once you have a breakpoint defined, you can
use the Disable-PsBreakpoint and
Enable-PsBreakpoint cmdlets to control how PowerShell
reacts to those breakpoints. If a breakpoint is disabled, PowerShell
does not pause execution when it reaches that breakpoint. To remove a
breakpoint completely, use the Remove-PsBreakpoint
cmdlet.
In addition to interactive debugging, PowerShell also lets you define actions to perform automatically when it reaches a breakpoint. For more information, see the section called “Create a Conditional Breakpoint”.
For more information about PowerShell's
debugging support, Get-Help
about_Debuggers.
You want PowerShell to enter debugging mode as soon as it encounters an error.
Run the Enable-BreakOnError
script to have PowerShell automatically pause script execution when it
encounters an error:
Example 14.4. Enable-BreakOnError.ps1
$GLOBAL:EnableBreakOnErrorLastErrorCount = $error.Count
Set-PSBreakpoint -Command Out-Default -Action {
if($error.Count -ne $EnableBreakOnErrorLastErrorCount)
{
$GLOBAL:EnableBreakOnErrorLastErrorCount = $error.Count
break
}
} When PowerShell generates an error, its final
action is displaying that error to you. This goes through the
Out-Default cmdlet, as does all other PowerShell
output. Knowing this, Example 14.4, “Enable-BreakOnError.ps1” defines
a conditional breakpoint. That breakpoint fires only when the number of
errors in the global $error collection changes from
the last time it checked.
If you don't want PowerShell to break on all
errors, you might just want to set a breakpoint on the last error you
encountered. For that, run Set-PsBreakpointLastError
and then run your script again:
Example 14.5. Set-PsBreakpointLastError.ps1
$lastError = $error[0]
Set-PsBreakpoint $lastError.InvocationInfo.ScriptName `
$lastError.InvocationInfo.ScriptLineNumber
For more information about intercepting stages
of the PowerShell pipeline via the Out-Default
cmdlet, see the section called “Intercept Stages of the Pipeline”. For more
information about conditional breakpoints, see the section called “Create a Conditional Breakpoint”.
For more information about PowerShell's
debugging support, Get-Help
about_Debuggers.
You want PowerShell to enter debugging mode when it encounters a breakpoint, but only when certain other conditions hold true as well.
Use the -Action parameter
to define an action that PowerShell should take when it encounters the
breakpoint. If the action includes a break statement,
PowerShell pauses execution and enters debugging mode.
PS > Get-Content .\looper.ps1
for($count = 0; $count -lt 10; $count++)
{
"Count is: $count"
}
PS > Set-PsBreakpoint .\looper.ps1 -Line 3 -Action {
>> if($count -eq 4) { break }
>> }
>>
ID Script Line Command Variable Action
-- ------ ---- ------- -------- ------
0 looper.ps1 3 ...
PS > .\looper.ps1
Count is: 0
Count is: 1
Count is: 2
Count is: 3
Entering debug mode. Use h or ? for help.
Hit Line breakpoint on 'C:\temp\looper.ps1:3'
looper.ps1:3 "Count is: $count"
PS > $count
4
PS > c
Count is: 4
Count is: 5
Count is: 6
Count is: 7
Count is: 8
Count is: 9Conditional breakpoints are a great way to automate repetitive interactive debugging. When you are debugging an often-executed portion of your script, the problematic behavior often doesn't occur until that portion of your script has been executed hundreds or thousands of times. By narrowing down the conditions under which the breakpoint should apply (such as the value of an interesting variable), you can drastically simplify your debugging experience.
The solution demonstrates a conditional
breakpoint that triggers only when the value of the
$count variable is 4. When the
-Action script block executes a
break statement, PowerShell enters debug mode.
Inside the -Action script
block, you have access to all variables that exist at that time. You can
review them, or even change them if desired.
In addition to being useful for conditional
breakpoints, the -Action script block also proves
helpful for generalized logging or automatic debugging. For example,
consider the following action that logs the text of a line whenever it
reaches it:
PS > cd c:\temp
PS > Set-PsBreakpoint .\looper.ps1 -line 3 -Action {
>> $debugPreference = "Continue"
>> Write-Debug (Get-Content .\looper.ps1)[2]
>> }
>>
ID Script Line Command Variable Action
-- ------ ---- ------- -------- ------
0 looper.ps1 3 ...
PS > .\looper.ps1
DEBUG: "Count is: $count"
Count is: 0
DEBUG: "Count is: $count"
Count is: 1
DEBUG: "Count is: $count"
Count is: 2
DEBUG: "Count is: $count"
(...)When we create the breakpoint, we know which line we've set it on. When we hit the breakpoint, we can simply get the content of the script and return the appropriate line.
For an even more complete example of conditional breakpoints being used to perform code coverage analysis, see the section called “Program: Get Script Code Coverage”.
For more information about PowerShell's
debugging support, Get-Help
about_Debuggers.
Get-Help
about_Debuggers
PowerShell has paused execution after hitting a breakpoint, and you want to investigate the state of your script.
Examine the $PSDebugContext
variable to investigate information about the current breakpoint and
script location. Examine other variables to investigate the internal
state of your script. Use the debug mode commands
(Get-PsCallstack, List, and
others) for more information about how you got to the current
breakpoint, and what source code corresponds to the current
location:
PS > Get-Content .\looper.ps1
param($userInput)
for($count = 0; $count -lt 10; $count++)
{
"Count is: $count"
}
if($userInput -eq "One")
{
"Got 'One'"
}
if($userInput -eq "Two")
{
"Got 'Two'"
}
PS > Set-PsBreakpoint c:\temp\looper.ps1 -Line 5
ID Script Line Command Variable Action
-- ------ ---- ------- -------- ------
0 looper.ps1 5
PS > c:\temp\looper.ps1 -UserInput "Hello World"
Entering debug mode. Use h or ? for help.
Hit Line breakpoint on 'C:\temp\looper.ps1:5'
looper.ps1:5 "Count is: $count"
PS > $PSDebugContext.InvocationInfo.Line
"Count is: $count"
PS > $PSDebugContext.InvocationInfo.ScriptLineNumber
5
PS > $count
0
PS > s
Count is: 0
looper.ps1:3 for($count = 0; $count -lt 10; $count++)
PS > s
looper.ps1:3 for($count = 0; $count -lt 10; $count++)
PS > s
Hit Line breakpoint on 'C:\temp\looper.ps1:5'
looper.ps1:5 "Count is: $count"
PS > s
Count is: 1
looper.ps1:3 for($count = 0; $count -lt 10; $count++)
PS > $count
1
PS > $userInput
Hello World
PS > Get-PsCallStack
Command Arguments Location
------- --------- --------
looper.ps1 {userInput=Hello World} looper.ps1: Line 3
prompt {} prompt
PS > l 3 3
3:* for($count = 0; $count -lt 10; $count++)
4: {
5: "Count is: $count"
PS > When PowerShell pauses your script as it hits a breakpoint, it enters a debugging mode very much like the regular console session you are used to. You can execute commands, get and set variables, and otherwise explore the state of the system.
What makes debugging mode unique, however, is its context. When you enter commands in the PowerShell debugger, you are investigating the live state of the script. If you pause in the middle of a loop, you can view and modify the counter variable that controls that loop. Commands that you enter, in essence, become temporary parts of the script itself.
in essence
In addition to the regular variables available
to you, PowerShell creates a new $PSDebugContext
automatic variable whenever it reaches a breakpoint. The
$PSDebugContext.BreakPoints property holds the
current breakpoint, while the
$PSDebugContext.InvocationInfo property holds
information about the current location in the script:
PS > $PSDebugContext.InvocationInfo
MyCommand :
BoundParameters : {}
UnboundArguments : {}
ScriptLineNumber : 3
OffsetInLine : 40
HistoryId : -1
ScriptName : C:\temp\looper.ps1
Line : for($count = 0; $count -lt 10; $count++)
PositionMessage :
At C:\temp\looper.ps1:3 char:40
+ for($count = 0; $count -lt 10; $count++ <<<< )
InvocationName : ++
PipelineLength : 0
PipelinePosition : 0
ExpectingInput : False
CommandOrigin : InternalFor information about the nesting
of functions and commands that called each other to reach this point
(the "call stack,") type Get-PsCallStack.
If you find yourself continually monitoring a specific variable (or set of variables) for changes, the section called “Program: Watch an Expression for Changes” shows a script that lets you automatically watch an expression of your choice.
After investigating the state of the script, you can analyze its flow of execution through the three stepping commands: step into, step over, and step out. These functions single-step through your script with three different behaviors: entering functions and scripts as you go, skipping over functions and scripts as you go, or popping out of the current function or script (while still executing its remainder.)
For more information about PowerShell's
debugging support, Get-Help
about_Debuggers.
When debugging a script (or even just generally using the shell), you might find yourself monitoring the same expression very frequently. This gets tedious to type by hand, so Example 14.6, “Watch-Expression.ps1” simplifies the task by automatically displaying the value of expressions that interest you as part of your prompt.
Example 14.6. Watch-Expression.ps1
<#
.SYNOPSIS
Updates your prompt to display the values of information you want to track.
.EXAMPLE
PS >Watch-Expression { (Get-History).Count }
Expression Value
---------- -----
(Get-History).Count 3
PS >Watch-Expression { $count }
Expression Value
---------- -----
(Get-History).Count 4
$count
PS >$count = 100
Expression Value
---------- -----
(Get-History).Count 5
$count 100
PS >Watch-Expression -Reset
PS >
#>
param(
[ScriptBlock] $ScriptBlock,
[Switch] $Reset
)
if($Reset)
{
Remove-Item variable:\expressionWatch
return
}
if(-not (Test-Path variable:\expressionWatch))
{
$GLOBAL:expressionWatch = @()
}
$GLOBAL:expressionWatch += $scriptBlock
$oldPrompt = Get-Content function:\prompt
if($oldPrompt -notlike '*$expressionWatch*')
{
$newPrompt = @'
$results = foreach($expression in $expressionWatch)
{
New-Object PSObject -Property @{
Expression = $expression.ToString().Trim();
Value = & $expression
} | Select Expression,Value
}
Write-Host "`n"
Write-Host ($results | Format-Table -Auto | Out-String).Trim()
Write-Host "`n"
'@
$newPrompt += $oldPrompt
Set-Item function:\prompt ([ScriptBlock]::Create($newPrompt))
}
For more information about running scripts, see the section called “Run Programs, Scripts, and Existing Tools”.
When developing a script, testing it (either automatically, or by hand) is a critical step in knowing how well it does the job you think it does. While you can spend enormous amounts of time testing new and interesting variations in your script, how do you know when you are done?
Code coverage is the standard technique to answer this question. You instrument your script so that the system knows what portions it executed, and then review the report at the end to see which portions were not executed. If a portion was not executed during your testing, you have untested code and can improve your confidence in its behavior by adding more tests.
In PowerShell, we can combine two powerful techniques to create a code coverage analysis tool: the tokenizer API, and conditional breakpoints.
First, we use the tokenizer API to discover all of the unique elements of our script: its statements, variables, loops, and more. Each token tells us the line and column that holds it, so we then create breakpoints for all of those line and column combinations.
When we hit a breakpoint, we record that we hit it, and continue.
Once the script completes, we can compare the entire set of tokens against the ones we actually hit. Any tokens that were not hit by a breakpoint represent gaps in our tests.
Example 14.7. Get-ScriptCoverage.ps1
<#
.SYNOPSIS
Uses conditional breakpoints to obtain information about what regions of
a script are executed when run.
.EXAMPLE
PS>$action = { c:\temp\looper.ps1 -UserInput 'One' }
PS>$coverage = Get-ScriptCoverage c:\temp\looper.ps1 -Action $action
PS>$coverage | Select Content,StartLine,StartColumn | Format-Table -Auto
Content StartLine StartColumn
------- --------- -----------
param 1 1
( 1 6
userInput 1 7
) 1 17
... 1 18
... 2 1
... 4 2
... 9 2
... 14 2
Got 'Two' 15 5
... 15 16
} 16 1
... 16 2
This example exercises a 'looper.ps1' script, and supplies it with some
user input. The output demonstrates that we didn't exercise the
"Got 'Two'" statement.
#>
param(
$path,
[ScriptBlock] $action = { & $path }
)
$scriptContent = Get-Content $path
$tokens = [System.Management.Automation.PsParser]::Tokenize(
$scriptContent, [ref] $null)
$tokens = $tokens | Sort-Object StartLine,StartColumn
$GLOBAL:visitedTokens = @()
$breakpoints = foreach($token in $tokens)
{
$breakAction = { $GLOBAL:hitTokens += $token }.GetNewClosure()
Set-PsBreakpoint $path -Line `
$token.StartLine -Column $token.StartColumn -Action $breakAction
}
. $action
$breakpoints | Remove-PsBreakpoint
$visitedTokens = $visitedTokens | Sort-Object -Unique StartLine,StartColumn
Compare-Object $tokens $visitedTokens -Property StartLine,StartColumn -PassThru
Remove-Item variable:\visitedTokens
$hitTokens? Not that I'm affected by it, but $hit looks like an obfuscated curse word. Maybe that can be (un)intentional comedy. Just saying...
Hah! Good one, thanks.
For more information about running scripts, see the section called “Run Programs, Scripts, and Existing Tools”.
No comments yet
Add a comment