-
-
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, $_) } | |
| } |
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 $scriptThis 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 adminTo state the "obvious" but for new users (and for educational purposes) note that this script should be placed in your profile file e.g:
C:\Users\Bob\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
If you have VS code installed you could run code $PROFILE and it should look like:
using namespace System.Management.Automation
# Any other imports you had before
Import-Module posh-sshell
# Then the SSH completion script
Register-ArgumentCompleter -CommandName ssh,scp,sftp -Native -ScriptBlock {
param($wordToComplete, $commandAst, $cursorPosition)
# ... rest of the script
}
# Any other stuff you had in your profile
i found this and it was a great starting point, thanks a lot to the people above :3
i then sunk a little too much time into enhancing it... so here's my version, complete with scp remote-path completion:
using namespace System.Management.Automation
function Get-SSH-Hosts {
param (
[string]$filename = "${env:USERPROFILE}\.ssh\config"
)
if ([System.IO.File]::Exists($filename)) {
Get-Content $filename `
| Select-String -Pattern '^(Include|Host) ' `
| ForEach-Object {
if ($_ -match '^Include ') {
$included = (Join-Path $filename .. ($_ -replace '^Include ', '') -Resolve)
Get-SSH-Hosts $included
} elseif (!($_ -match '\*')) {
($_ -replace '^Host ', '' -split ' ')[0]
}
} `
| Select-Object -Unique
}
}
function Get-SSH-KnownHosts {
Get-Content ${env:USERPROFILE}\.ssh\known_hosts `
| ForEach-Object { ([string]$_).Split(' ')[0] } `
| ForEach-Object { $_.Split(',') }
}
Function Complete-SSH-Host {
param($wordToComplete, $hosts, $generateCompletionText)
$textToComplete = $wordToComplete
# preserve username if specified; only complete hostname
if ($wordToComplete -match '^(?<user>[-\w/\\]+)@(?<host>[-.\w]+)$') {
$textToComplete = $Matches['host']
$generateCompletionText = {
param($hostname)
"$($Matches['user'])@${hostname}:"
}
}
$allMatches = @()
$prefixMatch = @($hosts | Where-Object { $_ -like "${textToComplete}*" })
$allMatches += $prefixMatch
$substringMatch = @($hosts | Where-Object { $_ -notin $allMatches -and $_ -like "*${textToComplete}*" })
$allMatches += $substringMatch
$fuzzyMatchString = ($textToComplete.toCharArray() | Join-String -OutputPrefix '*' -Separator '*' -OutputSuffix '*')
# not quite smart-casing, but matches with same case shall be offered first
$fuzzyMatchCased = @($hosts | Where-Object { $_ -notin $allMatches -and $_ -clike $fuzzyMatchString })
$allMatches += $fuzzyMatchCased
$fuzzyMatchUncased = @($hosts | Where-Object { $_ -notin $allMatches -and $_ -like $fuzzyMatchString })
$allMatches += $fuzzyMatchUncased
$allMatches | ForEach-Object { [CompletionResult]::new((&$generateCompletionText($_)), $_, [CompletionResultType]::ParameterValue, $_) }
}
Register-ArgumentCompleter -CommandName ssh,sftp -Native -ScriptBlock {
param($wordToComplete, $commandAst, $cursorPosition)
$hosts = (Get-SSH-Hosts) + (Get-SSH-KnownHosts) | Sort-Object -Unique
# for now just assume it's a hostname; no command completion
$generateCompletionText = {
param($x)
$x
}
Complete-SSH-Host $wordToComplete $hosts $generateCompletionText
}
Register-ArgumentCompleter -CommandName scp -Native -ScriptBlock {
param($wordToComplete, $commandAst, $cursorPosition)
$hosts = (Get-SSH-Hosts) + (Get-SSH-KnownHosts) | Sort-Object -Unique
if ($wordToComplete -match '^(?<remote>([-\w/\\]+@)?([-.\w]+)):(?<path>.*)$') {
$remote = $Matches['remote']
$pathPrefix = $Matches['path'] -replace "^'", "" -replace "([^\\])'$", '$1'
$generateCompletionText = {
param($path)
$quotedPath = $path
if ($quotedPath -match '\s' -and $quotedPath -notmatch "'") {
$quotedPath = "`'$quotedPath`'"
}
"${remote}:${quotedPath}"
}
# 'Write-Host' here is a hack to make password/TOTP prompts appear on a new line
$files = (Write-Host) + (ssh -oRemoteCommand=none $remote shopt -s dotglob`; ls -1dp "'${pathPrefix}'*")
return $files `
| ForEach-Object {
[CompletionResult]::new((&$generateCompletionText($_)), $_, [CompletionResultType]::ParameterValue, $_)
}
}
$generateCompletionText = {
param($x)
"${x}:"
}
Complete-SSH-Host $wordToComplete $hosts $generateCompletionText
}
Thanks @hoang-himself -- this snippet works perfectly for my use case, much appreciated.