Summary: Microsoft Scripting Guy, Ed Wilson, talks about a better Windows PowerShell console experience by using custom PSReadLine functions.
Microsoft Scripting Guy, Ed Wilson, is here. I was reading an interesting article the other day. The author was talking about movies and history. The author said that in reality it does not matter if a historical movie has any basis in reality at all. The reason is that we really cannot know what things were like—say a thousand years ago. We have glimpses, slivers of light, but by-and-large, a lot of what we perceive is subject to interpretation.
We do not even need to go back a thousand years ago. For example, lots of people do not really think there was a person named William Shakespeare. (Some people think he was Francis Bacon, Christopher Marlowe, or someone else.) We have a lot more evidence for the bard of Stratford-upon-Avon, than what types of horses medieval knights rode into battle.
But one thing that is not subject to interpretation is that before I found PSReadLine editing, using the Windows PowerShell console was often frustrating (with problems in command history for commands that spanned multiple lines). At times, it was even infuriating. (Remember the edit/Tab problem in Windows PowerShell 1.0 where it would erase everything to the end of the line when you pressed Tab complete? If not, you are lucky.) But now that I have PSReadLine, those days are as far gone as ambling Palfreys in A Midsummer Night’s Dream.
Note This is PSReadLine Week. You might also be interested in reading the following posts:
- The Search for a Better PowerShell Console Experience
- A Better PowerShell Command-Line Edit
- Better PowerShell History Management with PSReadLine
- Useful Shortcuts from PSReadLine PowerShell Module
Customizing PSReadLine
One of the real cool things about PSReadLine is how customizable it is. For example, if I wanted to create a custom handler that could clear the command history in PSReadLine. It would look like the following:
Set-PSReadlineKeyHandler -Key Ctrl+w –ScriptBlock { [PSConsoleUtilities.PSConsoleReadLine]::ClearHistory() }
In fact, in the PSReadLine module location (normally in your MY Documents\WindowsPowerShell\Modules\PSReadLine folder), there is a great sample profile. It is called SamplePSReadLineProfile.ps,1 and it contains some great customizations. In can also serve as a point of departure for additional customizations.
The sample profile includes a whole bunch of new key handlers and options:
Set-PSReadLineOption -HistorySearchCursorMovesToEnd
Set-PSReadlineKeyHandler -Key UpArrow -Function HistorySearchBackward
Set-PSReadlineKeyHandler -Key DownArrow -Function HistorySearchForward
Set-PSReadlineKeyHandler -Key Alt+D -Function ShellKillWord
Set-PSReadlineKeyHandler -Key Alt+Backspace -Function ShellBackwardKillWord
Set-PSReadlineKeyHandler -Key Alt+B -Function ShellBackwardWord
Set-PSReadlineKeyHandler -Key Alt+F -Function ShellForwardWord
Set-PSReadlineKeyHandler -Key Shift+Alt+B -Function SelectShellBackwardWord
Set-PSReadlineKeyHandler -Key Shift+Alt+F -Function SelectShellForwardWord
But the way cool things are the matching quotes, parentheses, and braces. It really makes working from the command line much better. There are actually four key handlers that are way cool in the sample profile file. Here is one of them:
Set-PSReadlineKeyHandler -Key '"',"'" `
-BriefDescription SmartInsertQuote `
-LongDescription "Insert paired quotes if not already on a quote" `
-ScriptBlock {
param($key, $arg)
$line = $null
$cursor = $null
[PSConsoleUtilities.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
if ($line[$cursor] -eq $key.KeyChar) {
# Just move the cursor
[PSConsoleUtilities.PSConsoleReadLine]::SetCursorPosition($cursor + 1)
}
else {
# Insert matching quotes, move cursor to be in between the quotes
[PSConsoleUtilities.PSConsoleReadLine]::Insert("$($key.KeyChar)" * 2)
[PSConsoleUtilities.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
[PSConsoleUtilities.PSConsoleReadLine]::SetCursorPosition($cursor - 1)
}
}
Key/command mapping
Here is a table of the default Windows PSReadLine key/command mapping assignments.
Key combination | Command | Meaning |
Enter | AcceptLine | Accept the input or move to the next line ... |
Shift+Enter | AddLine | Move the cursor to the next line without ... |
Escape | RevertLine | Equivalent to undo all edits (clears the ...) |
LeftArrow | BackwardChar | Move the cursor back one character |
RightArrow | ForwardChar | Move the cursor forward one character |
Ctrl+LeftArrow | BackwardWord | Move the cursor to the beginning of the c... |
Ctrl+RightArrow | NextWord | Move the cursor forward to the start of t... |
Shift+LeftArrow | SelectBackwardChar | Adjust the current selection to include t... |
Shift+RightArrow | SelectForwardChar | Adjust the current selection to include t... |
Ctrl+Shift+LeftArrow | SelectBackwardWord | Adjust the current selection to include t... |
Ctrl+Shift+RightArrow SelectNextWord | SelectNextWord | Adjust the current selection to include t... |
UpArrow | PreviousHistory | Replace the input with the previous item ... |
DownArrow | NextHistory | Replace the input with the next item in t... |
Home | BeginningOfLine | Move the cursor to the beginning of the l... |
End | EndOfLine | Move the cursor to the end of the line |
Shift+Home | SelectBackwardsLine | Adjust the current selection to include f... |
Shift+End | SelectLine | Adjust the current selection to include f... |
Delete | DeleteChar | Delete the character under the cursor |
Backspace | BackwardDeleteChar | Delete the character before the cursor |
Ctrl+Spacebar | PossibleCompletions | Display the possible completions without ... |
Tab | TabCompleteNext | Complete the input using the next complete... |
Shift+Tab | TabCompletePrevious | Complete the input using the previous complete... |
Ctrl+v | Paste | Paste text from the system clipboard |
Ctrl+a | SelectAll | Select the entire line. Moves the cursor... |
Ctrl+c | CopyOrCancelLine | Copy or cancel selected text to the clipboard... |
Ctrl+C | Copy | Copy selected region to the system clipboard... |
Ctrl+l | ClearScreen | Clear the screen and redraw the current l... |
Ctrl+r | ReverseSearchHistory | Search history backward interactively |
Ctrl+s | ForwardSearchHistory | Search history forward interactively |
Ctrl+x | Cut | Delete selected region placing deleted t... |
Ctrl+y | Redo | Redo an undo |
Ctrl+z | Undo | Undo a previous edit |
Ctrl+Backspace | BackwardKillWord | Move the text from the start of the current... |
Ctrl+Delete | KillWord | Move the text from the cursor to the end... |
Ctrl+End | ForwardDeleteLine | Delete text from the cursor to the end of... |
Ctrl+Home | BackwardDeleteLine | Delete text from the cursor to the start... |
Ctrl+] | GotoBrace | Go to matching brace |
Ctrl+Alt+? | ShowKeyBindings | Show all key bindings |
Alt+0 | DigitArgument | Start or accumulate a numeric argument to... |
Alt+1 | DigitArgument | Start or accumulate a numeric argument to... |
Alt+2 | DigitArgument | Start or accumulate a numeric argument to... |
Alt+3 | DigitArgument | Start or accumulate a numeric argument to... |
Alt+4 | DigitArgument | Start or accumulate a numeric argument to... |
Alt+5 | DigitArgument | Start or accumulate a numeric argument to... |
Alt+6 | DigitArgument | Start or accumulate a numeric argument to... |
Alt+7 | DigitArgument | Start or accumulate a numeric argument to... |
Alt+8 | DigitArgument | Start or accumulate a numeric argument to... |
Alt+9 | DigitArgument | Start or accumulate a numeric argument to... |
Alt+- | DigitArgument | Start or accumulate a numeric argument to... |
Alt+? | WhatIsKey | Show the key binding for the next chord e... |
F3 | CharacterSearch | Read a character and move the cursor to t... |
Shift+F3 | CharacterSearchBackward | Read a character and move the cursor to t... |
PageUp | ScrollDisplayUp | Scroll the display up one screen |
PageDown | ScrollDisplayDown | Scroll the display down one screen |
Bonus time
Probably the leading community proponent of PSReadLine is Windows PowerShell MVP, Keith Hill. He wrote a really cool blog post: PSReadLine: A Better Line Editing Experience for the PowerShell Console. In fact, this what got me hooked on this great utility. I asked him for his comments about PSReadLine. Here is what he had to say:
"I believe this already made it into my blog post, but I love the new Ctrl+A key binding that selects the entire command. Jason Shirk, the author of PSReadLine, also added a new command, CopyOrCancelLine, which makes Ctrl+c do a regular clipboard copy if there is text selected. If no text is selected, it does the normal Ctrl+c to abort. So to copy a command to the clipboard is the standard ol’ Ctrl+a then Ctrl+c.
"Jason also created a file that is part of the installation called SamplePSReadlineProfile.ps1. It shows how to set up PSReadLine as part of your profile, in additon to providing a number of custom handlers. I use the one for inserting into a here string (Ctrl+Shift+v). Let me tell you, for supporting folks on SO, that is a very handy handler. I’m always copying XML fragments or file content fragments into a here string to try to repro a problem. Here is the script for that handler:
Set-PSReadlineKeyHandler -Key Ctrl+Shift+v `
-BriefDescription PasteAsHereString `
-LongDescription "Paste the clipboard text as a here string" `
-ScriptBlock {
param($key, $arg)
Add-Type -Assembly PresentationCore
if ([System.Windows.Clipboard]::ContainsText())
{
# Get clipboard text - remove trailing spaces, convert \r\n to \n, and remove the final \n.
$text = ([System.Windows.Clipboard]::GetText() -replace "\p{Zs}*`r?`n","`n").TrimEnd()
[PSConsoleUtilities.PSConsoleReadLine]::Insert("@'`n$text`n'@")
}
else
{
[PSConsoleUtilities.PSConsoleReadLine]::Ding()
}
}
"I also use the one for inserting smart quotes, but I don’t insert smart quotes by default. I prefer to invoke that functionality explicitly by typing Ctrl+’ or Ctrl+Shift+’. Here is the script:
Set-PSReadlineKeyHandler -Chord "Ctrl+'","Ctrl+Shift+'" `
-BriefDescription SmartInsertQuote `
-Description "Insert paired quotes if not already on a quote" `
-ScriptBlock {
param($key, $arg)
$line = $null
$cursor = $null
[PSConsoleUtilities.PSConsoleReadline]::GetBufferState([ref]$line, [ref]$cursor)
$keyChar = $key.KeyChar
if ($key.Key -eq 'Oem7') {
if ($key.Modifiers -eq 'Control') {
$keyChar = "`'"
}
elseif ($key.Modifiers -eq 'Shift','Control') {
$keyChar = '"'
}
}
if ($line[$cursor] -eq $keyChar) {
# Just move the cursor
[PSConsoleUtilities.PSConsoleReadLine]::SetCursorPosition($cursor + 1)
}
else {
# Insert matching quotes, move cursor to be in between the quotes
[PSConsoleUtilities.PSConsoleReadLine]::Insert("$keyChar" * 2)
[PSConsoleUtilities.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
[PSConsoleUtilities.PSConsoleReadLine]::SetCursorPosition($cursor - 1)
}
}
"I use the following handler to put parens around the whole line:
Set-PSReadlineKeyHandler -Key 'Ctrl+9' `
-BriefDescription ParenthesizeLine `
-LongDescription "Put parenthesis around the entire line and move the cursor to the end of line" `
-ScriptBlock {
param($key, $arg)
$line = $null
$cursor = $null
[PSConsoleUtilities.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
[PSConsoleUtilities.PSConsoleReadLine]::Replace(0, $line.Length, '(' + $line + ')')
[PSConsoleUtilities.PSConsoleReadLine]::EndOfLine()
}
"I use these last two to skip around a long command line by whole pipe stages:
Set-PSReadlineKeyHandler -Chord Ctrl+\ `
-BriefDescription SearchForwardPipeChar `
-Description "Searches forward for the next pipeline character" `
-ScriptBlock {
param($key, $arg)
[PSConsoleUtilities.PSConsoleReadLine]::CharacterSearch($key, '|')
}
Set-PSReadlineKeyHandler -Chord Ctrl+Shift+\ `
-BriefDescription SearchBackwardPipeChar `
-Description "Searches backward for the next pipeline character" `
-ScriptBlock {
param($key, $arg)
[PSConsoleUtilities.PSConsoleReadLine]::CharacterSearchBackward($key, '|')
}
"This is basically using the character search feature to long forwards (or backwards) for “|" characters. PSReadLine is the bomb!"
More bonus
Jason Shirk is currently working on an update to PSReadLine. It will have a couple of cool new features. Here is Jason’s latest handy custom completion, which is inspired by marks in vim or bookmarks in an editor. You add this to your profile. When you press Ctrl+Shift+j and type one more character, that sets a bookmark to the current directory. To jump back to that directory, type Ctrl+j and the character. Notice that the prompt is updated to show the directory change.
$global:PSReadlineMarks = @{}
Set-PSReadlineKeyHandler -Key Ctrl+Shift+j `
-BriefDescription MarkDirectory `
-LongDescription "Mark the current directory" `
-ScriptBlock {
param($key, $arg)
$key = [Console]::ReadKey($true)
$global:PSReadlineMarks[$key.KeyChar] = $pwd
}
Set-PSReadlineKeyHandler -Key Ctrl+j `
-BriefDescription JumpDirectory `
-LongDescription "Goto the marked directory" `
-ScriptBlock {
param($key, $arg)
$key = [Console]::ReadKey($true)
$dir = $global:PSReadlineMarks[$key.KeyChar]
if ($dir)
{
cd $dir
[PSConsoleUtilities.PSConsoleReadLine]::InvokePrompt()
}
}
That is all there is to customizing PSReadLine. This also concludes PSReadLine Week. Join me tomorrow when Honorary Scripting Guy and Microsoft Windows PowerShell MVP, Sean Kearney, begins a series about using Windows PowerShell with LYNC. It is some good stuff that you do not want to miss. Make plans now—it all begins tomorrow morning.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy