More Efficient PowerShell with PSReadline — Part 6

PowerShell Text Purple hero
We have been spending a ton of time on the great stuff that is in the PSReadline module, which is now a default part of PowerShell. If you are just jumping in, take some time to get caught up on past articles or you will be a little lost.
 

 
Even though the module has been around for awhile, I never paid it much attention. Now that I have dug into it, I can see some terrific value. I think it will make my time at a PowerShell prompt more efficient and enjoyable. I spend most of my day with a PowerShell session running and other than a web browser and email, it is where I am most likely working. I thought I would share some of the things I am using with PSReadline. Some of these are drawn from the sample profile script I mentioned last time. But I have made a few modifications.

Graphical Command History

We already know that PSReadline maintains a historical record outside of PowerShell. You may also recall that in previous versions of PowerShell (and Windows) you used to be able to press F7 to get a graphical popup of recent commands. This was actually the command buffer from the CMD window that was running PowerShell. Regardless, I have a key handler assigned to F7 that uses Out-Gridview to display command history.

Set-PSReadlineKeyHandler -Key F7 -BriefDescription HistoryList -Description "Show command history with Out-Gridview. [$($env:username)]" -ScriptBlock {
    $pattern = $null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$pattern, [ref]$null)
    if ($pattern)
    {
        $pattern = [regex]::Escape($pattern)
    }
    $history = [System.Collections.ArrayList]@(
        $last = ''
        $lines = ''
        foreach ($line in [System.IO.File]::ReadLines((Get-PSReadlineOption).HistorySavePath))
        {
            if ($line.EndsWith('`'))
            {
                $line = $line.Substring(0, $line.Length - 1)
                $lines = if ($lines)
                {
                    "$lines`n$line"
                }
                else
                {
                    $line
                }
                continue
            }
            if ($lines)
            {
                $line = "$lines`n$line"
                $lines = ''
            }
            if (($line -cne $last) -and (!$pattern -or ($line -match $pattern)))
            {
                $last = $line
                $line
            }
        }
    )
    $history.Reverse()
    $command = $history | Select-Object -unique | Out-GridView -Title "PSReadline History - Select a command to insert at the prompt" -OutputMode Single
    if ($command)
    {
        [Microsoft.PowerShell.PSConsoleReadLine]::RevertLine()
        [Microsoft.PowerShell.PSConsoleReadLine]::Insert(($command -join "`n"))
    }
}

My version filters out duplicates. When I press F7 I get something like this:

My F7 Handler (Image Credit: Jeff Hicks)
My F7 Handler (Image Credit: Jeff Hicks)

If I select a command and press OK, the command is inserted at my prompt where I can modify and run it. Or if I type a string like ‘process’ and then press F7, the handler script block will filter commands that match the pattern.
Filtered history by pattern (Image Credit: Jeff Hicks)
Filtered History by Pattern (Image Credit: Jeff Hicks)

Now, I have an easy way to find a previous command.
 

Jump Lists

The other set of handlers I am getting a ton of use from are those that provide “jump list” functionality. With a jump list, I can assign a keyboard marker or shortcut to a directory. I can then invoke the shortcut to rapidly change to that folder. For example, with my PSReadline tools, I can change to a folder like C:\scripts. I then press Ctrl+Alt+J and press the keyboard shortcut I want to assign such as ‘s’. It can be any single keyboard character. I can repeat the process for any folder I am likely to use throughout the day. The data is stored in a hashtable. I also wrote a handler for Alt+J that displays my current assignments in a popup.

My Jump List (Image Credit: Jeff Hicks)
My Jump List (Image Credit: Jeff Hicks)

 
To jump to any directory, I press Ctrl+J and the shortcut key. With this, I can very quickly jump between frequently used directories. Because I want to always have these handlers, I use this code in my PowerShell profile script.

# Ctrl+Alt+j then type a key to mark the current directory.
# Alt+J to show the current shortcuts in a popup
# Ctrj+j then the same key will change back to that directory without
# needing to type cd and won't change the command line.
#pre-populate a global variable
$global:PSReadlineMarks = @{
[char]"s"="c:\scripts"
[char]"d"="c:\users\jeff\documents"
}
Set-PSReadlineKeyHandler -Key Ctrl+Alt+j -BriefDescription MarkDirectory -LongDescription "Mark the current directory. [$($env:username)]" -ScriptBlock {
#press a single character to mark the current directory
    $key = [Console]::ReadKey($true)
    if ($key.keychar -match "\w") {
        $global:PSReadlineMarks[$key.KeyChar] = $pwd
    }
    else {
        [Microsoft.PowerShell.PSConsoleReadLine]::Ding()
        Write-Warning "You entered an invalid character."
        [Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()
    }
}
Set-PSReadlineKeyHandler -Key Ctrl+j -BriefDescription JumpDirectory -LongDescription "Goto the marked directory.[$($env:username)]" -ScriptBlock {
    $key = [Console]::ReadKey()
    $dir = $global:PSReadlineMarks[$key.KeyChar]
    if ($dir)
    {
        cd $dir
        [Microsoft.PowerShell.PSConsoleReadLine]::InvokePrompt()
    }
}
Set-PSReadlineKeyHandler -Key Alt+j -BriefDescription ShowDirectoryMarks -LongDescription "Show the currently marked directories in a popup. [$($env:username)]" -ScriptBlock {
    $data = $global:PSReadlineMarks.GetEnumerator() | Where {$_.key} | Sort key
    $data | foreach -begin {
     $text=@"
Key`tDirectory
---`t---------
"@
     } -process {
     $text+="{0}`t{1}`n" -f $_.key,$_.value
     }
     $ws = New-Object -ComObject Wscript.Shell
     $ws.popup($text,10,"Use Ctrl+J to jump") | Out-Null
     [Microsoft.PowerShell.PSConsoleReadLine]::InvokePrompt()
}

I am pre-populating the hashtable with some values I know I always want but you don’t have to. The bottom line, at least for me, is that PSReadline helps me work much smarter and more efficiently. Anything that saves a few keystrokes here and there or eliminates an error-prone activity like typing (!) is worth my time to learn and leverage.

I hope you found this series interesting. If you are doing anything fun and interesting with PSReadline I would love to hear about it.