Private/SSHScriptBuilder.ps1
function SSHScriptBuilder { [CmdletBinding()] Param( [Parameter(Mandatory=$True)] [string]$RemoteHostNameOrIP, [Parameter( Mandatory=$True, ParameterSetName='Local' )] [ValidatePattern("\\")] # Must be in format <RemoteHostName>\<User> [string]$LocalUserName, [Parameter( Mandatory=$True, ParameterSetName='Domain' )] [ValidatePattern("\\")] # Must be in format <DomainShortName>\<User> [string]$DomainUserName, [Parameter( Mandatory=$False, ParameterSetName='Local' )] [string]$LocalPassword, [Parameter( Mandatory=$False, ParameterSetName='Domain' )] [string]$DomainPassword, [Parameter(Mandatory=$False)] [string]$KeyFilePath, [Parameter(Mandatory=$False)] [System.Collections.ArrayList]$SSHScriptArray, # NOTE: $SSHScriptArray will ALWAYS be run BEFORE $ElevatedSSHScriptArray [Parameter(Mandatory=$False)] [System.Collections.ArrayList]$ElevatedSSHScriptArray, # NOTE: $SSHScriptArray will ALWAYS be run BEFORE $ElevatedSSHScriptArray [Parameter(Mandatory=$False)] [string]$ScriptCompleteFlag, # This should be a regex string [Parameter(Mandatory=$False)] [int]$PotentialAdditionalPwdPrompts, [Parameter(Mandatory=$False)] [int]$PwdPromptTimeoutMin = 10, [Parameter(Mandatory=$False)] [int]$PwdPromptDelaySeconds, [Parameter(Mandatory=$False)] [float]$WindowsWaitTimeMin = 2, [Parameter(Mandatory=$False)] [switch]$WindowsTarget ) if ($LocalUserName) { $FullUserName = $($LocalUserName -split "\\")[-1] } if ($DomainUserName) { $DomainNameShort = $($DomainUserName -split "\\")[0] $FullUserName = $($DomainUserName -split "\\")[-1] } $HostNameValue = $RHostIP = @( $RemoteHostNetworkInfo.IPAddressList | Where-Object {$_ -notmatch "^169"} )[0] # This is what we're going for: # ssh -t pdadmin@192.168.2.10 "echo 'ConnectionSuccessful'" [System.Collections.ArrayList]$SSHCmdStringArray = @( 'ssh' '-t' ) if ($KeyFilePath) { $null = $SSHCmdStringArray.Add("-i") $null = $SSHCmdStringArray.Add('"' + $KeyFilePath + '"') } if ($LocalUserName) { $null = $SSHCmdStringArray.Add("$FullUserName@$HostNameValue") } if ($DomainUserName) { $null = $SSHCmdStringArray.Add("$FullUserName@$DomainNameShort@$HostNameValue") } if ($SSHScriptArray) { [System.Collections.ArrayList][array]$AwaitScriptArray = @($SSHScriptArray) | foreach { $FinalLine = $_ -replace [regex]::Escape('\'),'\\' $FinalLine = $FinalLine -replace [regex]::Escape('$'),'\`$' $FinalLine = $FinalLine -replace [regex]::Escape('"'),'\`"\`"' if ($FinalLine -match 'sed.*subsystemline') { $FinalLine = $FinalLine -replace [regex]::Escape('\`"\`"'),'\\\`"' } $FinalLine } #$AwaitScriptArray.Insert($($AwaitScriptArray.Count-1),"echo $ScriptCompleteFlag") #$null = $AwaitScriptArray.Add("echo $ScriptCompleteFlag") $FinalAwaitScript = $AwaitScriptArray -join "; " [System.Collections.ArrayList][array]$ExpectScriptArray = @($SSHScriptArray) | foreach { $FinalLine = $_ -replace [regex]::Escape('\'),'\\\' $FinalLine = $FinalLine -replace [regex]::Escape('$'),'\\\$' $FinalLine = $FinalLine -replace [regex]::Escape('"'),'\\\"' #$FinalLine = $FinalLine -replace [regex]::Escape('\n'),'\\\n"' $FinalLine = $FinalLine -replace [regex]::Escape('['),'\[' $FinalLine = $FinalLine -replace [regex]::Escape(']'),'\]' $FinalLine } #$null = $ExpectScriptArray.Add("echo $ScriptCompleteFlag") # No need for $FinalExpectScript, because we loop through the $ExpectScriptArray to send each command one at a time } if ($ElevatedSSHScriptArray) { [System.Collections.ArrayList][array]$ElevatedAwaitScriptArray = @($ElevatedSSHScriptArray) | foreach { $FinalLine = $_ -replace [regex]::Escape('\'),'\\' $FinalLine = $FinalLine -replace [regex]::Escape('$'),'\`$' $FinalLine = $FinalLine -replace [regex]::Escape('"'),'\`"\`"' if ($FinalLine -match 'sed.*subsystemline') { $FinalLine = $FinalLine -replace [regex]::Escape('\`"\`"'),'\\\`"' } $FinalLine } #$ElevatedAwaitScriptArray.Insert($($ElevatedAwaitScriptArray.Count-1),"echo $ScriptCompleteFlag") #$null = $ElevatedAwaitScriptArray.Add("echo $ScriptCompleteFlag") $FinalElevatedAwaitScript = $ElevatedAwaitScriptArray -join "; " [System.Collections.ArrayList][array]$ElevatedExpectScriptArray = @($ElevatedSSHScriptArray) | foreach { $FinalLine = $_ -replace [regex]::Escape('\'),'\\\' $FinalLine = $FinalLine -replace [regex]::Escape('$'),'\\\$' $FinalLine = $FinalLine -replace [regex]::Escape('"'),'\\\"' #$FinalLine = $FinalLine -replace [regex]::Escape('\n'),'\\\n"' $FinalLine = $FinalLine -replace [regex]::Escape('['),'\[' $FinalLine = $FinalLine -replace [regex]::Escape(']'),'\]' $FinalLine } #$null = $ExpectScriptArray.Add("echo $ScriptCompleteFlag") # No need for $FinalElevatedExpectScript, because we loop through the $ExpectScriptArray to send each command one at a time } if (!$PSVersionTable.Platform -or $PSVersionTable.Platform -eq "Win32NT") { if (!$WindowsTarget) { if ($SSHScriptArray -and !$ElevatedSSHScriptArray) { $SSHCmdString = $($SSHCmdStringArray -join " ") + ' ' + '"bash -c \`"' + $FinalAwaitScript + '\`""' } if (!$SSHScriptArray -and $ElevatedSSHScriptArray) { $SSHCmdString = $($SSHCmdStringArray -join " ") + ' ' + '"sudo bash -c \`"' + $FinalElevatedAwaitScript + '\`""' } if ($SSHScriptArray -and $ElevatedSSHScriptArray) { $SSHCmdString = $($SSHCmdStringArray -join " ") + ' ' + '"bash -c \`"' + $FinalAwaitScript + '\`"; ' + 'sudo bash -c \`"' + $FinalElevatedAwaitScript + '\`""' } } else { $SSHCmdString = $($SSHCmdStringArray -join " ") + ' ' + '"' + $FinalAwaitScript + '"' } Write-Host "`$SSHCmdString is:`n $SSHCmdString" #region >> Await Attempt Number 1 of 2 $PSAwaitProcess = $null $null = Start-AwaitSession Start-Sleep -Seconds 1 $null = Send-AwaitCommand '$host.ui.RawUI.WindowTitle = "PSAwaitSession"' $PSAwaitProcess = $($(Get-Process | Where-Object {$_.Name -eq "powershell"}) | Sort-Object -Property StartTime -Descending)[0] Start-Sleep -Seconds 1 $null = Send-AwaitCommand "`$env:Path = '$env:Path'" Start-Sleep -Seconds 1 $null = Send-AwaitCommand -Command $([scriptblock]::Create($SSHCmdString)) Start-Sleep -Seconds 5 # This will either not prompt at all, prompt to accept the RemoteHost's RSA Host Key, or prompt for a password $SuccessOrAcceptHostKeyOrPwdPrompt = Receive-AwaitResponse [System.Collections.ArrayList]$CheckForExpectedResponses = @() $null = $CheckForExpectedResponses.Add($SuccessOrAcceptHostKeyOrPwdPrompt) $Counter = 0 while (![bool]$($($CheckForExpectedResponses -split "`n") -match [regex]::Escape("Are you sure you want to continue connecting (yes/no)?")) -and ![bool]$($($CheckForExpectedResponses -split "`n") -match "assword.*:") -and ![bool]$($($CheckForExpectedResponses -split "`n") -match $ScriptCompleteFlag) -and $Counter -le 30 ) { $SuccessOrAcceptHostKeyOrPwdPrompt = Receive-AwaitResponse $null = $CheckForExpectedResponses.Add($SuccessOrAcceptHostKeyOrPwdPrompt) if ($CheckResponsesOutput -match "must be greater than zero" -or @($CheckResponsesOutput)[-1] -notmatch "[a-zA-Z]") { break } Start-Sleep -Seconds 1 $Counter++ } if ($Counter -eq 31) { Write-Verbose "SSH via '$($SSHCmdStringArray -join " ")' timed out!" if ($PSAwaitProcess.Id) { try { $null = Stop-AwaitSession } catch { if ($PSAwaitProcess.Id -eq $PID) { Write-Error "The PSAwaitSession never spawned! Halting!" $global:FunctionResult = "1" return } else { if ([bool]$(Get-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue)) { Stop-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue } $Counter = 0 while ([bool]$(Get-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue) -and $Counter -le 15) { Write-Verbose "Waiting for Await Module Process Id $($PSAwaitProcess.Id) to end..." Start-Sleep -Seconds 1 $Counter++ } } } $PSAwaitProcess = $null } } #endregion >> Await Attempt 1 of 2 $CheckResponsesOutput = $CheckForExpectedResponses | foreach {$_ -split "`n"} #region >> Await Attempt 2 of 2 # If $CheckResponsesOutput contains the string "must be greater than zero", then something broke with the Await Module. # Most of the time, just trying again resolves any issues if ($CheckResponsesOutput -match "must be greater than zero" -or @($CheckResponsesOutput)[-1] -notmatch "[a-zA-Z]" -or $CheckResponsesOutput -match "background process reported an error") { if ($PSAwaitProcess.Id) { try { $null = Stop-AwaitSession } catch { if ($PSAwaitProcess.Id -eq $PID) { Write-Error "The PSAwaitSession never spawned! Halting!" $global:FunctionResult = "1" return } else { if ([bool]$(Get-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue)) { Stop-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue } $Counter = 0 while ([bool]$(Get-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue) -and $Counter -le 15) { Write-Verbose "Waiting for Await Module Process Id $($PSAwaitProcess.Id) to end..." Start-Sleep -Seconds 1 $Counter++ } } } } $PSAwaitProcess = $null $null = Start-AwaitSession Start-Sleep -Seconds 1 $null = Send-AwaitCommand '$host.ui.RawUI.WindowTitle = "PSAwaitSession"' $PSAwaitProcess = $($(Get-Process | Where-Object {$_.Name -eq "powershell"}) | Sort-Object -Property StartTime -Descending)[0] Start-Sleep -Seconds 1 $null = Send-AwaitCommand "`$env:Path = '$env:Path'" Start-Sleep -Seconds 1 $null = Send-AwaitCommand -Command $([scriptblock]::Create($SSHCmdString)) Start-Sleep -Seconds 5 # This will either not prompt at all, prompt to accept the RemoteHost's RSA Host Key, or prompt for a password $SuccessOrAcceptHostKeyOrPwdPrompt = Receive-AwaitResponse [System.Collections.ArrayList]$CheckForExpectedResponses = @() $null = $CheckForExpectedResponses.Add($SuccessOrAcceptHostKeyOrPwdPrompt) $Counter = 0 while (![bool]$($SuccessOrAcceptHostKeyOrPwdPrompt -match [regex]::Escape("Are you sure you want to continue connecting (yes/no)?")) -and ![bool]$($SuccessOrAcceptHostKeyOrPwdPrompt -match "assword.*:") -and ![bool]$($SuccessOrAcceptHostKeyOrPwdPrompt -match $ScriptCompleteFlag) -and $Counter -le 30 ) { $SuccessOrAcceptHostKeyOrPwdPrompt = Receive-AwaitResponse $null = $CheckForExpectedResponses.Add($SuccessOrAcceptHostKeyOrPwdPrompt) Start-Sleep -Seconds 1 $Counter++ } if ($Counter -eq 31) { Write-Error "SSH via '$($SSHCmdStringArray -join " ")' timed out!" $global:FunctionResult = "1" #$CheckForExpectedResponses if ($PSAwaitProcess.Id) { try { $null = Stop-AwaitSession } catch { if ($PSAwaitProcess.Id -eq $PID) { Write-Error "The PSAwaitSession never spawned! Halting!" $global:FunctionResult = "1" return } else { if ([bool]$(Get-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue)) { Stop-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue } $Counter = 0 while ([bool]$(Get-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue) -and $Counter -le 15) { Write-Verbose "Waiting for Await Module Process Id $($PSAwaitProcess.Id) to end..." Start-Sleep -Seconds 1 $Counter++ } } } } return } } #endregion >> Await Attempt 2 of 2 $CheckResponsesOutput = $CheckForExpectedResponses | foreach {$_ -split "`n"} # At this point, if we don't have the expected output, we need to fail if ($CheckResponsesOutput -match "must be greater than zero" -or @($CheckResponsesOutput)[-1] -notmatch "[a-zA-Z]" -or $CheckResponsesOutput -match "background process reported an error") { if ($CheckResponsesOutput -match "must be greater than zero" -or @($CheckResponsesOutput)[-1] -notmatch "[a-zA-Z]") { Write-Error "Something went wrong with the PowerShell Await Module! Halting!" } if ($CheckResponsesOutput -match "background process reported an error") { Write-Error "Please check your credentials! Halting!" } $global:FunctionResult = "1" if ($PSAwaitProcess.Id) { try { $null = Stop-AwaitSession } catch { if ($PSAwaitProcess.Id -eq $PID) { Write-Error "The PSAwaitSession never spawned! Halting!" $global:FunctionResult = "1" return } else { if ([bool]$(Get-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue)) { Stop-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue } $Counter = 0 while ([bool]$(Get-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue) -and $Counter -le 15) { Write-Verbose "Waiting for Await Module Process Id $($PSAwaitProcess.Id) to end..." Start-Sleep -Seconds 1 $Counter++ } } } } return } # Now we should either have a prompt to accept the host key, a prompt for a password, or it already worked... if ($CheckResponsesOutput -match [regex]::Escape("Are you sure you want to continue connecting (yes/no)?")) { $null = Send-AwaitCommand "yes" Start-Sleep -Seconds 3 # This will either not prompt at all or prompt for a password $SuccessOrAcceptHostKeyOrPwdPrompt = Receive-AwaitResponse [System.Collections.ArrayList]$CheckExpectedSendYesOutput = @() $null = $CheckExpectedSendYesOutput.Add($SuccessOrAcceptHostKeyOrPwdPrompt) $Counter = 0 while (![bool]$($($CheckExpectedSendYesOutput -split "`n") -match "assword.*:") -and ![bool]$($($CheckExpectedSendYesOutput -split "`n") -match $ScriptCompleteFlag) -and $Counter -le 30 ) { $SuccessOrAcceptHostKeyOrPwdPrompt = Receive-AwaitResponse $null = $CheckExpectedSendYesOutput.Add($SuccessOrAcceptHostKeyOrPwdPrompt) Start-Sleep -Seconds 1 $Counter++ } if ($Counter -eq 31) { Write-Error "Sending 'yes' to accept the ssh host key timed out!" $global:FunctionResult = "1" if ($PSAwaitProcess.Id) { try { $null = Stop-AwaitSession } catch { if ($PSAwaitProcess.Id -eq $PID) { Write-Error "The PSAwaitSession never spawned! Halting!" $global:FunctionResult = "1" return } else { if ([bool]$(Get-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue)) { Stop-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue } $Counter = 0 while ([bool]$(Get-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue) -and $Counter -le 15) { Write-Verbose "Waiting for Await Module Process Id $($PSAwaitProcess.Id) to end..." Start-Sleep -Seconds 1 $Counter++ } } } } return } $CheckSendYesOutput = $CheckExpectedSendYesOutput | foreach {$_ -split "`n"} # This will handle EITHER the ssh login password prompt OR the sudo prompt (neither of which are guaranteed to happen) if ($CheckSendYesOutput -match "assword.*:") { if ($LocalPassword) { $null = Send-AwaitCommand $LocalPassword } if ($DomainPassword) { $null = Send-AwaitCommand $DomainPassword } Start-Sleep -Seconds 3 if ($PwdPromptDelaySeconds) { Start-Sleep -Seconds $PwdPromptDelaySeconds } $SuccessOrAcceptHostKeyOrPwdPrompt = Receive-AwaitResponse if (!$SSHOutputPrep) { [System.Collections.ArrayList]$script:SSHOutputPrep = @() if (![System.String]::IsNullOrWhiteSpace($SuccessOrAcceptHostKeyOrPwdPrompt)) { $null = $SSHOutputPrep.Add($SuccessOrAcceptHostKeyOrPwdPrompt) } } $Counter = 0 $PwdPromptTimeoutSeconds = $PwdPromptTimeoutMin*60 $CounterLimit = $PwdPromptTimeoutSeconds/10 while (![bool]$($($SSHOutputPrep -split "`n") -match "assword.*:") -and ![bool]$($($SSHOutputPrep -split "`n") -match $ScriptCompleteFlag) -and $Counter -le $CounterLimit) { $SuccessOrAcceptHostKeyOrPwdPrompt = Receive-AwaitResponse if (![System.String]::IsNullOrWhiteSpace($SuccessOrAcceptHostKeyOrPwdPrompt)) { $null = $SSHOutputPrep.Add($SuccessOrAcceptHostKeyOrPwdPrompt) } Start-Sleep -Seconds 10 $Counter++ } if ($Counter -eq $($CounterLimit+1)) { Write-Verbose "Sending the user's password timed out!" #$SSHOutputPrep if ($PSAwaitProcess.Id) { try { $null = Stop-AwaitSession } catch { if ($PSAwaitProcess.Id -eq $PID) { Write-Error "The PSAwaitSession never spawned! Halting!" $global:FunctionResult = "1" return } else { if ([bool]$(Get-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue)) { Stop-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue } $Counter = 0 while ([bool]$(Get-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue) -and $Counter -le 15) { Write-Verbose "Waiting for Await Module Process Id $($PSAwaitProcess.Id) to end..." Start-Sleep -Seconds 1 $Counter++ } } } } } # This will handle EITHER the sudo prompt or another generic password prompt (neither of which are guaranteed to happen) if ($SuccessOrAcceptHostKeyOrPwdPrompt -match "assword.*:" -or @($($SSHOutputPrep -split "`n"))[-1] -match "assword.*:") { if ($LocalPassword) { $null = Send-AwaitCommand $LocalPassword } if ($DomainPassword) { $null = Send-AwaitCommand $DomainPassword } Start-Sleep -Seconds 3 if ($PwdPromptDelaySeconds) { Start-Sleep -Seconds $PwdPromptDelaySeconds } $SuccessOrAcceptHostKeyOrPwdPrompt = Receive-AwaitResponse if (!$SSHOutputPrep) { [System.Collections.ArrayList]$script:SSHOutputPrep = @() if (![System.String]::IsNullOrWhiteSpace($SuccessOrAcceptHostKeyOrPwdPrompt)) { $null = $SSHOutputPrep.Add($SuccessOrAcceptHostKeyOrPwdPrompt) } } $Counter = 0 $PwdPromptTimeoutSeconds = $PwdPromptTimeoutMin*60 $CounterLimit = $PwdPromptTimeoutSeconds/10 while (![bool]$($($SSHOutputPrep -split "`n") -match "assword.*:") -and ![bool]$($($SSHOutputPrep -split "`n") -match $ScriptCompleteFlag) -and $Counter -le $CounterLimit) { $SuccessOrAcceptHostKeyOrPwdPrompt = Receive-AwaitResponse if (![System.String]::IsNullOrWhiteSpace($SuccessOrAcceptHostKeyOrPwdPrompt)) { $null = $SSHOutputPrep.Add($SuccessOrAcceptHostKeyOrPwdPrompt) } Start-Sleep -Seconds 10 $Counter++ } if ($Counter -eq $($CounterLimit+1)) { Write-Verbose "Sending the user's password timed out!" #$SSHOutputPrep if ($PSAwaitProcess.Id) { try { $null = Stop-AwaitSession } catch { if ($PSAwaitProcess.Id -eq $PID) { Write-Error "The PSAwaitSession never spawned! Halting!" $global:FunctionResult = "1" return } else { if ([bool]$(Get-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue)) { Stop-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue } $Counter = 0 while ([bool]$(Get-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue) -and $Counter -le 15) { Write-Verbose "Waiting for Await Module Process Id $($PSAwaitProcess.Id) to end..." Start-Sleep -Seconds 1 $Counter++ } } } } } # We need this extra if block for MacOS because there is one more Password prompt if ($SuccessOrAcceptHostKeyOrPwdPrompt -match "assword.*:" -or @($($SSHOutputPrep -split "`n"))[-1] -match "assword.*:") { if ($LocalPassword) { $null = Send-AwaitCommand $LocalPassword } if ($DomainPassword) { $null = Send-AwaitCommand $DomainPassword } Start-Sleep -Seconds 3 if ($PwdPromptDelaySeconds) { Start-Sleep -Seconds $PwdPromptDelaySeconds } $SuccessOrAcceptHostKeyOrPwdPrompt = Receive-AwaitResponse if (!$SSHOutputPrep) { [System.Collections.ArrayList]$script:SSHOutputPrep = @() if (![System.String]::IsNullOrWhiteSpace($SuccessOrAcceptHostKeyOrPwdPrompt)) { $null = $SSHOutputPrep.Add($SuccessOrAcceptHostKeyOrPwdPrompt) } } $Counter = 0 $PwdPromptTimeoutSeconds = $PwdPromptTimeoutMin*60 $CounterLimit = $PwdPromptTimeoutSeconds/10 while (![bool]$($($SSHOutputPrep -split "`n") -match "assword.*:") -and ![bool]$($($SSHOutputPrep -split "`n") -match $ScriptCompleteFlag) -and $Counter -le $CounterLimit) { $SuccessOrAcceptHostKeyOrPwdPrompt = Receive-AwaitResponse if (![System.String]::IsNullOrWhiteSpace($SuccessOrAcceptHostKeyOrPwdPrompt)) { $null = $SSHOutputPrep.Add($SuccessOrAcceptHostKeyOrPwdPrompt) } Start-Sleep -Seconds 10 $Counter++ } if ($Counter -eq $($CounterLimit+1)) { Write-Verbose "Sending the user's password timed out!" #$SSHOutputPrep if ($PSAwaitProcess.Id) { try { $null = Stop-AwaitSession } catch { if ($PSAwaitProcess.Id -eq $PID) { Write-Error "The PSAwaitSession never spawned! Halting!" $global:FunctionResult = "1" return } else { if ([bool]$(Get-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue)) { Stop-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue } $Counter = 0 while ([bool]$(Get-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue) -and $Counter -le 15) { Write-Verbose "Waiting for Await Module Process Id $($PSAwaitProcess.Id) to end..." Start-Sleep -Seconds 1 $Counter++ } } } } } } } } else { $SSHOutputPrep = $($CheckSendYesOutput | Out-String) -split "`n" } } elseif ($CheckResponsesOutput -match "assword.*:") { if ($LocalPassword) { $null = Send-AwaitCommand $LocalPassword } if ($DomainPassword) { $null = Send-AwaitCommand $DomainPassword } Start-Sleep -Seconds 3 if ($PwdPromptDelaySeconds) { Start-Sleep -Seconds $PwdPromptDelaySeconds } $SuccessOrAcceptHostKeyOrPwdPrompt = Receive-AwaitResponse if (!$SSHOutputPrep) { [System.Collections.ArrayList]$script:SSHOutputPrep = @() if (![System.String]::IsNullOrWhiteSpace($SuccessOrAcceptHostKeyOrPwdPrompt)) { $null = $SSHOutputPrep.Add($SuccessOrAcceptHostKeyOrPwdPrompt) } } $Counter = 0 $PwdPromptTimeoutSeconds = $PwdPromptTimeoutMin*60 $CounterLimit = $PwdPromptTimeoutSeconds/10 while (![bool]$($($SSHOutputPrep -split "`n") -match "assword.*:") -and ![bool]$($($SSHOutputPrep -split "`n") -match $ScriptCompleteFlag) -and $Counter -le $CounterLimit) { $SuccessOrAcceptHostKeyOrPwdPrompt = Receive-AwaitResponse if (![System.String]::IsNullOrWhiteSpace($SuccessOrAcceptHostKeyOrPwdPrompt)) { $null = $SSHOutputPrep.Add($SuccessOrAcceptHostKeyOrPwdPrompt) } Start-Sleep -Seconds 10 $Counter++ } if ($Counter -eq $($CounterLimit+1)) { Write-Verbose "Sending the user's password timed out!" #$SSHOutputPrep if ($PSAwaitProcess.Id) { try { $null = Stop-AwaitSession } catch { if ($PSAwaitProcess.Id -eq $PID) { Write-Error "The PSAwaitSession never spawned! Halting!" $global:FunctionResult = "1" return } else { if ([bool]$(Get-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue)) { Stop-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue } $Counter = 0 while ([bool]$(Get-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue) -and $Counter -le 15) { Write-Verbose "Waiting for Await Module Process Id $($PSAwaitProcess.Id) to end..." Start-Sleep -Seconds 1 $Counter++ } } } } } #Write-Host "`$SuccessOrAcceptHostKeyOrPwdPrompt is:`n$SuccessOrAcceptHostKeyOrPwdPrompt" -ForegroundColor Yellow #Write-Host "`$SSHOutputPrep is:`n$SSHOutputPrep" -ForegroundColor Yellow #$SuccessOrAcceptHostKeyOrPwdPrompt | Export-CliXml "$HOME\SuccessOrAcceptHostKeyOrPwdPrompt.xml" #$SSHOutputPrep | Export-CliXml "$HOME\SSHOutputPrep.xml" # This will handle EITHER the sudo prompt or another generic password prompt (neither of which are guaranteed to happen) if ($SuccessOrAcceptHostKeyOrPwdPrompt -match "assword.*:" -or @($($SSHOutputPrep -split "`n"))[-1] -match "assword.*:") { if ($LocalPassword) { $null = Send-AwaitCommand $LocalPassword } if ($DomainPassword) { $null = Send-AwaitCommand $DomainPassword } Start-Sleep -Seconds 3 if ($PwdPromptDelaySeconds) { Start-Sleep -Seconds $PwdPromptDelaySeconds } $SuccessOrAcceptHostKeyOrPwdPrompt = Receive-AwaitResponse if (!$SSHOutputPrep) { [System.Collections.ArrayList]$script:SSHOutputPrep = @() if (![System.String]::IsNullOrWhiteSpace($SuccessOrAcceptHostKeyOrPwdPrompt)) { $null = $SSHOutputPrep.Add($SuccessOrAcceptHostKeyOrPwdPrompt) } } $Counter = 0 $PwdPromptTimeoutSeconds = $PwdPromptTimeoutMin*60 $CounterLimit = $PwdPromptTimeoutSeconds/10 while (![bool]$($($SSHOutputPrep -split "`n") -match "assword.*:") -and ![bool]$($($SSHOutputPrep -split "`n") -match $ScriptCompleteFlag) -and $Counter -le $CounterLimit) { $SuccessOrAcceptHostKeyOrPwdPrompt = Receive-AwaitResponse if (![System.String]::IsNullOrWhiteSpace($SuccessOrAcceptHostKeyOrPwdPrompt)) { $null = $SSHOutputPrep.Add($SuccessOrAcceptHostKeyOrPwdPrompt) } Start-Sleep -Seconds 10 $Counter++ } if ($Counter -eq $($CounterLimit+1)) { Write-Verbose "Sending the user's password timed out!" #$SSHOutputPrep if ($PSAwaitProcess.Id) { try { $null = Stop-AwaitSession } catch { if ($PSAwaitProcess.Id -eq $PID) { Write-Error "The PSAwaitSession never spawned! Halting!" $global:FunctionResult = "1" return } else { if ([bool]$(Get-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue)) { Stop-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue } $Counter = 0 while ([bool]$(Get-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue) -and $Counter -le 15) { Write-Verbose "Waiting for Await Module Process Id $($PSAwaitProcess.Id) to end..." Start-Sleep -Seconds 1 $Counter++ } } } } } #Write-Host "`$SuccessOrAcceptHostKeyOrPwdPrompt is:`n$SuccessOrAcceptHostKeyOrPwdPrompt" -ForegroundColor Yellow #Write-Host "`$SSHOutputPrep is:`n$SSHOutputPrep" -ForegroundColor Yellow #$SuccessOrAcceptHostKeyOrPwdPrompt | Export-CliXml "$HOME\SuccessOrAcceptHostKeyOrPwdPrompt2.xml" #$SSHOutputPrep | Export-CliXml "$HOME\SSHOutputPrep2.xml" # We need this extra if block for MacOS because there is one moe Password prompt if ($SuccessOrAcceptHostKeyOrPwdPrompt -match "assword.*:" -or @($($SSHOutputPrep -split "`n"))[-1] -match "assword.*:") { if ($LocalPassword) { $null = Send-AwaitCommand $LocalPassword } if ($DomainPassword) { $null = Send-AwaitCommand $DomainPassword } Start-Sleep -Seconds 3 if ($PwdPromptDelaySeconds) { Start-Sleep -Seconds $PwdPromptDelaySeconds } $SuccessOrAcceptHostKeyOrPwdPrompt = Receive-AwaitResponse if (!$SSHOutputPrep) { [System.Collections.ArrayList]$script:SSHOutputPrep = @() if (![System.String]::IsNullOrWhiteSpace($SuccessOrAcceptHostKeyOrPwdPrompt)) { $null = $SSHOutputPrep.Add($SuccessOrAcceptHostKeyOrPwdPrompt) } } $Counter = 0 $PwdPromptTimeoutSeconds = $PwdPromptTimeoutMin*60 $CounterLimit = $PwdPromptTimeoutSeconds/10 while (![bool]$($($SSHOutputPrep -split "`n") -match "assword.*:") -and ![bool]$($($SSHOutputPrep -split "`n") -match $ScriptCompleteFlag) -and $Counter -le $CounterLimit) { $SuccessOrAcceptHostKeyOrPwdPrompt = Receive-AwaitResponse if (![System.String]::IsNullOrWhiteSpace($SuccessOrAcceptHostKeyOrPwdPrompt)) { $null = $SSHOutputPrep.Add($SuccessOrAcceptHostKeyOrPwdPrompt) } Start-Sleep -Seconds 10 $Counter++ } if ($Counter -eq $($CounterLimit+1)) { Write-Verbose "Sending the user's password timed out!" #$SSHOutputPrep if ($PSAwaitProcess.Id) { try { $null = Stop-AwaitSession } catch { if ($PSAwaitProcess.Id -eq $PID) { Write-Error "The PSAwaitSession never spawned! Halting!" $global:FunctionResult = "1" return } else { if ([bool]$(Get-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue)) { Stop-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue } $Counter = 0 while ([bool]$(Get-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue) -and $Counter -le 15) { Write-Verbose "Waiting for Await Module Process Id $($PSAwaitProcess.Id) to end..." Start-Sleep -Seconds 1 $Counter++ } } } } } } } } else { $SSHOutputPrep = $($CheckResponsesOutput | Out-String) -split "`n" } Write-Host "Waiting for up to $WindowsWaitTimeMin minutes for operation to finish..." Start-Sleep -Seconds $($WindowsWaitTimeMin*60) if ($PSAwaitProcess.Id) { try { $null = Stop-AwaitSession } catch { if ($PSAwaitProcess.Id -eq $PID) { Write-Error "The PSAwaitSession never spawned! Halting!" $global:FunctionResult = "1" return } else { if ([bool]$(Get-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue)) { Stop-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue } $Counter = 0 while ([bool]$(Get-Process -Id $PSAwaitProcess.Id -ErrorAction SilentlyContinue) -and $Counter -le 15) { Write-Verbose "Waiting for Await Module Process Id $($PSAwaitProcess.Id) to end..." Start-Sleep -Seconds 1 $Counter++ } } } } if (!$SSHOutputPrep) { $TentativeResult = "ManualVerificationRequired" } elseif (![bool]$($SSHOutputPrep -match $ScriptCompleteFlag)) { $TentativeResult = "ReviewAllOutput" } else { $TentativeResult = "Success" } $FinalOutput = [pscustomobject]@{ TentativeResult = $TentativeResult AllOutput = $SSHOutputPrep } } if ($PSVersionTable.Platform -eq "Unix") { if ($DomainPassword -or $LocalPassword) { $FinalPassword = if ($DomainPassword) {$DomainPassword} else {$LocalPassword} } else { $FinalPassword = 'null' } #$FinalPassword = $FinalPassword -replace [regex]::Escape('$'),'\\\$' -replace [regex]::Escape('"'),'\\\"' if ($SSHScriptArray -and $ElevatedSSHScriptArray) { $ScriptA = $ExpectScriptArray | foreach { if ($_ -match "brew cask reinstall powershell") { $SendLine = $_ -replace ' && echo powershellInstallComplete','' $ExpectPwdPrompt = @( "send -- \`"$SendLine\r\`"" 'expect {' ' -re \".*assword.*:\" {' ' send -- \"\$password\r\"' ' exp_continue' ' }' ' -re \"was successfully installed!\" {' ' send -- \"echo powershellInstallComplete\r\"' ' expect \"*\"' ' }' '}' ) $ExpectPwdPrompt -join "`n" } elseif ($_ -match "powershellInstallComplete") { 'send -- \"' + $_ + '\r\"' + "`n" + $('expect \"*{0}*\"' -f 'powershellInstallComplete') } elseif ($_ -match 'pwshConfigComplete') { 'send -- \"' + $_ + '\r\"' + "`n" + $('expect \"*{0}*\"' -f 'pwshConfigComplete') } else { 'send -- \"' + $_ + '\r\"' + "`n" + 'expect -re \"$prompt\"' } } $ScriptB = $ElevatedExpectScriptArray | foreach { if ($_ -match "powershellInstallComplete") { 'send -- \"' + $_ + '\r\"' + "`n" + $('expect \"*{0}*\"' -f 'powershellInstallComplete') } elseif ($_ -match 'pwshConfigComplete') { 'send -- \"' + $_ + '\r\"' + "`n" + $('expect \"*{0}*\"' -f 'pwshConfigComplete') } else { 'send -- \"' + $_ + '\r\"' + "`n" + 'expect -re \"$prompt\"' } } } if (!$SSHScriptArray -and $ElevatedSSHScriptArray) { $ScriptB = $ElevatedExpectScriptArray | foreach { if ($_ -match "powershellInstallComplete") { 'send -- \"' + $_ + '\r\"' + "`n" + $('expect \"*{0}*\"' -f 'powershellInstallComplete') } elseif ($_ -match 'pwshConfigComplete') { 'send -- \"' + $_ + '\r\"' + "`n" + $('expect \"*{0}*\"' -f 'pwshConfigComplete') } else { 'send -- \"' + $_ + '\r\"' + "`n" + 'expect -re \"$prompt\"' } } } if ($SSHScriptArray -and !$ElevatedSSHScriptArray) { $ScriptA = $ExpectScriptArray | foreach { if ($_ -match "brew cask reinstall powershell") { $SendLine = $_ -replace ' && echo powershellInstallComplete','' $ExpectPwdPrompt = @( "send -- \`"$SendLine\r\`"" 'expect {' ' -re \".*assword.*:\" {' ' send -- \"\$password\r\"' ' exp_continue' ' }' ' -re \"was successfully installed!\" {' ' send -- \"echo powershellInstallComplete\r\"' ' expect \"*\"' ' }' '}' ) $ExpectPwdPrompt -join "`n" } elseif ($_ -match "powershellInstallComplete") { 'send -- \"' + $_ + '\r\"' + "`n" + $('expect \"*{0}*\"' -f 'powershellInstallComplete') } elseif ($_ -match 'pwshConfigComplete') { 'send -- \"' + $_ + '\r\"' + "`n" + $('expect \"*{0}*\"' -f 'pwshConfigComplete') } else { 'send -- \"' + $_ + '\r\"' + "`n" + 'expect -re \"$prompt\"' } } } [System.Collections.ArrayList]$ExpectScriptPrep = @( 'expect - << EOF' 'set timeout 120' "set password $FinalPassword" 'set prompt \"(>|:|#|\\\\\\$)\\\\s+\\$\"' "spawn $($SSHCmdStringArray -join " ")" 'match_max 100000' 'expect {' ' \"*(yes/no)?*\" {' ' send -- \"yes\r\"' ' exp_continue' ' }' ' -re \".*assword.*:\" {' ' send -- \"\$password\r\"' ' exp_continue' ' }' ' -re \"\$prompt\" {' ' send -- \"echo LoggedIn\r\"' ' expect \"*\"' ' }' '}' ) if ($ScriptA) { $ScriptA | foreach { $null = $ExpectScriptPrep.Add($_) } } if ($ScriptB) { $null = $ExpectScriptPrep.Add('send -- \"sudo su\r\"') $null = $ExpectScriptPrep.Add('expect {') $null = $ExpectScriptPrep.Add(' -re \".*assword.*:\" {') $null = $ExpectScriptPrep.Add(' send -- \"\$password\r\"') $null = $ExpectScriptPrep.Add(' exp_continue') $null = $ExpectScriptPrep.Add(' }') $null = $ExpectScriptPrep.Add(' -re \"\$prompt\" {') $null = $ExpectScriptPrep.Add(' send -- \"echo StartSudoersUpdate\r\"') $null = $ExpectScriptPrep.Add(' expect \"StartSudoersUpdate\"') $null = $ExpectScriptPrep.Add(' }') $null = $ExpectScriptPrep.Add('}') $ScriptB | foreach { $null = $ExpectScriptPrep.Add($_) } $null = $ExpectScriptPrep.Add('send -- \"exit\r\"') $null = $ExpectScriptPrep.Add('expect -re \"\$prompt\"') $null = $ExpectScriptPrep.Add('send -- \"exit\r\"') $null = $ExpectScriptPrep.Add('expect eof') $null = $ExpectScriptPrep.Add('EOF') } else { $null = $ExpectScriptPrep.Add('send -- \"exit\r\"') $null = $ExpectScriptPrep.Add('expect eof') $null = $ExpectScriptPrep.Add('EOF') } $ExpectScript = $ExpectScriptPrep -join "`n" #Write-Host "`$ExpectScript is:`n$ExpectScript" #$ExpectScript | Export-CliXml "$HOME/ExpectScript2.xml" # The below $ExpectOutput is an array of strings $ExpectOutput = bash -c "$ExpectScript" # NOTE: The below -replace regex string removes garbage escape sequences like: [116;1H #$SSHOutputPrep = $ExpectOutput -replace "\e\[(\d+;)*(\d+)?[ABCDHJKfmsu]","" $SSHOutputPrep = $ExpectOutput if (!$SSHOutputPrep) { $TentativeResult = "ManualVerificationRequired" } elseif (![bool]$($SSHOutputPrep -match $ScriptCompleteFlag)) { $TentativeResult = "ReviewAllOutput" } else { $TentativeResult = "Success" } $FinalOutput = [pscustomobject]@{ TentativeResult = $TentativeResult AllOutput = $SSHOutputPrep } } $FinalOutput } |