-
-
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, $_) } | |
} |
@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 [email protected]
, given the following ~\.ssh\config
:
Host dummy
Hostname dummy.com
User admin
@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: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: