-
-
Save backerman/2c91d31d7a805460f93fe10bdfa0ffb0 to your computer and use it in GitHub Desktop.
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, $_) } | |
} |
@backerman Thank you for sharing!
@elazarcoh I also wanted to use it with my SSH config file but additionally support the Include
feature AND support host name aliases:
# We are assuming that all SSH config files are named *config and
# are correctly included in the main .ssh/config file.
$hosts = Get-Content $(Get-ChildItem -Path ${Env:HOMEPATH}\.ssh -Filter *config -Recurse).FullName `
| Select-String -Pattern "^Host " `
| ForEach-Object { $_ -replace "host ", "" -split " " } `
| Sort-Object -Unique
And if you want the SSH hostname autocompletion to be persistent for all PowerShell instances you have to paste it to your PowerShell Profile.
Open the PowerShell Profile file for all users on the current host by executing the following with administrator rights in a PowerShell:
if (!(Test-Path -Path $PROFILE.AllUsersCurrentHost)) {
New-Item -ItemType File -Path $PROFILE.AllUsersCurrentHost -Force
};
notepad $PROFILE.AllUsersCurrentHost
@countzero Support for Include
directly. I guess it can be used recursively, if you need it.
using namespace System.Management.Automation
function Get-Hosts($configFile) {
Get-Content $configFile `
| Select-String -Pattern "^Host "
| ForEach-Object { $_ -replace "host ", "" }
| Sort-Object -Unique
}
Register-ArgumentCompleter -CommandName ssh, scp, sftp -Native -ScriptBlock {
param($wordToComplete, $commandAst, $cursorPosition)
$sshDir = "${Env:HOMEPATH}\.ssh"
$hosts = Get-Content "$sshDir\config" `
| Select-String -Pattern "^Include "
| ForEach-Object { $_ -replace "include ", "" }
| ForEach-Object { Get-Hosts "$sshDir/$_" } `
$hosts += Get-Hosts "$sshDir\config"
$hosts = $hosts | 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
}
}
$hosts `
| Where-Object { $_ -like "${textToComplete}*" } `
| ForEach-Object { [CompletionResult]::new((&$generateCompletionText($_)), $_, [CompletionResultType]::ParameterValue, $_) }
}
Does this still work? I've tried adding this to my Powershell profile but no tab completion for any of my ssh known hosts or hosts in the config file.
I works for me. Maybe you need to load PSReadLine
, I'm not really sure.
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.
using namespace System.Management.Automation
### SSH autocompletion
Function Get-Hosts($configFile) {
Get-Content $configFile `
| Select-String -Pattern "^Host " `
| ForEach-Object { $_ -replace "host ", "" } `
| Sort-Object -Unique `
}
Register-ArgumentCompleter -CommandName ssh, scp, sftp -Native -ScriptBlock {
param($wordToComplete, $commandAst, $cursorPosition)
$sshDir = "${Env:HOMEPATH}\.ssh"
$hosts = Get-Content "$sshDir\config" `
| Select-String -Pattern "^Include " `
| ForEach-Object { $_ -replace "include ", "" } `
| ForEach-Object { Get-Hosts "$sshDir/$_" } `
$hosts += Get-Hosts "$sshDir\config"
$hosts = $hosts | 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
}
}
$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
, 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.
Awesome! It works perfectly! Thanks you! This reminds me to renew my Shell scripting
If you want to simplify the script and just want to use the host-aliases without the user@
part in your ssh command, you can use the following script:
using namespace System.Management.Automation
$script = {
param($wordToComplete)
Get-Content ${Env:HOMEPATH}\.ssh\config `
| Select-String -Pattern "^Host " `
| ForEach-Object { $_ -replace "host ", "" -split " " } `
| Sort-Object -Unique `
| Where-Object { $_ -like "$wordToComplete*" } `
| ForEach-Object { "$_" }
}
Register-ArgumentCompleter -CommandName ssh,scp,sftp -ScriptBlock $script
This autocompletes for example ssh du⭾
to ssh dummy
which will internally calls ssh admin@dummy.com
, given the following ~\.ssh\config
:
Host dummy
Hostname dummy.com
User admin
Thanks!
I wanted to use with the .ssh/config, So I changed to: