# encoding: ascii
# api: powershell
# title: SharpSsh 3
# description: I’ve tweaked New-SshSession again, this time it supports multiple parameter sets (including one with the password in plain text, and one which takes a PSCredential).
# version: 0.1
# type: script
# author: Joel Bennett
# license: CC0
# function: ConvertTo-SecureString
# x-poshcode-id: 2099
# x-derived-from-id: 3601
# x-archived: 2017-03-12T05:29:49
# x-published: 2011-08-24T21:41:00
#
# A few wrapper functions to make working with the SSH portion of SharpSSH easier: New-SshSession, Invoke or Send Ssh commands, Receive output, all with support for “Expect” ... which means we’ll continue reading from the ssh output until we see the expected output, and then stop.
#
#requires -version 2.0
## A simple SSH Scripting module for PowerShell
## History:
## v1 - Initial Script
## v2 - Capture default prompt in New-SshSession
## v3 - Update to advanced functions, require 2.0, and add basic help

## USING the binaries from:
## http://downloads.sourceforge.net/sharpssh/SharpSSH-1.1.1.13.bin.zip
[void][reflection.assembly]::LoadFrom( (Resolve-Path "~\Documents\WindowsPowerShell\Libraries\Tamir.SharpSSH.dll") )

Function ConvertTo-SecureString {
#.Synopsis
#   Helper function which converts a string to a SecureString
Param([string]$input)
   $result = new-object System.Security.SecureString
 
   foreach($c in $input.ToCharArray()) {
      $result.AppendChar($c)
   }
   $result.MakeReadOnly()
   return $result
}

Function ConvertTo-PSCredential {
#.Synopsis
#   Helper function which converts a NetworkCredential to a PSCredential
Param([System.Net.NetworkCredential]$Credential)
   $result = new-object System.Security.SecureString
 
   foreach($c in $input.ToCharArray()) {
      $result.AppendChar($c)
   }
   $result.MakeReadOnly()
   New-Object System.Management.Automation.PSCredential "$($Credential.UserName)@$($Credential.Domain)", (ConvertTo-SecureString $Credential.Password)
}

## NOTE: These are still bare minimum functions, and only cover ssh, not sftp or scp (see here: http://poshcode.org/1034) 
##       IMPORTANT: if you "expect" something that does NOT get output, you'll be completely stuck and unable to 'break'
##
## As a suggestion, the best way to handle the output is to "expect" your prompt, and then do 
## select-string matching on the output that was captured before the prompt.

