@@ -25,12 +25,16 @@ if (-not $config) {
2525# Get servers from config
2626$servers = @ {}
2727$serverKeyFiles = @ {}
28+ $serverUsers = @ {}
2829if ($config.ssh.servers ) {
2930 foreach ($key in $config.ssh.servers.PSObject.Properties.Name ) {
3031 $servers [$key ] = $config.ssh.servers .$key.hostname
3132 if ($config.ssh.servers .$key.keyFile ) {
3233 $serverKeyFiles [$key ] = $config.ssh.servers .$key.keyFile
3334 }
35+ if ($config.ssh.servers .$key.user ) {
36+ $serverUsers [$key ] = $config.ssh.servers .$key.user
37+ }
3438 }
3539}
3640
@@ -64,13 +68,17 @@ if ($LocalPort -eq 0) {
6468 $LocalPort = $RemotePort
6569}
6670
67- # Resolve hostname and key file
71+ # Resolve hostname, key file, and user
6872$keyFile = $null
73+ $configUser = $null
6974if ($servers.ContainsKey ($Target )) {
7075 $Server = $servers [$Target ]
7176 if ($serverKeyFiles.ContainsKey ($Target )) {
7277 $keyFile = $serverKeyFiles [$Target ]
7378 }
79+ if ($serverUsers.ContainsKey ($Target )) {
80+ $configUser = $serverUsers [$Target ]
81+ }
7482} else {
7583 $Server = $Target
7684}
@@ -81,7 +89,12 @@ $credsDir = Join-Path $scriptDir "creds"
8189# Check for key file authentication
8290$keyFilePath = $null
8391if ($keyFile ) {
84- $keyFilePath = Join-Path $credsDir $keyFile
92+ # Support both absolute paths and relative paths (in creds directory)
93+ if ([System.IO.Path ]::IsPathRooted($keyFile )) {
94+ $keyFilePath = $keyFile
95+ } else {
96+ $keyFilePath = Join-Path $credsDir $keyFile
97+ }
8598 if (-not (Test-Path $keyFilePath )) {
8699 Write-Host " "
87100 Write-Host " Key file not found: $keyFilePath " - ForegroundColor Yellow
@@ -129,19 +142,23 @@ if (-not $keyFile) {
129142 $password = $cred.GetNetworkCredential ().Password
130143} else {
131144 # For key file auth, we need a username from config or credential file
132- $credFile = $config.ssh.credentialFile
133- if ($credFile ) {
134- $credPath = Join-Path $credsDir $credFile
135- if (Test-Path $credPath ) {
136- $cred = Import-Clixml $credPath
137- $username = $cred.UserName
145+ if ($configUser ) {
146+ $username = $configUser
147+ } else {
148+ $credFile = $config.ssh.credentialFile
149+ if ($credFile ) {
150+ $credPath = Join-Path $credsDir $credFile
151+ if (Test-Path $credPath ) {
152+ $cred = Import-Clixml $credPath
153+ $username = $cred.UserName
154+ }
138155 }
139156 }
140157
141158 if (-not $username ) {
142159 Write-Host " "
143160 Write-Host " Username required for key file authentication." - ForegroundColor Yellow
144- Write-Host " Create a credential file with just the username :" - ForegroundColor Cyan
161+ Write-Host " Add 'user' to server config or create a credential file :" - ForegroundColor Cyan
145162 Write-Host " `$ cred = Get-Credential -UserName 'your-ssh-username'" - ForegroundColor Gray
146163 Write-Host " `$ cred | Export-Clixml '.\creds\ssh-credentials.xml'" - ForegroundColor Gray
147164 Write-Host " "
@@ -157,13 +174,23 @@ Write-Host "Press Ctrl+C to close the tunnel" -ForegroundColor Yellow
157174Write-Host " "
158175
159176# Use WSL with sshpass for the tunnel
177+ $useWSL = $false
160178$wsl = Get-Command wsl - ErrorAction SilentlyContinue
161179if ($wsl ) {
180+ # Check if WSL has a distribution installed
181+ $wslCheck = wsl echo " ok" 2>&1
182+ if ($wslCheck -eq " ok" ) {
183+ $useWSL = $true
184+ }
185+ }
186+ if ($useWSL ) {
162187 if ($keyFile ) {
163188 # Convert Windows path to WSL path for key file
164- $wslKeyPath = wsl wslpath - u " '$keyFilePath '"
189+ $escapedPath = $keyFilePath -replace ' \\' , ' /'
190+ $wslKeyPath = (wsl wslpath - u " '$escapedPath '" ).Trim()
191+ Write-Host " Key: $wslKeyPath " - ForegroundColor Gray
165192 # Create SSH tunnel using key file through WSL
166- wsl bash - c " ssh -o StrictHostKeyChecking=no -i $wslKeyPath -N -L ${LocalPort} :${RemoteHost} :${RemotePort} $username @$Server "
193+ wsl bash - c " ssh -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -i ' $wslKeyPath ' -N -L ${LocalPort} :${RemoteHost} :${RemotePort} $username @$Server "
167194 } else {
168195 # Check if sshpass is installed
169196 $hasSshpass = wsl bash - c " command -v sshpass >/dev/null 2>&1 && echo 'yes' || echo 'no'"
@@ -176,27 +203,48 @@ if ($wsl) {
176203 wsl bash - c " SSHPASS='$password ' sshpass -e ssh -o StrictHostKeyChecking=no -N -L ${LocalPort} :${RemoteHost} :${RemotePort} $username @$Server "
177204 }
178205} else {
179- # Fallback to Posh-SSH
180- try {
181- Import-Module Posh- SSH - ErrorAction Stop
206+ # Try native Windows SSH first (available in Windows 10/11)
207+ $nativeSsh = Get-Command ssh.exe - ErrorAction SilentlyContinue
208+ if ($nativeSsh ) {
209+ Write-Host " Using native Windows SSH..." - ForegroundColor Gray
210+ $sshArgs = @ (
211+ " -o" , " StrictHostKeyChecking=no" ,
212+ " -o" , " IdentitiesOnly=yes" ,
213+ " -N" ,
214+ " -L" , " ${LocalPort} :${RemoteHost} :${RemotePort} "
215+ )
182216 if ($keyFile ) {
183- $session = New-SSHSession - ComputerName $Server - KeyFile $keyFilePath - AcceptKey
184- } else {
185- $session = New-SSHSession - ComputerName $Server - Credential $cred - AcceptKey
217+ $sshArgs += @ (" -i" , $keyFilePath )
186218 }
219+ $sshArgs += " $username @$Server "
187220
188- Write-Host " Tunnel established. Keep this window open." - ForegroundColor Green
189- Write-Host " Press Ctrl+C to close..." - ForegroundColor Yellow
190-
191- # Keep tunnel open
192- Start-Sleep - Seconds 999999
193-
194- } catch {
195- Write-Host " Tunnel setup failed: $ ( $_.Exception.Message ) " - ForegroundColor Red
196- exit 1
197- } finally {
198- if ($session ) {
199- Remove-SSHSession - SessionId $session.SessionId | Out-Null
221+ & ssh.exe @sshArgs
222+ } else {
223+ # Fallback to Posh-SSH
224+ try {
225+ Import-Module Posh- SSH - ErrorAction Stop
226+ if ($keyFile ) {
227+ $session = New-SSHSession - ComputerName $Server - KeyFile $keyFilePath - AcceptKey
228+ } else {
229+ $session = New-SSHSession - ComputerName $Server - Credential $cred - AcceptKey
230+ }
231+
232+ # Set up port forwarding
233+ $tunnel = New-SSHLocalPortForward - SSHSession $session - BoundHost " 127.0.0.1" - BoundPort $LocalPort - RemoteAddress $RemoteHost - RemotePort $RemotePort
234+
235+ Write-Host " Tunnel established. Keep this window open." - ForegroundColor Green
236+ Write-Host " Press Ctrl+C to close..." - ForegroundColor Yellow
237+
238+ # Keep tunnel open
239+ Start-Sleep - Seconds 999999
240+
241+ } catch {
242+ Write-Host " Tunnel setup failed: $ ( $_.Exception.Message ) " - ForegroundColor Red
243+ exit 1
244+ } finally {
245+ if ($session ) {
246+ Remove-SSHSession - SessionId $session.SessionId | Out-Null
247+ }
200248 }
201249 }
202250}
0 commit comments