-
-
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, $_) } | |
} |
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
I works for me. Maybe you need to load
PSReadLine
, I'm not really sure.