630
CHAPTER 15
T
HE
P
OWER
S
HELL
ISE 
AND
DEBUGGER
Equals           Method     bool Equals(System.Object obj)
Focus            Method     System.Void Focus()
GetHashCode      Method     int GetHashCode()
GetLineLength    Method     int GetLineLength(int lineNumber)
GetType          Method     type GetType()
InsertText       Method     System.Void InsertText(string text)
Select           Method     System.Void Select(int startLine, int 
startColumn, int endLine, int endColumn)
SetCaretPosition Method     System.Void SetCaretPosition(int lineNumber, 
int columnNumber)                
ToString         Method     string ToString()
CaretColumn      Property   System.Int32 CaretColumn {get;}
CaretLine        Property   System.Int32 CaretLine {get;}
LineCount        Property   System.Int32 LineCount {get;}
SelectedText     Property   System.String SelectedText {get;}
Text             Property   System.String Text {get;set;}
Looking at the members on this object, you see that there are members available to 
select text in the pane, insert text, and so on. You’re looking for one specific member: 
the 
Text
property itself. This property gives you access to the full text in the output 
pane, which you can save to a file by using redirection:
PS (STA) (22) > $psISE.CurrentPowerShellTab.Output.Text > output.txt
This command copies the contents of the output pane to a file called output.txt. You 
can then use Notepad to look at this file:
PS (STA) (23) > notepad output.txt
The results of this command are shown in figure 15.18.
Figure 15.18 The contents of 
the output buffer, which were 
saved to a file on disk and then 
opened in Notepad
Pdf reverse page order - re-order PDF pages in C#.net, ASP.NET, MVC, Ajax, WinForms, WPF
Support Customizing Page Order of PDF Document in C# Project
rearrange pdf pages in reader; reorder pdf pages
Pdf reverse page order - VB.NET PDF Page Move Library: re-order PDF pages in vb.net, ASP.NET, MVC, Ajax, WinForms, WPF
Sort PDF Document Pages Using VB.NET Demo Code
how to reorder pdf pages; how to move pages in pdf converter professional
E
XTENDING
THE
ISE
631
We used Notepad to display this output rather than use the 
ISE
so you can see both 
the output and the contents of the saved file at the same time. Next, we’ll look at the 
object model for the editor pane and see what you can do with that.
Making changes in the editor pane
You’ve seen how to save the contents of the output pane to a file, but you’re limited 
in what you can do in this pane because it’s read-only. With the editor pane, you’re 
also able to change the contents of the 
Text
property. Let’s see how to do this by 
working through an example where you’ll rename  a variable in the editor buffer. 
Your task will be to replace the 
$tab
variable name in listing 15.1 with the more 
descriptive 
$currentTab
.
Assuming the listing is loaded in the current editor pane, let’s see how lines of the 
function reference this variable. You can do so with the following command:
PS (STA) (110) > $psISE.CurrentFile.Editor.Text -split "`n" -match '\$tab'
$tab = $null
foreach ($tab in $psISE.PowerShellTabs)
if ($tab.DisplayName -eq $bkgnTab)
$tab = $psISE.PowerShellTabs.Add()
$tab.DisplayName = $bkgnTab
while (-not $tab.CanInvoke)
if (-not $tab.CanInvoke)
$tab.Invoke($ScriptBlockToInvoke)
In this command, because the text in the editor buffer is returned as a single file, you 
use the 
-split
operator to split the text at newline characters and then use the 
-match
operator to see the lines containing the pattern. To get a simple count, just 
use this:
PS (STA) (111) > ($psISE.CurrentFile.Editor.Text -split "`n" ` 
>> -match '\$tab').Count 
8
We’ll look at modifying the text in the editor using the object model next. However, 
before we do that, let’s copy the text to another editor buffer. Direct updates to the 
contents of the editor panes aren’t tracked by the normal undo mechanism, but by 
making a copy, you’re providing your own “undo” mechanism in case something goes 
wrong. First, get a reference to the current tab:
$curFile = $psISE.CurrentFile
You’ll use this to get at the contents of the editor. Create a new file tab and save the 
result in the 
$newFile
variable:
$newFile = $psISE.CurrentPowerShellTab.Files.Add()
When you run this command, the tab you just created will become the currently dis-
played file tab. Now you can use the reference to the original file tab to get the text
C# Word: How to Use C# Code to Print Word Document for .NET
document pages in original or reverse order within entire C# Class Code to Print Certain Page(s) of powerful & profession imaging controls, PDF document, image
how to move pages in pdf files; reorder pages pdf
632
CHAPTER 15
T
HE
P
OWER
S
HELL
ISE 
AND
DEBUGGER
source text. Using the 
-replace
operator, you can make the replacement and assign
the result to the 
Text
property on the editor object in the new file tab:
$newFile.Editor.Text = $curFile.Editor.Text -replace '\$tab', '$currentTab'
This command will also set the new buffer to be the current buffer so you can see the 
results of your operation.
Finally, if you want to set current file back to the original file, use this:
$psISE.CurrentPowerShellTab.Files.SetSelectedFile($curFile)
which changes the current file tab back to the original.
In the next section, we’ll look at an example where you work with both tabs and 
editor panes to save the state of the 
ISE
.
Saving the list of open files
With the ability to have multiple session tabs with multiple files open, you can end 
up with a lot of files open, working on different parts of a project. Unfortunately, the 
ISE
doesn’t have a project mechanism to keep track of all these files. But now that you 
know how to work with both session tabs and file tabs, you can write your own tool 
to save a list of the files you’re working with. A function to do this is shown in the fol-
lowing listing.
$PathToSavedISEState =
"~/documents/WindowsPowerShell/SavedISEState.ps1xml" 
function Save-ISEState 
{
$state = foreach ($tab in $psISE.PowerShellTabs)  
{
foreach ($file in $tab.Files)  
{
if ($file.FullPath)
{
@{
Tab = $tab.DisplayName        
DisplayName = $file.DisplayName
FullPath = $file.FullPath
}
}
}
}
$state | Export-Clixml $PathToSavedISEState 
}
This function loops through all of the 
ISE
tabs and then the files in each tab. For the 
file tabs  that have been  saved (and therefore have a file  path), the code builds a 
hashtable containing the information you need to restore the tabs. This data is saved 
to the target file using the 
CliXML
format (essentially equivalent to the way remoting 
serialization works).
Listing15.2    A function that saves the list of currently open files in the ISE
Save list of files
Loop through 
each file in tab
Use hashtable 
for data
E
XTENDING
THE
ISE
633
Now you need a way to read the data back. This task turns out to be quite simple, 
as shown by the following function:
function Get-SavedISEState 
{
Import-Clixml $PathToSavedISEState 
}
Using the 
Import-Clixml
cmdlet, you can re-create the collection of hashtables you 
saved.
Reading the data isn’t as interesting as reloading the files, so the following listing 
shows a function that does this as well.
function Restore-ISEState 
{
$tab = ""
Import-Clixml $PathToSavedISEState |  
foreach {
if ($_.Tab -ne $tab)      
{
$targetTab = $psise.PowerShellTabs.Add()
$tab = $_.Tab
}
$targetTab.Files.Add($_.FullPath)  
}
This function loads the data and then loops through the hashtables. If the name of 
the target tab in the hashtable doesn’t match the current tab name, a new tab is cre-
ated and the file is added to the new tab. This isn’t a perfect solution—it always cre-
ates new tabs—but it shows how this type of operation can be performed.
So far, all of our 
ISE
object model examples have required that you type com-
mands in the command pane to do things. In the next section, you’ll learn how to 
speed things up by adding menu items and hotkeys to execute your extensions.
15.3.5
Adding a custom menu
In this section, you’ll see how to add custom menu items and hotkeys to the 
ISE
. You 
do so by accessing the 
AddOnsMenu
property on the tab object:
$psise.CurrentPowerShellTab.AddOnsMenu
NOTE
This property is associated with a specific tab. This means it 
isn’t possible to add a custom menu item to all tabs through a single 
API 
call. On the other hand, it means that different tabs can have different 
custom menus, allowing for task-specialized tabs. Of course, if you do 
want to add a menu item automatically to each tab, you can put the code 
into your profile, which is run every time a new tab is created.
Listing 15.3    A function that restores the file tabs
Reload saved 
hashtables
Add file to 
current tab
634
CHAPTER 15
T
HE
P
OWER
S
HELL
ISE 
AND
DEBUGGER
Custom menu items are always added as submenus of the Add-ons menu. By default, 
the Add-ons menu isn’t displayed until the first custom menu item is added. When 
this happens, the menu will appear. Start by adding a simple menu item that just 
prints some text to the output pane. The command to do this is
$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add("Say hello!",
{Write-Host "Hello there"}, "Alt+s")
This command adds a submenu item with the name Say hello!, a scriptblock that 
defines the action to take, and a hotkey sequence to invoke the item from the key-
board. Figure 15.19 shows what the added menu item looks like.
In this figure, you can see that executing this command returns the object repre-
senting  this  menu  item.  If  you  try  to  add  another  item  with  the  same  hotkey 
sequence, you’ll get an error:
PS (STA) (26) > $psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add(
"Say hello!", {Write-Host "Hello there"}, "Alt+s")
Exception calling "Add" with "3" argument(s): "The menu 'Say hello!' 
uses shortcut 'Alt+S', which is already 
in use by the menu or editor functionality."
At line:1 char:52 
+ $psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add <<<< 
("Say hello!", {Write-Host "Hello there"}, "Alt+s" 
)
+ CategoryInfo          : NotSpecified: (:) [], 
MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodTargetInvocation
But you can add a second item with the same name as long as there’s no colliding hot-
key sequence associated with it:
PS (STA) (29) > $psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add( 
"Say hello!", {Write-Host "Hello there"}, $null)
Action                   DisplayName       Shortcut       Submenus 
------                   -----------       --------       --------
Write-Host "Hello there" Say hello!                             {} 
Figure 15.19 When you add a custom menu item, it always appears under 
the Add-ons menu. The first time a custom item is added, the Add-ons item 
appears on the menu bar.
E
XTENDING
THE
ISE
635
Adding this second item adds another item to the menu instead of replacing the exist-
ing one, as you can see in figure 15.20.
Although you can’t modify an existing item, you can remove it and add a new 
item to replace it. Items in the submenu collection can be accessed like an array:
PS (STA) (33) > $psISE.CurrentPowerShellTab.AddOnsMenu.Submenus[0]
Action               DisplayName         Shortcut            Submenus 
------               -----------         --------            --------
Write-Host "Hello... Say hello!          System.Windows.I... {}
This  code  returns  the  corresponding menu  item,  which  you’ll  need  in  order  to 
remove the item:
PS (STA) (36) > $mi = $psISE.CurrentPowerShellTab.AddOnsMenu.Submenus[0]
PS (STA) (37) > $psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Remove($mi) 
True
In this example, first you retrieve the menu item and then pass that object to the 
Remove()
method. If you check the Add-ons menu item, you’ll see that the item has 
been removed. If you want to remove all the items, run the following code:
foreach ($item in @($psISE.CurrentPowerShellTab.AddOnsMenu.Submenus)) 
{
$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Remove($item) 
}
This code gathers the collection of menu items to remove and then loops over the 
collection, removing them one at a time.
Figure 15.20 The Add-ons menu can have more than one item with the 
name caption. If you add a second item with the same caption, two items 
with the same caption will be displayed, but you can’t have two items 
with the same shortcut key.
Modifying collections
There’s a trick in this example that’s useful to know. You’d normally expect to be 
able to use the Submenus collection directly in the foreach loop. A problem arises, 
though, when you remove items from the collection you’re cycling through. This 
occurs because the foreach statement uses a .NET enumerator to keep track of 
where you are in the collection. If you remove an item from the collection, the enu-
636
CHAPTER 15
T
HE
P
OWER
S
HELL
ISE 
AND
DEBUGGER
A simpler way to remove all menu items is to call the 
Clear()
method on the 
Sub-
menus
collection:
$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Clear()
But the looping solution is more general in that you can selectively remove menu 
items by adding conditions to the body of the 
foreach
statement.
Let’s make our example a little more sophisticated. Add some code to prompt the 
user for a name to display. You can use the host object’s prompt function to do this:
$sb = {
$name = Read-Host "Enter the name of the person to say hi to"
Write-Host "Hello name!" 
$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add("Say hello!", 
$sb, $null)
This code will cause a message box to be displayed, as you can see in figure 15.21.
The ability to prompt the user for input significantly increases what you can do 
with your extensions. For example, you can add a search tool that wraps around when 
it reaches the end of the editor buffer. But you can try that on your own. Right now, 
we’ll move on to something else that’s also quite useful.
(continued) 
merator won’t match the collection anymore. To avoid problems with inconsistency 
in the collection, an error is raised.
To get around this problem, you need to create an intermediate collection of the sub-
menu  items.  In  PowerShell,  doing so  is  easy:  simply  use the @(...expres-
sion...) operator. This operator evaluates the expression it contains and returns a 
new collection containing the results of that evaluation. Because the new collection 
is a snapshot of the collection you’re changing, the problem goes away.
Figure 15.21 The prompt from the example menu item looks like this.
E
XTENDING
THE
ISE
637
Adding a file checker menu item
Let’s look at a more useful example. In chapter 14, you learned how to use the Power-
Shell tokenizer 
API
to check a script for certain kinds of errors. This ability is pretty 
handy in the 
ISE
, so let’s add it as a menu item. The code for this appears in the fol-
lowing listing.
function Test-Buffer 
{
$text = $psISE.CurrentFile.Editor.Text
$out=$null
$tokens = [management.automation.psparser]::Tokenize($text, [ref] $out)
$out | fl message, @{
n="line";
e = {
"{0} char: {1}" -f $_.Token.StartLine, $_.Token.StartColumn
}
$psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add("Test Buffer",
{Test-Buffer}, "Control+F5")
The code in listing 15.4 adds a custom menu item that will run the script-checking 
code when you press 
C
trl-
F
5 (as opposed to pressing 
F
5, which actually runs the 
script). This turns out to be a surprisingly handy feature. Although the 
ISE
does syn-
tax highlighting, when there’s an error, it just stops the highlighting without showing 
you the error. Using the extension you just added, you can find the syntax errors 
without having to run the script.
A submenu for snippets
We’ll look at one final example where you create a custom nested menu. In this exam-
ple,  you  want  to  create  a  Snippets menu  showing  various  PowerShell  examples. 
Because there are multiple items, you’ll add this as a submenu of the Add-ons menu. 
The next listing shows the necessary code.
$snippetText = @'             
if ($true) { "if-Part" } elseif { "elseif-Part" } else { "else-Part" } 
while ($true) { Write-Host "in while loop" } 
foreach ($i in 1..10) { $i; Write-Host "i is $i" } 
for ($i=1; $i -lt 20; $i++) { $i } 
function basicFunc ($p1, $p2) { $p1 + $p2 } 
'@ -split "`n"                         
$addOns = $psISE.CurrentPowerShellTab.AddOnsMenu 
$snippets = $addOns.Submenus.Add("snippets", $null, $null)
Listing 15.4    Script code that adds a syntax checker to the ISE
Listing 15.5    Adding a Snippets menu
Define snippets
Split on 
newlines
Add top-level 
Snippets menu
638
CHAPTER 15
T
HE
P
OWER
S
HELL
ISE 
AND
DEBUGGER
foreach ($snip in $snippetText ) 
{
[void] $snippets.SubMenus.Add($snip, {    
$psise.CurrentFile.Editor.InsertText($snip)
}.GetNewClosure(),         
$null) 
}
Most of this function is straightforward. You use a here-string to define the snippets 
and then split it into individual lines with the 
-split
operator. Then you define a 
top-level Snippets menu that has no action and add each line as a submenu of the 
top-level menu. The one somewhat unusual thing is the use of a closure (see chapter
11) and then defining the action. This closure captures the value of the 
$snip
vari-
able so the code to insert the text is very simple.
In the first part of this chapter, we introduced the 
ISE
and spent a fair amount of 
time on how to customize the 
ISE
using the object model. Having a more modern 
environment like the 
ISE
makes scripting in PowerShell much easier, but the biggest 
advantage is the integrated debugger. Through the remainder of this chapter, we’ll 
look at how to use the debugging feature built into the 
ISE
to debug your scripts. 
You’ll also see how to use the debugger from the command line and the additional 
features it has to offer. Combined, the 
ISE
and debugger provide powerful tools for 
debugging scripts in a variety of environments.
15.4
P
OWER
S
HELL
SCRIPT
DEBUGGING
FEATURES
This section covers the various tools and techniques for debugging PowerShell scripts. 
PowerShell v1 didn’t include a debugger but did have some limited tracing capabilities. 
Version 2 introduced a much more comprehensive debugger along with graphical 
debugging support in the 
ISE
. We’ll start by looking at the limited (but still useful) trac-
ing features carried over from v1. Then you’ll learn how to debug from the 
ISE
. Finally, 
you’ll see the command-line debugger and the additional capabilities it has to offer.
15.4.1
The Set-PSDebug cmdlet
In chapter 14, we introduced the 
Set-PSDebug
cmdlet and talked about using it to 
set the PowerShell v1 strict mode. In this section, we’ll cover the remaining features, 
tracing and stepping through scripts, that this cmdlet offers. The syntax for this com-
mand is shown in figure 15.22.
The details of each of these features are covered in the following sections.
Add each line 
as submenu
Use closure to 
capture text
Set-PSDebug  [-Trace <Int32>]  [-Step]  [-Strict]
Set-PSDebug  -Off
Set script tracing level:
0 = off, 1 = basic, 2 = full
Turn on strict mode
(see chapter 14) 
Turn on stepping
Turn off all
debugging features
Figure 15.22 The 
Set-PSDebug
cmdlet parameters. This cmdlet can 
be used to enable tracing and step-
ping through an executing script.
P
OWER
S
HELL
SCRIPT
DEBUGGING
FEATURES
639
Tracing statement execution
You turn on basic script tracing as follows:
PS (1) > Set-PSDebug -Trace 1
In this trace mode, each statement executed by the interpreter will be displayed on 
the console as shown:
PS (2) > 2+2 
DEBUG:    1+ 2+2 
PS (3) > $a=3 
DEBUG:    1+ $a=3 
PS (4) > pwd 
DEBUG:    1+ pwd
Path 
----
C:\files
The debugging output is prefixed with the 
DEBUG:
tag and is typically shown in a dif-
ferent color than normal text. Note that the entire script line is displayed. This means 
that if you have a loop all on one line, you’ll see the line repeated:
PS (5) > foreach ($i in 1..3) {"i is $i"}
DEBUG:    1+ foreach ($i in 1..3) {"i is $i"} 
DEBUG:    1+ foreach ($i in 1..3) {"i is $i"} 
i is 1 
DEBUG:    1+ foreach ($i in 1..3) {"i is $i"} 
i is 2 
DEBUG:    1+ foreach ($i in 1..3) {"i is $i"} 
i is 3
In this example, you see the line repeated four times: once for evaluating the expres-
sion 
1..3
in the 
foreach
loop and then once for each iteration of the loop, for a 
total of four times. This is a good reason, even though PowerShell doesn’t require it, 
to write scripts with one statement per line: it can help with debugging, both when 
tracing and when using the debugger to set breakpoints.
Basic tracing doesn’t show you any function calls or scripts you’re executing. First, 
define a function 
foo
:
PS (6) > function foo {"`$args is " + $args}
DEBUG:    1+ function foo {"`$args is " + $args}
And run it in a loop:
PS (7) > foreach ($i in 1..3) {foo $i}
DEBUG:    1+ foreach ($i in 1..3) {foo $i} 
DEBUG:    1+ foreach ($i in 1..3) {foo $i} 
DEBUG:    1+ function foo {"`$args is " + $args} 
$args is 1 
DEBUG:    1+ foreach ($i in 1..3) {foo $i} 
DEBUG:    1+ function foo {"`$args is " + $args}
Documents you may be interested
Documents you may be interested