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.
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:
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.
Now, I have an easy way to find a previous command.
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.
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.