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:

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