function New-SshSession {
<#
.Synopsis
   Create an SSH session to a remote server
.Description
   Connect to a specific SSH server with the specified credentials.  Supports RSA KeyFile connections.
.Parameter HostName
   The server to SSH into
.Parameter UserName
   The user name to use for login
.Parameter Password
   The Password for login (Note: you really should pass this as a System.Security.SecureString, but it will accept a string instead)
.Parameter KeyFile
   An RSA keyfile for ssh secret key authentication (INSTEAD of username/password authentication).
.Parameter Credentials
   A PSCredential object containing all the information needed to log in. The login name should be in the form user@host
.Parameter Passthru
   If passthru is specified, the new SshShell is returned.
.Example 
   New-SshSession Microsoft.com BillG Micr050ft
   
   Description
   -----------
   Creates a new ssh session with the ssh server at Microsoft.com using the 'BillG' as the login name and 'Micr050ft' as the password (please don't bother trying that).
.Example 
   New-SshSession Microsoft.com -Keyfile BillGates.ppk
   
   Description
   -----------
   Creates a new ssh session with the ssh server at Microsoft.com using the credentials supplied in the BillGates.ppk private key file.
.Example
   $MSCred = Get-Credential BillG@Microsoft.com  # prompts for password
   New-SshSession $MSCred
  
   Description
   -----------
   Creates a new ssh session based on the supplied credentials. Uses the output of $MsCred.GetNetworkCredential() for the server to log into (domain) and the username and password.
#>
[CmdletBinding(DefaultParameterSetName='NamePass')]
Param(
   [Parameter(Position=0,Mandatory=$true,ParameterSetName="NamePass",ValueFromPipelineByPropertyName=$true)]
   [Parameter(Position=0,Mandatory=$true,ParameterSetName="RSAKeyFile",ValueFromPipelineByPropertyName=$true)]
   [string]$HostName
,
   [Parameter(Position=1,Mandatory=$false,ParameterSetName="NamePass",ValueFromPipelineByPropertyName=$true)]
   [string]$UserName
,
   [Parameter(Position=2,Mandatory=$false,ParameterSetName="NamePass",ValueFromPipelineByPropertyName=$true)]
   $Password
,  
   [Parameter(Position=1,Mandatory=$true,ParameterSetName="RSAKeyFile",ValueFromPipelineByPropertyName=$true)]
   [string]$KeyFile
,
   [Parameter(Position=0,Mandatory=$true,ParameterSetName="PSCredential",ValueFromPipeline=$true)]
   [System.Management.Automation.PSCredential]$Credentials
,
   [switch]$Passthru
)
   process {
      switch($PSCmdlet.ParameterSetName) {
         'RSAKeyFile'   {
            $global:LastSshSession = new-object Tamir.SharpSsh.SshShell $HostName, $UserName
            $global:LastSshSession.AddIdentityFile( (Convert-Path (Resolve-Path $KeyFile)) )
            
         }
         'NamePass' {
            if(!$UserName -or !$Password) {
               $Credentials = $Host.UI.PromptForCredential("SSH Login Credentials",
                                                "Please specify credentials in user@host format",
                                                "$UserName@$HostName","")
            } else {
               if($Password -isnot [System.Security.SecureString]) {
                  $Password = ConvertTo-SecureString $Password
               }
               $Credentials = New-Object System.Management.Automation.PSCredential "$UserName@$HostName", $Password
            }
         }
      }
      if($Credentials) {
         $global:LastSshSession = new-object Tamir.SharpSsh.SshShell `
                                          $Credentials.GetNetworkCredential().Domain, 
                                          $Credentials.GetNetworkCredential().UserName,
                                          $Credentials.GetNetworkCredential().Password
      }

      $global:LastSshSession.Connect()
      $global:LastSshSession.RemoveTerminalEmulationCharacters = $true
      
      if($Passthru) { return $global:LastSshSession }
      
      $global:LastSshSession.WriteLine("")
      sleep -milli 500
      $global:defaultSshPrompt = [regex]::Escape( $global:LastSshSession.Expect().Split("`n")[-1] )
   }
}

function Remove-SshSession {
<#
   .Synopsis
      Exits the open SshSession
#>
Param([Tamir.SharpSsh.SshShell]$SshShell=$global:LastSshSession)
   $SshShell.WriteLine( "exit" )
   sleep -milli 500
   if($SshShell.ShellOpened) { Write-Warning "Shell didn't exit cleanly, closing anyway." }
   $SshShell.Close()
   $SshShell = $null
}

function Invoke-Ssh {
<#
   .Synopsis
      Executes an SSH command and Receives output
   .Description
      Invoke-Ssh is basically the same as a Send-Ssh followed by a Receive-Ssh, except that Expect defaults to $defaultSshPrompt (which is read in New-SshSession)
   .Parameter Command
      The command to send
   .Parameter Expect
      A regular expression for expect. The ssh session will wait for a line that matches this regular expression to be output before returning, and will return all the text up to AND INCLUDING the line that matches.
      Defaults
   .Parameter SshShell
      The shell to invoke against. Defaults to the LastSshSession
#>
Param(
   [string]$Command
,  [regex]$Expect = $global:defaultSshPrompt ## there ought to be a non-regex parameter set...
,  [Tamir.SharpSsh.SshShell]$SshShell=$global:LastSshSession
)

   if($SshShell.ShellOpened) {
      $SshShell.WriteLine( $command )
      if($expect) {
         $SshShell.Expect( $expect ).Split("`n")
      }
      else {
         sleep -milli 500
         $SshShell.Expect().Split("`n")
      }
   }
   else { throw "The ssh shell isn't open!" } 
}

function Send-Ssh {
<#
   .Synopsis
      Executes an SSH command without receiving input
   .Description
      Sends a command to an ssh session
   .Parameter Command
      The command to send
   .Parameter SshShell
      The shell to send to. Defaults to the LastSshSession
#>
Param(
   [string]$command
,  [Tamir.SharpSsh.SshShell]$SshShell=$global:LastSshSession
)

   if($SshShell.ShellOpened) {
      $SshShell.WriteLine( $command )
   }
   else { throw "The ssh shell isn't open!" } 
}

function Receive-Ssh {
<#
   .Synopsis
      Receives output from an SSH session
   .Description
      Retrieves output from an SSH session until the text matches the Expect pattern
   .Parameter Expect
      A regular expression for expect. The ssh session will wait for a line that matches this regular expression to be output before returning, and will return all the text up to AND INCLUDING the line that matches.
   .Parameter SshShell
      The shell to wait for. Defaults to the LastSshSession
#>
Param(
   [RegEx]$expect ## there ought to be a non-regex parameter set...
,  [Tamir.SharpSsh.SshShell]$SshShell=$global:LastSshSession
)
   if($SshShell.ShellOpened) {
      if($expect) {
         $SshShell.Expect( $expect ).Split("`n")
      }
      else {
         sleep -milli 500
         $SshShell.Expect().Split("`n")
      }
   }
   else { throw "The ssh shell isn't open!" } 
}
