Invoke-SSHStreamShellCommand - properly wait until expected prompt, not a fixed time
Hello darkoperator, Would it be possible to rewrite the Invoke-SSHStreamShellCommand cmdlet so it is more robust on a real network environment? I mean, not to wait for a hardcoded amount of time and then read all received text so far. The current implementation can lead to:
- empty response (if the remote device takes more than 500ms to start responding and/or a network delay occurs)
- truncated response (if the remote device takes more than 500ms to send the whole content and/or a network delay occurs).
The proper approach would be to wait for the expected prompt. It would suffice to implement it like done in Invoke-SSHStreamExpectAction, using $ShellStream.Expect(), something like this:
function Invoke-SSHStreamShellCommand {
[CmdletBinding()]
[Alias()]
[OutputType([string])] # proper output type
Param (
...
# Number of seconds to wait for a match.
[Parameter(Mandatory=$false,
ValueFromPipelineByPropertyName=$true,
Position=3)]
[int]
$TimeOut = 10
...
Process {
Write-Verbose -Message "Executing command $($Command)."
$ShellStream.WriteLine($Command)
$ShellStream.ReadLine() | Out-Null # trash command echo
Write-Verbose -Message "Matching on pattern $($promptRegEx)"
$out = $ShellStream.Expect($promptRegEx, (New-TimeSpan -Seconds $TimeOut))}
$outputLines = $out.Split("`n")
foreach ($line in $outputLines) {
if ($line -notmatch $promptRegEx) {
$line
}
}
}
}
Thank you! Regards Jose
I had the same problem too and can confirm the fix with Expect() works much better. Previously, I was having duplicate responses for multiple sent commands but now, every command sent has the expected response.
This is the function I came up with. I usually include it in my code after 'Import-Module -Name Posh-SSH' to override the original in the module. I'm glad the Expect() version works better :)
# Custom override of Posh-SSH library's Invoke-SSHStreamShellCommand with Expect
function Invoke-SSHStreamShellCommand {
[CmdletBinding()]
[Alias()]
[OutputType([string])]
Param (
# SSH stream to use for command execution.
[Parameter(Mandatory = $true,
ValueFromPipelineByPropertyName = $true,
Position = 0)]
[Renci.SshNet.ShellStream]
$ShellStream,
# Command to execute on SSHStream.
[Parameter(Mandatory = $true,
ValueFromPipelineByPropertyName = $true,
Position = 1)]
[string]
$Command,
[Parameter(Mandatory = $false,
ValueFromPipelineByPropertyName = $true,
Position=2)]
[Alias("PrompPattern")]
[string]
$PromptPattern = '[\$%#>] $',
# Number of seconds to wait for a match.
[Parameter(Mandatory = $false,
ValueFromPipelineByPropertyName = $true,
Position=3)]
[int]
$TimeOut = 10
)
Begin {
$promptRegEx = [regex]$PromptPattern
}
Process {
Write-Verbose -Message "Executing command $($Command)."
$ShellStream.WriteLine($Command)
$ShellStream.ReadLine() | Out-Null # trash command echo
Write-Verbose -Message "Matching on pattern $($promptRegEx)"
$out = $ShellStream.Expect($promptRegEx, (New-TimeSpan -Seconds $TimeOut))
if ($out -ne $null) {
$outputLines = $out.Split("`n")
foreach ($line in $outputLines) {
if ($line -notmatch $promptRegEx) {
$line
}
}
}
}
End{}
}
Thanks for sharing. The original function was a a example one I wrote for vyos and got the same questions on how to read and write to a stream so much I simply copied it as a command. Will look in to modifying it. Will need to set some time to setup some vms for it given I no longer do SSH automation
Improved the regex and added timeout support. if it still has issues I will switch to expect. Update in release 3.2.2