Last active
April 30, 2024 08:58
-
-
Save backerman/2c91d31d7a805460f93fe10bdfa0ffb0 to your computer and use it in GitHub Desktop.
Enable tab completion for ssh hostnames in PowerShell
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using namespace System.Management.Automation | |
Register-ArgumentCompleter -CommandName ssh,scp,sftp -Native -ScriptBlock { | |
param($wordToComplete, $commandAst, $cursorPosition) | |
$knownHosts = Get-Content ${Env:HOMEPATH}\.ssh\known_hosts ` | |
| ForEach-Object { ([string]$_).Split(' ')[0] } ` | |
| ForEach-Object { $_.Split(',') } ` | |
| Sort-Object -Unique | |
# For now just assume it's a hostname. | |
$textToComplete = $wordToComplete | |
$generateCompletionText = { | |
param($x) | |
$x | |
} | |
if ($wordToComplete -match "^(?<user>[-\w/\\]+)@(?<host>[-.\w]+)$") { | |
$textToComplete = $Matches["host"] | |
$generateCompletionText = { | |
param($hostname) | |
$Matches["user"] + "@" + $hostname | |
} | |
} | |
$knownHosts ` | |
| Where-Object { $_ -like "${textToComplete}*" } ` | |
| ForEach-Object { [CompletionResult]::new((&$generateCompletionText($_)), $_, [CompletionResultType]::ParameterValue, $_) } | |
} |
Thanks to all of you, this is my version that uses ~\.ssh\config
, reads Include
recursively, accepts multiple hosts per line, and filters out hosts with wildcards
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')]
param()
function Get-SSHHost($sshConfigPath) {
Get-Content -Path $sshConfigPath `
| Select-String -Pattern '^Host ' `
| ForEach-Object { $_ -replace 'Host ', '' } `
| ForEach-Object { $_ -split ' ' } `
| Sort-Object -Unique `
| Select-String -Pattern '^.*[*!?].*$' -NotMatch
}
Register-ArgumentCompleter -CommandName 'ssh', 'scp', 'sftp' -Native -ScriptBlock {
param($wordToComplete, $commandAst, $cursorPosition)
$sshPath = "$env:USERPROFILE\.ssh"
$hosts = Get-Content -Path "$sshPath\config" `
| Select-String -Pattern '^Include ' `
| ForEach-Object { $_ -replace 'Include ', '' } `
| ForEach-Object { Get-SSHHost "$sshPath/$_" }
$hosts += Get-SSHHost "$sshPath\config"
$hosts = $hosts | Sort-Object -Unique
$hosts | Where-Object { $_ -like "$wordToComplete*" } `
| ForEach-Object { $_ }
}
I don't use known_hosts
and I don't understand why some people use it, but I made this anyway
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')]
param()
function Get-SSHKnownHost($sshKnownHostsPath) {
Get-Content -Path $sshKnownHostsPath `
| ForEach-Object { $_.split(' ')[0] } `
| Sort-Object -Unique
}
Register-ArgumentCompleter -CommandName 'ssh', 'scp', 'sftp' -Native -ScriptBlock {
param($wordToComplete, $commandAst, $cursorPosition)
$hosts = Get-SSHKnownHost "$env:USERPROFILE\.ssh\known_hosts"
if ($wordToComplete -match '^(?<user>[-\w/\\]+)@(?<host>[-.\w]+)$') {
$hosts | Where-Object { $_ -like "$($Matches['host'].ToString())*" } `
| ForEach-Object { "$($Matches['user'].ToString())@$_" }
}
}
I imagine you can use both of these at the same time, but pattern matching must be changed.
Thank you so much everyone. This is my version that can read "Include" keyword recursively and support glob too
using namespace System.Management.Automation
Register-ArgumentCompleter -CommandName ssh,scp,sftp -Native -ScriptBlock {
param($wordToComplete, $commandAst, $cursorPosition)
function Get-SSHHostList($sshConfigPath) {
Get-Content -Path $sshConfigPath `
| Select-String -Pattern '^Host ' `
| ForEach-Object { $_ -replace 'Host ', '' } `
| ForEach-Object { $_ -split ' ' } `
| Sort-Object -Unique `
| Select-String -Pattern '^.*[*!?].*$' -NotMatch
}
function Get-SSHConfigFileList ($sshConfigFilePath) {
$sshConfigDir = Split-Path -Path $sshConfigFilePath -Resolve -Parent
$sshConfigFilePaths = @()
$sshConfigFilePaths += $sshConfigFilePath
$pathsPatterns = @()
Get-Content -Path $sshConfigFilePath `
| Select-String -Pattern '^Include ' `
| ForEach-Object { $_ -replace 'Include ', '' } `
| ForEach-Object { $_ -replace '~', $Env:USERPROFILE } `
| ForEach-Object { $_ -replace '\$Env:USERPROFILE', $Env:USERPROFILE } `
| ForEach-Object { $_ -replace '\$Env:HOMEPATH', $Env:USERPROFILE } `
| ForEach-Object {
$sshConfigFilePaths += $(Get-ChildItem -Path $sshConfigDir\$_ -File -ErrorAction SilentlyContinue -Force).FullName `
| ForEach-Object { Get-SSHConfigFileList $_ }
}
if (($sshConfigFilePaths.Length -eq 1) -and ($sshConfigFilePaths.item(0) -eq $sshConfigFilePath) ) {
return $sshConfigFilePath
}
return $sshConfigFilePaths | Sort-Object -Unique
}
$sshPath = "$Env:USERPROFILE\.ssh"
$hosts = Get-SSHConfigFileList "$sshPath\config" `
| ForEach-Object { Get-SSHHostList $_ } `
# For now just assume it's a hostname.
$textToComplete = $wordToComplete
$generateCompletionText = {
param($x)
$x
}
if ($wordToComplete -match "^(?<user>[-\w/\\]+)@(?<host>[-.\w]+)$") {
$textToComplete = $Matches["host"]
$generateCompletionText = {
param($hostname)
$Matches["user"] + "@" + $hostname
}
}
$hosts `
| Where-Object { $_ -like "${textToComplete}*" } `
| ForEach-Object { [CompletionResult]::new((&$generateCompletionText($_)), $_, [CompletionResultType]::ParameterValue, $_) }
}
Thanks to all of you, this is my version that uses
~\.ssh\config
, readsInclude
recursively, accepts multiple hosts per line, and filters out hosts with wildcards
Thanks @hoang-himself -- this snippet works perfectly for my use case, much appreciated.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Just adding a note. There are missing backticks ``` in the example. You need to add them at the end of every line begining with a pipe
|
.I think that they have been removed by the markdown parser...
Here is the working version to auto complete the from the
.ssh/config
file.