More PSReadLine Power
Last time, I showed you how to customize PSReadLine in your module by taking advantage of examples in the sample PSReadLine profile script. But since this module has so many options, I thought I'd come back to it one more time and share some other tips and tricks. Let's start with looking at key bindings again.
The PSReadline module is an integral part of PowerShell. It gets loaded automatically when you start PowerShell and pre-defines many keyboard shortcuts. Which is great, because the more you can keep your hands on the keyboard, the more efficient you can be. One keyboard shortcut which I wasn't taking advantage of until Sean Wheeler, the lead docs writer for PowerShell, opened my eyes is Alt+a
.
Suppose you have a command like this entered at your prompt:
Get-Process p* | sort WS -Descending | Select -first 10
Or this is a previous command you've brought back to your prompt using the up arrow key. Pressing Alt+a
will cycle through each parameter value and select it. This makes it very easy to change a parameter value. I love this, because often I want to re-run a command with a different parameter value. Using this keyboard shortcut is the fastest way for me to update the command.
I know that last time I shared some custom key handlers you could add, but I have a few more that you might find helpful.
More Key Handlers
Because of all the writing I do, I often need to copy commands I've run in PowerShell. In the past I've relied on my Copy-HistoryCommand
from the PSScriptTools module. It hasn't been that hard to use the ch
alias. But, this adds that command to my history. If I use a key handler, then nothing gets added to my command history. So I've come up with this key handler.
Set-PSReadLineKeyHandler -Key 'ctrl+c,ctrl+h' -Description 'Copy last command to the Windows clipboard' -ScriptBlock { (Get-History -Count 1).CommandLine | Out-String | Set-Clipboard }
This uses a key combination for Ctrl+c
plus Ctrl+h
which invokes the script block. One potential downside is that this only gets the last command, whereas my function lets me specify a history item by ID. But since I mostly copy the last command, this keyboard shortcut should do the trick. Although, I might change the key combination to make it even easier to type.
You can define a key handler for anything you can run from a command prompt. I normally use my np
alias that starts to Notepad.exe. But I can just as easily create a key handler.
Set-PSReadLineKeyHandler -key 'Alt+F11' -Description 'Launch notepad' -ScriptBlock { notepad.exe }
I would have preferred to use F11
but that is defined as a key binding in Windows Terminal so I made a slight adjustment.
The current version of PSReadline has a key handler to show command help when you press F1
. If your cursor is on a PowerShell command, pressing F1
will display command help in a buffer window. When you quit, you are returned to your command. I thought it would nice to do something similar but show online help.
Set-PSReadLineKeyHandler -Key 'Ctrl+F1' -BriefDescription OnlineCommandHelp -LongDescription 'Open online help for the current command.' -ScriptBlock {
$ast = $null
$tokens = $null
$errors = $null
$cursor = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$ast, [ref]$tokens, [ref]$errors, [ref]$cursor)
#parse out the command using the AST
$commandAst = $ast.FindAll( {
$node = $args[0]
$node -is [System.Management.Automation.Language.CommandAst] -and
$node.Extent.StartOffset -le $cursor -and
$node.Extent.EndOffset -ge $cursor
}, $true) | Select-Object -Last 1
if ($commandAst) {
$commandName = $commandAst.GetCommandName()
if ($commandName) {
$command = $ExecutionContext.InvokeCommand.GetCommand($commandName, 'All')
$Help = $command.HelpUri
$global:cmd = $command
if ($command -is [System.Management.Automation.AliasInfo]) {
#if the command is an alias, resolve to the referenced command
$commandName = $command.ResolvedCommandName
$help = (Get-Command $cmd.ResolvedCommand).helpUri
}
#make sure this is a PowerShell command
if ($help -match '^http') {
Get-Help $commandName -Online
}
}
}
}
The key handler will work with either the command name or its alias.
I showed you last time how to use Get-PSReadlineKeyHandler
to list everything. The command doesn't have any filtering parameters, but you can filter on the output object.
PS C:\> Get-PSReadLineKeyHandler | where Group -eq 'custom' | Sort-Object Key | Select-Object -Property Key,Description
Key Description
--- -----------
Alt+F11 Launch notepad
Ctrl+c,Ctrl+h Copy last command to the Windows clipboard
Ctrl+F1 Open online help for the current command.
Or, you might find it helpful to see all key handlers sorted by key binding.
Get-PSReadLineKeyHandler | Sort-Object Key | Select-Object -Property Key,Function,Description

I thought it would be handy to assign this command to a key handler, Unfortunately, I can't find any way invoke a PSReadline command or method inside a script block that I want to assign to a key binding.
Fine. It is just as easy to wrap the code in a simple function.
Function gkb { Get-PSReadLineKeyHandler | Sort-Object Key | Select-Object -Property Key,Function,Description }
This is a one-line function using a non-standard name. This is for my personal use. Even if I used a Verb-Noun naming convention, I would still end up using the alias so I'll simply use the alias as the name. I'll never use this in a script and no one else will see it outside of this article.
By the way, if you need to delete a key handler, use `Remove-PSReadlineKeyHandler
Remove-PSReadLineKeyHandler -Chord F6
Other Options
One option you might want to tweak is PromptText
. This is a very subtle PSReadLine feature you may not have noticed. As you type a command at the prompt, PowerShell is busy parsing it. If there is an error, PSReadline will catch it and flip the last part of your prompt to red.

You can modify this behavior.
Set-PSReadLineOption -PromptText '>', '!'
Here's the change.

You may not need to adjust this setting, but I wanted you to be aware of it
Finally, and I think I've shown you this before, you may want to adjust the color options for your session. This is the default for one of my PowerShell 7 profiles in Windows Terminal.

Depending on your color scheme, some of these settings might be hard to read. They are easy to change using Set-PSReadLineOption
. You need to define a hashtable of colors. The tricky thing is that the hashtable key is not property you see. You don't use StringColor
, you would use String
. The help is a bit mangled, so let me straighten it out.
- ContinuationPrompt : The color of the continuation prompt.
- Emphasis : The emphasis color. For example, the matching text when searching history.
- Error : The error color. For example, in the prompt.
- Selection : The color to highlight the menu selection or selected text.
- Default : The default token color.
- Comment : The comment token color.
- Keyword : The keyword token color.
- String : The string token color.
- Operator : The operator token color.
- Variable : The variable token color.
- Command : The command token color.
- Parameter :The parameter token color.
- Type : The type token color.
- Number : The number token color.
- Member : The member name token color.
- InlinePrediction : The color for the inline view of the predictive suggestion.
- ListPrediction : The color for the leading
>
character and prediction source name. - ListPredictionSelected : The color for the selected prediction in list view.
With this in mind, I can add code like this into my profile script.
Set-PSReadLineOption -Colors @{
InlinePrediction = $PSStyle.Foreground.Cyan + $PSStyle.Italic
Operator = "`e[38;5;210m"
Type = "`e[38;5;159m"
Parameter = '#AFFF33'
}
The escape character in PowerShell 7 is
e
with a backtick. Or you can use([char]27)
or0x1b
You can use $PSStyle, a console color like green
, an ANSI sequence, or an HTML code
I can run my code snippet any time to update the settings.
Although, there may be situations where you want to export and import settings. Here is an export function I wrote to export color options to a JSON file.
#requires -Version 7.4
Function Export-PSReadLineColorOption {
[cmdletbinding(SupportsShouldProcess)]
[OutputType('None','FileInfo')]
Param(
[Parameter(
Position = 0,
Mandatory,
ValueFromPipeline,
HelpMessage = "Specify the path to the JSON file."
)]
[ValidateNotNullOrEmpty()]
[ValidatePattern(".*\.json$")]
#Validate the parent path exists
[ValidateScript({Split-Path $_ -Parent | Test-Path})]
[string]$Path,
[switch]$PassThru
)
Begin {
Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN ] Starting $($MyInvocation.MyCommand)"
Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN ] Running under PowerShell version $($PSVersionTable.PSVersion)"
$ColorProperties =(Get-PSReadLineOption).PSObject.Properties.where({$_.Name -match 'color'})
} #begin
Process {
Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Exporting PSReadLine Color options to $Path"
$ColorProperties | Select-Object name,value | ConvertTo-Json | Out-File -FilePath $Path -Encoding utf8
} #process
End {
If ($PassThru) {
Get-Item -Path $Path
}
Write-Verbose "[$((Get-Date).TimeOfDay) END ] Ending $($MyInvocation.MyCommand)"
} #end
} #close Export-PSReadLineColorOption
It is easy enough to export all colors to a JSON file.
Export-PSReadLineColorOption -Path c:\work\ps7colors.json -Verbose
You could manually adjust colors in the JSON file, but I'd rather adjust the setting in PowerShell and then re-export.
Of course, you need an import function.
#requires -Version 7.4
Function Import-PSReadLineColorOption {
[cmdletbinding(SupportsShouldProcess)]
[OutputType('None')]
Param(
[Parameter(
Position = 0,
Mandatory,
ValueFromPipeline,
HelpMessage = 'Specify the path to the JSON file.'
)]
[ValidateNotNullOrEmpty()]
[ValidateScript({ Test-Path $_ })]
[ValidatePattern('.*\.json$')]
[string]$Path
)
Begin {
Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN ] Starting $($MyInvocation.MyCommand)"
Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN ] Running under PowerShell version $($PSVersionTable.PSVersion)"
} #begin
Process {
Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Importing values from $Path"
$import = Get-Content -Path $Path -Raw | ConvertFrom-Json
$import | ForEach-Object -Begin { $ColorHash = @{} } -Process {
#use a regular expression to define the proper hashtable key
$key = $($_.Name) -replace '(token)?color'
$ansi = $($_.Value) -replace '\x1b', '`e'
$sample = '{0}{1}{2}' -f $_.Value, $ansi, $PSStyle.Reset
Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Importing $key [$sample] "
$ColorHash.Add($key, $_.Value)
}
If ($PSCmdlet.ShouldProcess($Path, 'Importing PSReadLine Colors')) {
Set-PSReadLineOption -Colors $ColorHash
}
} #process
End {
Write-Verbose "[$((Get-Date).TimeOfDay) END ] Ending $($MyInvocation.MyCommand)"
} #end
} #close Import-PSReadLineColorOption
The function builds a hashtable of color values on-the-fly using a regular expression pattern to use the correct name. I'm also creating a verbose message that shows the ANSI sequence using the ANSI sequence.
$key = $($_.Name) -replace '(token)?color'
$ansi = $($_.Value) -replace '\x1b', '`e'
$sample = '{0}{1}{2}' -f $_.Value, $ansi, $PSStyle.Reset

Summary
The more you use the PSReadLine features, the more you'll wonder why you took so long to adopt them. Customize PSReadLine with settings that make your work easier and even more enjoyable. I'll wait for your questions in the comments.