Skip to content

Instantly share code, notes, and snippets.

@Hashbrown777
Last active April 17, 2023 14:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save Hashbrown777/5acd3acfc532eea8c30b752c3885b1d3 to your computer and use it in GitHub Desktop.
Save Hashbrown777/5acd3acfc532eea8c30b752c3885b1d3 to your computer and use it in GitHub Desktop.
A workaround for Windows official openssh client not supporting -f nor -N
#accepts all arguments for ssh except command, -N and -f
#command can be piped in
Filter ssh-f {
$N = $False
if (!$_) {
#the same as -N
$_ = 'read'
$N = $True
}
$_ = 'echo SUCCESS;' + $_
$args += "`"${_}`""
$_ = [Diagnostics.ProcessStartInfo]::new()
$_.CreateNoWindow = $False
$_.UseShellExecute = $False
$_.RedirectStandardInput = $True
$_.RedirectStandardOutput = $True
$_.RedirectStandardError = $True
$_.FileName = 'ssh'
#ArgumentList apparently isn't in my version of .NET/PowerShell
$_.Arguments = $args
$ssh = [Diagnostics.Process]::new()
$ssh.StartInfo = $_
$started=[Collections.ArrayList]@()
$watch = {
if (![String]::IsNullOrEmpty($EventArgs.Data)) {
$Event.MessageData.Add($EventArgs.Data)
}
}
$out = Register-ObjectEvent `
-InputObject $ssh `
-Action $watch `
-EventName 'OutputDataReceived' `
-MessageData $started
$err = Register-ObjectEvent `
-InputObject $ssh `
-Action $watch `
-EventName 'ErrorDataReceived' `
-MessageData $started
$ssh.Start() | Out-Null
$ssh.BeginOutputReadLine()
$ssh.BeginErrorReadLine()
if (!$N) {
$ssh.StandardInput.Close()
}
try {
while (!$ssh.HasExited) {
while ($started.Count) {
if ($started[0] -eq 'SUCCESS') {
$started.RemoveAt(0)
Unregister-Event -SourceIdentifier $out.Name
Unregister-Event -SourceIdentifier $err.Name
$started | Out-Host
$started.Clear()
#if I attempt to `break` or `return` instead of `throw` we hang!! WHAT
#it has to do with `CreateNoWindow = $False` but if it's true ssh can no longer accept PIN code input (which is NOT stdin)
if ($N) {
'Call .StandardInput.Close() to close your session' | Out-Host
}
else {
'You can monitor when your session closes at .HasExited' | Out-Host
}
throw $ssh
}
$started[0] | Out-Host
$started.RemoveAt(0)
}
}
}
catch {
$_.TargetObject
}
}
@Hashbrown777
Copy link
Author

Hashbrown777 commented Nov 5, 2022

Works even without certs, ie it will still successfully ask for passwords or PINs [for PIV cards].
You can call with or without command like so;

For example if you just want to forward ports (equivalent to -N)
$ssh = ssh-f -D 8080 remoteMachine
Then you can close it when you're done via
$ssh.StandardInput.Close()

Or if you're calling a long task to background
$ssh = 'cd projectDir/; ./buildAllAndRunTests.sh' | ssh-f remoteMachine
Where you can monitor whether it has exited via
$ssh.HasExited

You don't need to save it to a variable, you could simply (ssh-d blah blah).Id to print the PID to console and kill/observe it later, but unfortunately it will exit if you close the spawning terminal.

It even works with -v and outputs any logs to console until connection is established, but after that point it will discard everything.
You can edit the gist to forward this to a logfile if you'd like.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment