Posh-SSH.psm1

if ($PSVersionTable.PSVersion.Major -eq 5) {
    Add-Type -Path "$PSScriptRoot/Assembly/Newtonsoft.Json.dll"
}

# force load Renci and dependency do to MS including Renci in Windows 2019 Storage Server.
if ($PSVersionTable.PSVersion.Major -eq 5) {
    Add-Type -Path "$PSScriptRoot/Assembly/SshNet.Security.Cryptography.dll"
    Add-Type -Path "$PSScriptRoot/Assembly/Renci.SshNet.dll"
    
}
# Set up of Session variables.
##############################################################################################
if (!(Test-Path variable:Global:SshSessions ))
{
    $global:SshSessions = New-Object System.Collections.ArrayList
}

if (!(Test-Path variable:Global:SFTPSessions ))
{
    $global:SFTPSessions = New-Object System.Collections.ArrayList
}

New-Alias -Name 'Get-SSHJsonKnowHost' -Value 'Get-SSHJsonKnownHost' -Force

# SSH Functions
##############################################################################################


# .ExternalHelp Posh-SSH.psm1-Help.xml
function Get-SSHSession
{
    [CmdletBinding(DefaultParameterSetName='Index')]
    param(
        [Parameter(Mandatory=$false,
                   ParameterSetName = 'Index',
                   Position=0)]
        [Alias('Index')]
        [Int32[]]
        $SessionId,

        [Parameter(Mandatory=$false,
                   ParameterSetName = 'ComputerName',
                   Position=0)]
        [Alias('Server', 'HostName', 'Host')]
        [string[]]
        $ComputerName,

        [Parameter(Mandatory=$false,
                   ParameterSetName = 'ComputerName',
                   Position=0)]
        [switch]
        $ExactMatch
    )

    Begin{}
    Process
    {
        if ($PSCmdlet.ParameterSetName -eq 'Index')
        {
            # Can not reference SShSessions directly so as to be able
            # to remove the sessions when Remove-SSHSession is used
            if ( $PSBoundParameters.ContainsKey('SessionId') )
            {
                $result = foreach($session in $SshSessions)
                {
                    if ($session.SessionId -in $SessionId)
                    {
                        $session
                    }
                }
            }
            else
            {
                $result = $Global:SshSessions.psobject.copy()
            }
        }
        else # ParameterSetName -eq 'ComputerName'
        {
            # Only check to see if it contains ComputerName. If it get's it without having any values somehow, then don't return anything as they did something odd.
            if ( $PSBoundParameters.ContainsKey('ComputerName') )
            {
                $result = foreach($session in $SshSessions)
                {
                    foreach($s in $ComputerName)
                    {
                        if ($session.Host -like $s -and ( -not $ExactMatch -or $session.Host -eq $s ) )
                        {
                            $session
                        }
                    }
                }
            }
        }
        $result
    }
    End{}
}


# .ExternalHelp Posh-SSH.psm1-Help.xml
function Remove-SSHSession
{
    [CmdletBinding(DefaultParameterSetName='Index')]
    param(
        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Index',
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [Alias('Index')]
        [Int32[]]
        $SessionId,

        [Parameter(Mandatory=$false,
                   ParameterSetName = 'Session',
                   ValueFromPipeline=$true,
                   Position=0)]
        [Alias('Name')]
        [SSH.SSHSession[]]
        $SSHSession
        )

        Begin {
            $sessions2remove = New-Object System.Collections.ArrayList
        }
        Process
        {
            if ($PSCmdlet.ParameterSetName -eq 'Index')
            {
                foreach($i in $SessionId)
                {
                    foreach($session in $Global:SshSessions)
                    {
                        if ($session.SessionId -eq $i)
                        {
                            [void]$sessions2remove.Add($session)
                        }
                    }
                }
            }

            if ($PSCmdlet.ParameterSetName -eq 'Session')
            {
                foreach($i in $SSHSession)
                {
                    foreach($session in $Global:SshSessions)
                    {
                        if ($session -eq $i)
                        {
                            [void]$sessions2remove.Add($session)
                        }
                    }
                }
            }

        }
        End{
            foreach($badsession in $sessions2remove)
            {
                 Write-Verbose "Removing session $($badsession.SessionId)"
                 if ($badsession.session.IsConnected)
                 {
                    $badsession.session.Disconnect()
                 }
                 $badsession.session.Dispose()
                 $global:SshSessions.Remove($badsession)
                 Write-Verbose "Session $($badsession.SessionId) Removed"
            }
        }
}


# .ExternalHelp Posh-SSH.psm1-Help.xml
function Invoke-SSHCommand
{
    [CmdletBinding(DefaultParameterSetName='Index')]
    param(

        [Parameter(Mandatory=$true,
                   Position=1)]
        [string]$Command,

        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Session',
                   ValueFromPipeline=$true,
                   Position=0)]
        [Alias('Name')]
        [SSH.SSHSession[]]
        $SSHSession,

        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Index',
                   Position=0)]
        [Alias('Index')]
        [int32[]]
        $SessionId,

        # Ensures a connection is made by reconnecting before command.
        [Parameter(Mandatory=$false)]
        [switch]
        $EnsureConnection,

        [Parameter(Mandatory=$false,
                   Position=2)]
        [int]
        $TimeOut = 60,

        [Parameter(Mandatory=$false, Position=3)]
        [int]
        $ThrottleLimit = 32,

        [Parameter(Mandatory=$false)]
        [switch]
        $ShowStandardOutputStream,

        [Parameter(Mandatory=$false)]
        [switch]
        $ShowErrorOutputStream,

        [Parameter(Mandatory=$false)]
        [string]
        $StandardOutputStreamColor = "White",

        [Parameter(Mandatory=$false)]
        [string]
        $ErrorOutputStreamColor ="Red"
    )

    Begin
    {
        $ToProcess = @()
        switch($PSCmdlet.ParameterSetName)
        {
            'Session'
            {
                $ToProcess = $SSHSession
            }

            'Index'
            {
                foreach($session in $Global:SshSessions)
                {
                    if ($SessionId -contains $session.SessionId)
                    {
                        $ToProcess += $session
                    }
                }
            }
        }

        # array to keep track of all the running jobs.
        $Script:AsyncProcessing = @{}
        $JobId = 0
        $Script:IndexStandardOutputStream = 0
        $Script:IndexErrorOutputStream = 0
        function CheckAsyncProcessing
        {
            process
            {
                $RemoveJobs = @()
                $Script:AsyncProcessing.GetEnumerator() |
                ForEach-Object {
                    $JobKey = $_.Key
                    #Write-Verbose $JobKey -Verbose
                    $_.Value
                } |
                ForEach-Object {

                    # Output Stream
                    if ($ShowStandardOutputStream)
                    {
                        if ($_.cmd.Result.Length -gt $Script:IndexStandardOutputStream)
                        {
                            $NewStreamData = $_.cmd.Result.Substring($Script:IndexStandardOutputStream)
                            Write-Host $NewStreamData -ForegroundColor $StandardOutputStreamColor -NoNewline
                            $Script:IndexStandardOutputStream = $_.cmd.Result.Length
                        }
                    }
                    if ($ShowErrorOutputStream)
                    {
                        if ($_.cmd.Error.Length -gt $Script:IndexErrorOutputStream)
                        {
                            $NewStreamData = $_.cmd.Error.Substring($Script:IndexErrorOutputStream)
                            Write-Host $NewStreamData -ForegroundColor $ErrorOutputStreamColor -NoNewline
                            $Script:IndexErrorOutputStream = $_.cmd.Error.Length
                        }
                    }

                    # Check if it completed or is past the timeout setting.
                    if ( $_.Async.IsCompleted -or $_.Duration.Elapsed.TotalSeconds -gt $TimeOut )
                    {
                        # Set the variable within this function to not use its value from outer scope in case the
                        # EndExecute call throws an exception
                        $Output = ''
                        $Output = $_.cmd.EndExecute($_.Async)

                        # Generate custom object to return to pipeline and client
                        [pscustomobject]@{
                            Output = $Output -replace '\n$' -split '\n'
                            ExitStatus = $_.cmd.ExitStatus
                            Error = $_.cmd.Error
                            Host = $_.Connection.Host
                            Duration = $_.Duration.Elapsed
                        } |
                        ForEach-Object {
                            $_.pstypenames.insert(0,'Renci.SshNet.SshCommand');

                            #Return object to pipeline
                            $_
                        }

                        # Set this object as having been processed.
                        $_.Processed = $true
                        $RemoveJobs += $JobKey
                    }
                }

                # Remove all the items that are done.
                #[int[]]$Script:AsyncProcessing.Keys |
                $RemoveJobs |
                ForEach-Object {
                    if ($Script:AsyncProcessing.$_.Processed)
                    {
                        $Script:AsyncProcessing.Remove( $_ )
                    }
                }
            }
        }
    }


    Process
    {
        foreach($Connection in $ToProcess)
        {
            if ($Connection.Session.IsConnected)
            {
                if ($EnsureConnection)
                {
                    try
                    {
                        $Connection.Session.Connect()
                    }
                    catch
                    {
                        if ( $_.Exception.InnerException.Message -ne 'The client is already connected.' )
                        { Write-Error -Exception $_.Exception }
                    }
                }
            }
            else
            {
                try
                {
                    $Connection.Session.Connect()
                }
                catch
                {
                    Write-Error "Unable to connect session to $($Connection.Host) :: $_"
                }

            }

            if ($Connection.Session.IsConnected)
            {
                while ($Script:AsyncProcessing.Count -ge $ThrottleLimit )
                {
                    CheckAsyncProcessing
                    Write-Debug "Throttle reached: $ThrottleLimit"
                    Start-Sleep -Milliseconds 50
                }

                $cmd = $Connection.session.CreateCommand($Command)
                $cmd.CommandTimeout = [timespan]::FromMilliseconds(50) #New-TimeSpan -Seconds $TimeOut

                # start asynchronious execution of the command.
                $Duration = [System.Diagnostics.Stopwatch]::StartNew()
                $Async = $cmd.BeginExecute()

                $Script:AsyncProcessing.Add( $JobId, ( [pscustomobject]@{
                    cmd = $cmd
                    Async = $Async
                    Connection = $Connection
                    Duration = $Duration
                    Processed = $false
                }))

                $JobId++
            }

            CheckAsyncProcessing

        }
    }
    End
    {
        $Done = $false
        while ( -not $Done )
        {
           CheckAsyncProcessing

            if ( $Script:AsyncProcessing.Count -eq 0 )
            { $Done = $true }
            else { Start-Sleep -Milliseconds 50 }
        }
    }
}


# .ExternalHelp Posh-SSH.psm1-Help.xml
 function Get-PoshSSHModVersion
 {
    [CmdletBinding()]
    [OutputType([pscustomobject])]
    Param()

    Begin
    {
       $CurrentVersion = $null
       $installed = (Get-Module -Name 'posh-SSH').Version
    }
    Process
    {
       $webClient = New-Object System.Net.WebClient
       Try
       {
           $current = Invoke-Expression  $webClient.DownloadString('https://raw.github.com/darkoperator/Posh-SSH/master/Release/Posh-SSH.psd1')
           $CurrentVersion = [Version]$current.ModuleVersion
       }
       Catch
       {
           Write-Warning 'Could not retrieve the current version.'
       }

       if ( $null -eq $installed )
       {
           Write-Error 'Unable to locate Posh-SSH.'
       }
       elseif ( $CurrentVersion -gt $installed )
       {
           Write-Warning 'You are running an outdated version of the module.'
       }

       $props = @{
           InstalledVersion = $installed
           CurrentVersion   = $CurrentVersion
       }
       New-Object -TypeName psobject -Property $props
     }
     End{}
 }

 # .ExternalHelp Posh-SSH.psm1-Help.xml
function Invoke-SSHCommandStream
{
    [CmdletBinding(DefaultParameterSetName='Index')]
    param(

        [Parameter(Mandatory=$true,
                   Position=1)]
        [string]$Command,

        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Session',
                   ValueFromPipeline=$true,
                   Position=0)]
        [Alias('Name')]
        [SSH.SSHSession]
        $SSHSession,

        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Index',
                   Position=0)]
        [Alias('Index')]
        [int32]
        $SessionId,

        # Ensures a connection is made by reconnecting before command.
        [Parameter(Mandatory=$false)]
        [switch]
        $EnsureConnection,

        [Parameter(Mandatory=$false,
                   Position=2)]
        [int]
        $TimeOut = 60,

        [Parameter()]
        [string]
        $HostVariable,

        [Parameter()]
        [string]
        $ExitStatusVariable
    )

    Begin
    {
        $ToProcess = @()
        switch($PSCmdlet.ParameterSetName)
        {
            'Session'
            {
                $ToProcess = $SSHSession
            }

            'Index'
            {
                foreach($session in $Global:SshSessions)
                {
                    if ($SessionId -contains $session.SessionId)
                    {
                        $ToProcess += $session
                    }
                }
            }
        }
    }
    Process
    {
        foreach($Connection in $ToProcess)
        {
            if ($Connection.session.isconnected)
                {
                    if ($EnsureConnection)
                    {
                        $Connection.session.connect()
                    }
                    $cmd = $Connection.session.CreateCommand($Command)
                }
                else
                {
                    $s.session.connect()
                    $cmd = $Connection.session.CreateCommand($Command)
                }

                $cmd.CommandTimeout = New-TimeSpan -Seconds $TimeOut

                if ( $PSBoundParameters.ContainsKey('HostVariable') )
                { Set-Variable -Scope 1 -Name $HostVariable -Value $Connection.Host }


                # start asynchronious execution of the command.
                $Async = $cmd.BeginExecute()
                $Duration = [System.Diagnostics.Stopwatch]::StartNew()
                $Reader = New-Object System.IO.StreamReader -ArgumentList $cmd.OutputStream

                try
                {
                    Write-Verbose "IsCompleted Before: $($Async.IsCompleted)"
                    while(-not $Async.IsCompleted -and $Duration.Elapsed -lt $cmd.CommandTimeout )
                    {
                        Write-Verbose "IsCompleted During: $($Async.IsCompleted)"
                        #if ( $Reader.Peek() -gt -1)
                        while ( $Reader.Peek() -gt -1)
                        {
                            $Result = $Reader.ReadLine()
                            $Result -replace '\n\r$'
                        }
                        Start-Sleep -Milliseconds 5
                    }
                }
                catch
                {
                    Write-Error -Message "Error with Command: $_"
                }
                finally
                {
                    Write-Verbose "IsCompleted After: $($Async.IsCompleted)"
                    # Using finally clause to make sure the command is ended if Ctrl-C is used to cancel it.

                    while ( $Reader.Peek() -gt -1)
                    {
                        $Result = $Reader.ReadLine()
                        $Result -replace '\n\r$'
                    }

                    if (-not $Async.IsCompleted -and $Duration.Elapsed -ge $cmd.CommandTimeout )
                    {
                        $cmd.EndExecute($Async)

                    }
                    elseif ( -not $Async.IsCompleted )
                    {
                        $cmd.CancelAsync()
                        Write-Warning "Canceled execution"

                    }

                }

            if ( $PSBoundParameters.ContainsKey('ExitStatusVariable') )
            {
                Set-Variable -Scope 1 -Name $ExitStatusVariable -Value @{
                    Host = $Connection.Host
                    ExitStatus = $cmd.ExitStatus
                    Error = $cmd.Error
                }
            }
        }
    }
    End{}
}


# .ExternalHelp Posh-SSH.psm1-Help.xml
function New-SSHShellStream
{
    [CmdletBinding(DefaultParameterSetName="Index")]
    [OutputType([Renci.SshNet.ShellStream])]
    Param
    (
        [Parameter(Mandatory=$true,
            ParameterSetName = "Session",
            ValueFromPipeline=$true,
            Position=0)]
        [Alias("Session")]
        [SSH.SSHSession]
        $SSHSession,

        [Parameter(Mandatory=$true,
            ParameterSetName = "Index",
            ValueFromPipeline=$true,
            Position=0)]
        [Alias('Index')]
        [Int32]
        $SessionId,

        # Name of the terminal.
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true)]
        [string]
        $TerminalName = "dumb",

        # The columns
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true)]
        [int]
        $Columns=80,

        # The rows.
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true)]
        [int]
        $Rows=24,

        # The width.
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true)]
        [int]
        $Width= 800,

        # The height.
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true)]
        [int]
        $Height=600,

        # Size of the buffer.
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true)]
        [int]
        $BufferSize=1000
    )

    Begin
    {
        $ToProcess = $null
        switch($PSCmdlet.ParameterSetName)
        {
            'Session'
            {
                $ToProcess = $SSHSession
            }

            'Index'
            {
                $sess = Get-SSHSession -Index $SessionId
                if ($sess)
                {
                    $ToProcess = $sess
                }
                else
                {
                    Write-Error -Message "Session specified with Index $($SessionId) was not found"
                    return
                }
            }
        }
    }
    Process
    {
        $stream = $ToProcess.Session.CreateShellStream($TerminalName, $Columns, $Rows, $Width, $Height, $BufferSize)
        Add-Member -InputObject $stream -MemberType NoteProperty -Name SessionId -Value $ToProcess.SessionId
        Add-Member -InputObject $stream -MemberType NoteProperty -Name Session -Value $ToProcess.Session
        $stream
    }
    End
    {
    }
}

# .ExternalHelp Posh-SSH.psm1-Help.xml
function Invoke-SSHStreamExpectAction
{
    [CmdletBinding(DefaultParameterSetName='String')]
    [OutputType([Bool])]
    Param
    (
        # SSH Shell Stream.
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   ValueFromPipeline=$true,
                   Position=0)]
        [Renci.SshNet.ShellStream]
        $ShellStream,

        # Initial command that will generate the output to be evaluated by the expect pattern.
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=1)]
        [string]
        $Command,

        # String on what to trigger the action on.
        [Parameter(Mandatory=$true,
                   ParameterSetName='String',
                   ValueFromPipelineByPropertyName=$true,
                   Position=2)]
        [string]
        $ExpectString,

        # Regular expression on what to trigger the action on.
        [Parameter(Mandatory=$true,
                   ParameterSetName='Regex',
                   ValueFromPipelineByPropertyName=$true,
                   Position=2)]
        [regex]
        $ExpectRegex,

        # Command to execute once an expression is matched.
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=3)]
        [string]
        $Action,

        # Number of seconds to wait for a match.
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true,
                   Position=4)]
        [int]
        $TimeOut = 10
    )

    Begin
    {
    }
    Process
    {
        Write-Verbose -Message "Executing command $($Command)."
        $ShellStream.WriteLine($Command)
        Write-Verbose -Message "Waiting for match."
        switch ($PSCmdlet.ParameterSetName)
        {
            'string' {
                Write-Verbose "Matching on string $($ExpectString)"
                $found = $ShellStream.Expect($ExpectString, (New-TimeSpan -Seconds $TimeOut))}
            'Regex'  {
                Write-Verbose "Matching on pattern $($ExpectRegex)"
                $found = $ShellStream.Expect($ExpectRegex, (New-TimeSpan -Seconds $TimeOut))}
        }

        if ($null -ne $found)
        {
            Write-Verbose -Message "Executing action: $($Action)."
            $ShellStream.WriteLine($Action)
            Write-Verbose -Message 'Action has been executed.'
            $true
        }
        else
        {
            Write-Verbose -Message 'Expect timeout without achiving a match.'
            $false
        }
    }
    End
    {
    }
}

# .ExternalHelp Posh-SSH.psm1-Help.xml
function Invoke-SSHStreamExpectSecureAction
{
    [CmdletBinding(DefaultParameterSetName='String')]
    [OutputType([Bool])]
    Param
    (
        # SSH Shell Stream.
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   ValueFromPipeline=$true,
                   Position=0)]
        [Renci.SshNet.ShellStream]
        $ShellStream,

        # Initial command that will generate the output to be evaluated by the expect pattern.
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=1)]
        [string]
        $Command,

        # String on what to trigger the action on.
        [Parameter(Mandatory=$true,
                   ParameterSetName='String',
                   ValueFromPipelineByPropertyName=$true,
                   Position=2)]
        [string]
        $ExpectString,

        # Regular expression on what to trigger the action on.
        [Parameter(Mandatory=$true,
                   ParameterSetName='Regex',
                   ValueFromPipelineByPropertyName=$true,
                   Position=2)]
        [regex]
        $ExpectRegex,

        # SecureString representation of action once an expression is matched.
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=3)]
        [securestring]
        $SecureAction,

        # Number of seconds to wait for a match.
        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true,
                   Position=4)]
        [int]
        $TimeOut = 10
    )

    Begin
    {
    }
    Process
    {
        Write-Verbose -Message "Executing command $($Command)."
        $ShellStream.WriteLine($Command)
        Write-Verbose -Message "Waiting for match."
        switch ($PSCmdlet.ParameterSetName)
        {
            'string' {
                Write-Verbose -Message 'Matching by String.'
                $found = $ShellStream.Expect($ExpectString, (New-TimeSpan -Seconds $TimeOut))
             }

            'Regex'  {
                Write-Verbose -Message 'Matching by RegEx.'
                $found = $ShellStream.Expect($ExpectRegex, (New-TimeSpan -Seconds $TimeOut))
             }
        }

        if ($null -ne $found)
        {
            Write-Verbose -Message "Executing action."
            $ShellStream.WriteLine([System.Net.NetworkCredential]::new('none',$SecureAction).password)
            Write-Verbose -Message 'Action has been executed.'
            $true
        }
        else
        {
            Write-Verbose -Message 'Expect timeout without achiving a match.'
            $false
        }
    }
    End
    {
    }
}

# .ExternalHelp Posh-SSH.psm1-Help.xml
function Invoke-SSHStreamShellCommand {
    [CmdletBinding()]
    [Alias()]
    [OutputType([int])]
    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)]
        [string]
        $PromptPattern = '[\$#]\s*$',

        [Parameter(Mandatory = $false,
                   ValueFromPipelineByPropertyName = $true)]
        [int]
        $TimeoutSeconds = 0
    )

    Begin {
        $promptRegEx = [regex]$PromptPattern
    }
    Process {
        # Discard any banner or previous command output
        do { 
            $ShellStream.read() | Out-Null
        } while ($ShellStream.DataAvailable)

        $ShellStream.writeline($Command)

        # Discard line with command entered
        $ShellStream.ReadLine() | Out-Null
        Start-Sleep -Seconds 1

        $out = ''
        $promptFound = $false
        $startTime = Get-Date

        # Read all output until the prompt is found or timeout is reached
        while (-not $promptFound) {
            if ($ShellStream.DataAvailable) {
                $newData = $ShellStream.Read()
                $out += $newData

                # Check if the prompt is in the new data
                if ($newData -match $promptRegEx) {
                    $promptFound = $true
                }
            } else {
                Start-Sleep -Seconds 1
            }

            # Check for timeout
            if ($TimeoutSeconds -gt 0 -and ((Get-Date) - $startTime).TotalSeconds -ge $TimeoutSeconds) {
                Write-Warning "Timeout of $TimeoutSeconds seconds reached. Returning output collected so far."
                break
            }
        }

        $outputLines = $out.Split("`n")
        foreach ($line in $outputLines) {
            if ($line -notmatch $promptRegEx) {
                $line.Trim()
            }
        }
    }
    End{}
}

########################################################################################
# SFTP Functions


# .ExternalHelp Posh-SSH.psm1-Help.xml
function Get-SFTPSession
{
    param(
        [Parameter(Mandatory=$false,
                   Position=0)]
        [Alias('Index')]
        [Int32[]]
        $SessionId
    )

    Begin{}
    Process
    {
        if ($SessionId.Length -gt 0)
        {
            # Can not reference SFTPSessions directly so as to be able
            # to remove the sessions when Remove-Sftpession is used
            $result = foreach($session in $global:SFTPSessions)
            {
                if ($session.SessionId -in $SessionId)
                {
                    $session
                }
            }
        }
        else
        {
            $result = $Global:SFTPSessions.psobject.copy()
        }
        $result
    }
    End{}
}


# .ExternalHelp Posh-SSH.psm1-Help.xml
function Remove-SFTPSession
{
    [CmdletBinding(DefaultParameterSetName='Index')]
    param(
        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Index',
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [Alias('Index')]
        [Int32[]]
        $SessionId,

        [Parameter(Mandatory=$false,
                   ParameterSetName = 'Session',
                   ValueFromPipeline=$true,
                   Position=0)]
        [Alias('Session')]
        [SSH.SFTPSession[]]
        $SFTPSession
    )

        Begin {
            $sessions2remove = New-Object System.Collections.ArrayList
        }
        Process
        {
            if ($PSCmdlet.ParameterSetName -eq 'Index')
            {
                foreach($i in $SessionId)
                {
                    Write-Verbose $i
                    foreach($session in $Global:SFTPSessions)
                    {
                        if ($session.SessionId -eq $i)
                        {
                            [void]$sessions2remove.Add($session)
                        }
                    }
                }
            }

            if ($PSCmdlet.ParameterSetName -eq 'Session')
            {
                foreach($i in $SFTPSession)
                {
                    foreach($session in $global:SFTPSessions)
                    {
                        if ($session -eq $i)
                        {
                            [void]$sessions2remove.Add($session)
                        }
                    }
                }

            }

        }
        End {
            foreach($badsession in $sessions2remove)
            {
                 Write-Verbose "Removing session $($badsession.SessionId)"
                 if ($badsession.session.IsConnected)
                 {
                    $badsession.session.Disconnect()
                 }
                 $badsession.session.Dispose()
                 $Global:SFTPSessions.Remove($badsession)
                 Write-Verbose "Session $($badsession.SessionId) Removed"
            }
        }
}


# .ExternalHelp Posh-SSH.psm1-Help.xml
function Get-SFTPChildItem
{
    [CmdletBinding(DefaultParameterSetName='Index')]
    param(
        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Index',
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [Alias('Index')]
        [Int32[]]
        $SessionId,

        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Session',
                   ValueFromPipeline=$true,
                   Position=0)]
        [Alias('Session')]
        [SSH.SFTPSession[]]
        $SFTPSession,

        [Parameter(Mandatory=$false,
                   Position=1)]
        [string]
        $Path,

        [Parameter(Mandatory=$false,
                   Position=2)]
        [Alias('Recursive')]
        [switch]
        $Recurse,

        [Parameter(Mandatory=$false,
                   Position=3)]
        [switch]
        $Directory,

        [Parameter(Mandatory=$false,
                   Position=4)]
        [switch]
        $File,

        [Parameter(Mandatory=$false,
                   Position=5)]
        [string]
        $Name
    )

    Begin
    {
        function ConvertTo-UnixPath {
            param([string]$Path)
            # Convert Windows path to Unix-style path
            $Path = $Path -replace '\\', '/'
            # Remove drive letter if present
            $Path = $Path -replace '^[A-Za-z]:', ''
            # Ensure the path starts with a forward slash
            if (!$Path.StartsWith('/')) {
                $Path = '/' + $Path
            }
            return $Path
        }

        function Get-SFTPDirectoryRecursive
        {
            param($Path, $SFTPSession, $NameFilter)
            $Path = ConvertTo-UnixPath -Path $Path
            Write-Verbose "Attempting to list directory: $Path"
            $Sess.Session.ListDirectory($Path) | ForEach-Object {
                $keep = $false
                if ($File -and $Directory)
                {
                    # Item cannot be a file AND a directory
                }
                elseif ((!$File -and !$Directory) -or ($File -and !$_.IsDirectory) -or ($Directory -and $_.IsDirectory))
                {
                    if (@('.','..') -notcontains $_.Name)
                    {
                        $keep = $true
                    }
                }

                if ($keep -and (Test-WildcardMatch $_.Name $NameFilter))
                {
                    # Add Extension property for files
                    if (!$_.IsDirectory)
                    {
                        $extension = [System.IO.Path]::GetExtension($_.Name)
                        $_ | Add-Member -MemberType NoteProperty -Name "Extension" -Value $extension -Force
                    }
                    $_
                }

                if ($Recurse)
                {
                    if (($_.IsDirectory -and !$_.IsSocket) -eq $true -and @('.','..') -notcontains $_.Name)
                    {
                        Get-SFTPDirectoryRecursive -Path $_.FullName -SFTPSession $sess -NameFilter $NameFilter
                    }
                }
            }
        }

        function Test-WildcardMatch
        {
            param([string]$Name, [string]$Pattern)
            if ([string]::IsNullOrEmpty($Pattern)) { return $true }
            return $Name -like $Pattern
        }

        function Get-MatchingPaths
        {
            param($Session, $PathPattern)
            $PathPattern = ConvertTo-UnixPath -Path $PathPattern
            $directory = $PathPattern -replace '/[^/]+$', ''
            $filePattern = $PathPattern -replace '^.*/(?=[^/]+$)', ''

            if ([string]::IsNullOrEmpty($directory))
            {
                $directory = $Session.Session.WorkingDirectory
            }

            if ($Session.Session.Exists($directory))
            {
                return $Session.Session.ListDirectory($directory) | 
                    Where-Object { $_.Name -like $filePattern } | 
                    Select-Object -ExpandProperty FullName
            }
            return @()
        }

        $ToProcess = @()
        switch($PSCmdlet.ParameterSetName)
        {
            'Session'
            {
                $ToProcess = $SFTPSession
            }

            'Index'
            {
                foreach($session in $Global:SFTPSessions)
                {
                    if ($SessionId -contains $session.SessionId)
                    {
                        $ToProcess += $session
                    }
                }
            }
        }
    }

    Process
    {
        foreach($Sess in $ToProcess)
        {
            if ([string]::IsNullOrEmpty($Path))
            {
                $Path = $Sess.Session.WorkingDirectory
            }

            $Path = ConvertTo-UnixPath -Path $Path

            $matchingPaths = Get-MatchingPaths -Session $Sess -PathPattern $Path

            foreach ($matchedPath in $matchingPaths)
            {
                $Attribs = Get-SFTPPathAttribute -SFTPSession $Sess -Path $matchedPath
                if (!$Attribs.IsDirectory)
                {
                    Write-Warning "Specified path of $($matchedPath) is not a directory. Skipping."
                    continue
                }

                Get-SFTPDirectoryRecursive -Path $matchedPath -SFTPSession $Sess -NameFilter $Name
            }
        }
    }
    End{}
}

# .ExternalHelp Posh-SSH.psm1-Help.xml
function Test-SFTPPath
{
    [CmdletBinding(DefaultParameterSetName='Index')]
    param(
        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Index',
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [Alias('Index')]
        [Int32[]]
        $SessionId,

        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Session',
                   ValueFromPipeline=$true,
                   Position=0)]
        [Alias('Session')]
        [SSH.SFTPSession[]]
        $SFTPSession,

        [Parameter(Mandatory=$true,
                   Position=1)]
        [string]
        $Path

     )

     Begin
     {
        $ToProcess = @()
        switch($PSCmdlet.ParameterSetName)
        {
            'Session'
            {
                $ToProcess = $SFTPSession
            }

            'Index'
            {
                foreach($session in $Global:SFTPSessions)
                {
                    if ($SessionId -contains $session.SessionId)
                    {
                        $ToProcess += $session
                    }
                }
            }
        }
     }
     Process
     {
        foreach($session in $ToProcess)
        {

            $session.Session.Exists($Path)
        }
     }
     End{}
}

# .ExternalHelp Posh-SSH.psm1-Help.xml
function Remove-SFTPItem
{
    [CmdletBinding(DefaultParameterSetName='Index')]
    param(
        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Index',
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [Alias('Index')]
        [Int32[]]
        $SessionId,

        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Session',
                   ValueFromPipeline=$true,
                   Position=0)]
        [Alias('Session')]
        [SSH.SFTPSession[]]
        $SFTPSession,

        [Parameter(Mandatory=$true,
                   Position=1)]
        [string]
        $Path,

        # Force the deletion of a none empty directory by recursively deleting all files in it.
        [Parameter(Mandatory=$false)]
        [switch]
        $Force

     )

     Begin
     {
        $ToProcess = @()
        switch($PSCmdlet.ParameterSetName)
        {
            'Session'
            {
                $ToProcess = $SFTPSession
            }

            'Index'
            {
                foreach($session in $Global:SFTPSessions)
                {
                    if ($SessionId -contains $session.SessionId)
                    {
                        $ToProcess += $session
                    }
                }
            }
        }
     }
     Process
     {

        foreach($session in $ToProcess)
        {

            if (Test-SFTPPath -SFTPSession $session -Path $Path)
            {
                $attr = Get-SFTPPathAttribute -SFTPSession $session -Path $Path
                if ($attr.IsDirectory)
                {
                    $content = Get-SFTPChildItem -SFTPSession $session -Path $Path
                    if ($content.count -gt 2 -and !$Force)
                    {
                        throw "Specified path of $($Path) is not an empty directory."
                    }
                    elseif ($Force)
                    {
                        Write-Verbose -Message "Recursively deleting $($Path)."
                        [SSH.SshModHelper]::DeleteDirectoryRecursive($Path, $session.Session)
                        return
                    }
                }
                Write-Verbose -Message "Removing $($Path)."
                $session.Session.Delete($Path)
                Write-Verbose -Message "$($Path) removed."
            }
            else
            {
                Write-Error -Message "Specified path of $($Path) does not exist." -Exception (New-Object System.IO.FileNotFoundException) -CategoryActivity "Remove-SFTPItem" 
                #throw "Specified path of $($Path) does not exist."
            }

        }
     }
     End{}
}

# .ExternalHelp Posh-SSH.psm1-Help.xml
function Move-SFTPItem
{
    [CmdletBinding(DefaultParameterSetName='Index', SupportsShouldProcess=$true)]
    param(
        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Index',
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [Alias('Index')]
        [Int32[]]
        $SessionId,

        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Session',
                   ValueFromPipeline=$true,
                   Position=0)]
        [Alias('Session')]
        [SSH.SFTPSession[]]
        $SFTPSession,

        [Parameter(Mandatory=$true,
                   Position=1,
                   ValueFromPipelineByPropertyName = $true)]
        [Alias('FullName')]
        [string]
        $Path,

        [Parameter(Mandatory=$true,
                   Position=2)]
        [string]
        $Destination,

        [Parameter(Mandatory=$false)]
        [switch]
        $Force
     )

     Begin
     {
        $ToProcess = @()
        switch($PSCmdlet.ParameterSetName)
        {
            'Session'
            {
                $ToProcess = $SFTPSession
            }

            'Index'
            {
                foreach($session in $Global:SFTPSessions)
                {
                    if ($SessionId -contains $session.SessionId)
                    {
                        $ToProcess += $session
                    }
                }
            }
        }
     }
     Process
     {
        foreach($session in $ToProcess)
        {
            if (Test-SFTPPath -SFTPSession $session -Path $Path)
            {
                $itemInfo = $session.Session.Get($Path)
                
                # Check if destination exists
                if ($session.Session.Exists($Destination))
                {
                    if ($Force)
                    {
                        if ($PSCmdlet.ShouldProcess($Destination, "Delete existing file"))
                        {
                            Write-Verbose "Destination file exists. Deleting $Destination"
                            $session.Session.DeleteFile($Destination)
                        }
                    }
                    else
                    {
                        throw "Destination file $Destination already exists. Use -Force to overwrite."
                    }
                }

                if ($PSCmdlet.ShouldProcess($Path, "Move to $Destination"))
                {
                    Write-Verbose "Moving $Path to $Destination"
                    $itemInfo.MoveTo($Destination)
                }
            }
            else
            {
                throw "Specified path of $Path does not exist."
            }
        }
     }
     End{}
}

# .ExternalHelp Posh-SSH.psm1-Help.xml
function Set-SFTPLocation
{
    [CmdletBinding(DefaultParameterSetName='Index')]
    param(
        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Index',
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [Alias('Index')]
        [Int32[]]
        $SessionId,

        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Session',
                   ValueFromPipeline=$true,
                   Position=0)]
        [Alias('Session')]
        [SSH.SFTPSession[]]
        $SFTPSession,

        [Parameter(Mandatory=$true,
                   Position=1)]
        [string]
        $Path

     )

     Begin
     {
        $ToProcess = @()
        switch($PSCmdlet.ParameterSetName)
        {
            'Session'
            {
                $ToProcess = $SFTPSession
            }

            'Index'
            {
                foreach($session in $Global:SFTPSessions)
                {
                    if ($SessionId -contains $session.SessionId)
                    {
                        $ToProcess += $session
                    }
                }
            }
        }
     }
     Process
     {

        foreach($session in $ToProcess)
        {
            $Attribs = Get-SFTPPathAttribute -SFTPSession $session -Path $Path
            if ($Attribs.IsDirectory)
            {
                Write-Verbose -Message "Changing current directory to $($Path)"
                $session.Session.ChangeDirectory($Path)
                Write-Verbose -Message 'Current directory changed.'
            }
            else
            {
                throw "Specified path of $($Path) is not a directory."
            }
        }
     }
     End{}
}


# .ExternalHelp Posh-SSH.psm1-Help.xml
function Get-SFTPLocation
{
    [CmdletBinding(DefaultParameterSetName='Index')]
    param(
        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Index',
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [Alias('Index')]
        [Int32[]]
        $SessionId,

        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Session',
                   ValueFromPipeline=$true,
                   Position=0)]
        [Alias('Session')]
        [SSH.SFTPSession[]]
        $SFTPSession
     )

     Begin
     {
        $ToProcess = @()
        switch($PSCmdlet.ParameterSetName)
        {
            'Session'
            {
                $ToProcess = $SFTPSession
            }

            'Index'
            {
                foreach($session in $Global:SFTPSessions)
                {
                    if ($SessionId -contains $session.SessionId)
                    {
                        $ToProcess += $session
                    }
                }
            }
        }
     }
     Process
     {

        foreach($session in $ToProcess)
        {
            $session.Session.WorkingDirectory
        }

     }
    End{}
}

# .ExternalHelp Posh-SSH.psm1-Help.xml
function Rename-SFTPFile
{
    [CmdletBinding(DefaultParameterSetName='Index')]
    param(
        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Index',
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [Alias('Index')]
        [Int32[]]
        $SessionId,

        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Session',
                   ValueFromPipeline=$true,
                   Position=0)]
        [Alias('Session')]
        [SSH.SFTPSession[]]
        $SFTPSession,

        # Full path to file to rename
        [Parameter(Mandatory=$true,
                   Position=1)]
        [string]
        $Path,

        # New name for file.
        [Parameter(Mandatory=$true,
                   Position=2)]
        [string]
        $NewName
     )

     Begin
     {
        $ToProcess = @()
        switch($PSCmdlet.ParameterSetName)
        {
            'Session'
            {
                $ToProcess = $SFTPSession
            }

            'Index'
            {
                foreach($session in $Global:SFTPSessions)
                {
                    if ($SessionId -contains $session.SessionId)
                    {
                        $ToProcess += $session
                    }
                }
            }
        }
     }
     Process
     {

        foreach($session in $ToProcess)
        {
            $attrib = Get-SFTPPathAttribute -SFTPSession $session -Path $Path
            if ($attrib.IsRegularFile)
            {
                $ContainerPath = Split-Path -Path $Path |ForEach-Object {$_ -replace '\\','/'}
                Write-Verbose "Renaming $($Path) to $($NewName)"
                $session.Session.RenameFile($Path, "$($ContainerPath)/$($NewName)")
                Write-Verbose 'File renamed'
            }
            else
            {
                Write-Error -Message "The specified path $($Path) is not to a file."
            }
        }
     }
    End{}
}


# .ExternalHelp Posh-SSH.psm1-Help.xml
function Get-SFTPPathAttribute
{
    [CmdletBinding()]
    [OutputType([Renci.SshNet.Sftp.SftpFileAttributes])]
    Param
    (
        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Index',
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [Alias('Index')]
        [Int32[]]
        $SessionId,

        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Session',
                   ValueFromPipeline=$true,
                   Position=0)]
        [Alias('Session')]
        [SSH.SFTPSession[]]
        $SFTPSession,

        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   Position=1)]
        [string]
        $Path
    )

    Begin
    {
        $ToProcess = @()
        switch($PSCmdlet.ParameterSetName)
        {
            'Session'
            {
                $ToProcess = $SFTPSession
            }

            'Index'
            {
                foreach($session in $Global:SFTPSessions)
                {
                    if ($SessionId -contains $session.SessionId)
                    {
                        $ToProcess += $session
                    }
                }
            }
        }
    }
    Process
    {
        foreach($session in $ToProcess)
        {

            if (Test-SFTPPath -SFTPSession $session -Path $Path)
            {
                $session.Session.GetAttributes($Path)
            }
            else
            {
                throw "Path $($Path) does not exist on the target host."
            }
        }
    }
    End
    {
    }
}


# .ExternalHelp Posh-SSH.psm1-Help.xml
function Set-SFTPPathAttribute
{
    [CmdletBinding()]
    [OutputType([Renci.SshNet.Sftp.SftpFileAttributes])]
    Param
    (
        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Index',
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [Alias('Index')]
        [Int32[]]
        $SessionId,

        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Session',
                   ValueFromPipeline=$true,
                   Position=0)]
        [Alias('Session')]
        [SSH.SFTPSession[]]
        $SFTPSession,

        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   Position=1)]
        [string]
        $Path,

        [Parameter(Mandatory=$false,
                   ValueFromPipeline=$true)]
        [datetime]
        $LastAccessTime,

        [Parameter(Mandatory=$false,
                   ValueFromPipeline=$true)]
        [datetime]
        $LastWriteTime,

        [Parameter(Mandatory=$false,
                   ValueFromPipeline=$true)]
        [int]
        $GroupId,

        [Parameter(Mandatory=$false,
                   ValueFromPipeline=$true)]
        [int]
        $UserId,

        [Parameter(Mandatory=$false,
                   ValueFromPipeline=$true)]
        [bool]
        $GroupCanExecute,

        [Parameter(Mandatory=$false,
                   ValueFromPipeline=$true)]
        [bool]
        $GroupCanRead,

        [Parameter(Mandatory=$false,
                   ValueFromPipeline=$true)]
        [bool]
        $GroupCanWrite,

        [Parameter(Mandatory=$false,
                   ValueFromPipeline=$true)]
        [bool]
        $OthersCanExecute,

        [Parameter(Mandatory=$false,
                   ValueFromPipeline=$true)]
        [bool]
        $OthersCanRead,

        [Parameter(Mandatory=$false,
                   ValueFromPipeline=$true)]
        [bool]
        $OthersCanWrite,

        [Parameter(Mandatory=$false,
                   ValueFromPipeline=$true)]
        [bool]
        $OwnerCanExecute,

        [Parameter(Mandatory=$false,
                   ValueFromPipeline=$true)]
        [bool]
        $OwnerCanRead,

        [Parameter(Mandatory=$false,
                   ValueFromPipeline=$true)]
        [bool]
        $OwnerCanWrite
    )

    Begin
    {
        $ToProcess = @()
        switch($PSCmdlet.ParameterSetName)
        {
            'Session'
            {
                $ToProcess = $SFTPSession
            }

            'Index'
            {
                foreach($session in $Global:SFTPSessions)
                {
                    if ($SessionId -contains $session.SessionId)
                    {
                        $ToProcess += $session
                    }
                }
            }
        }
    }
    Process
    {
        foreach($session in $ToProcess)
        {

            if (Test-SFTPPath -SFTPSession $session -Path $Path)
            {
                $currentAttrib = $session.Session.GetAttributes($Path)

                if($PSBoundParameters.ContainsKey("OwnerCanWrite"))
                {
                    $currentAttrib.OwnerCanWrite = $OwnerCanWrite
                }

                if($PSBoundParameters.ContainsKey("OwnerCanRead"))
                {
                    $currentAttrib.OwnerCanRead = $OwnerCanRead
                }

                if($PSBoundParameters.ContainsKey("OwnerCanExecute"))
                {
                    $currentAttrib.OwnerCanExecute = $OwnerCanExecute
                }

                if($PSBoundParameters.ContainsKey("OthersCanWrite"))
                {
                    $currentAttrib.OthersCanWrite = $OthersCanWrite
                }

                if($PSBoundParameters.ContainsKey("OthersCanRead"))
                {
                    $currentAttrib.OthersCanRead = $OthersCanRead
                }

                if($PSBoundParameters.ContainsKey("OthersCanExecute"))
                {
                    $currentAttrib.OthersCanExecute = $OthersCanExecute
                }


                if($PSBoundParameters.ContainsKey("GroupCanWrite"))
                {
                    $currentAttrib.GroupCanWrite = $GroupCanWrite
                }

                if($PSBoundParameters.ContainsKey("GroupCanRead"))
                {
                    $currentAttrib.GroupCanRead = $GroupCanRead
                }

                if($PSBoundParameters.ContainsKey("GroupCanExecute"))
                {
                    $currentAttrib.GroupCanExecute = $GroupCanExecute
                }

                if($PSBoundParameters.ContainsKey("UserId"))
                {
                    $currentAttrib.UserId = $UserId
                }

                if($PSBoundParameters.ContainsKey("GroupId"))
                {
                    $currentAttrib.GroupId = $GroupId
                }

                if($PSBoundParameters.ContainsKey("LastWriteTime"))
                {
                    $currentAttrib.LastWriteTime = $LastWriteTime
                }

                if($PSBoundParameters.ContainsKey("LastAccessTime"))
                {
                    $currentAttrib.LastAccessTime = $LastAccessTime
                }

                $session.Session.SetAttributes($Path, $currentAttrib)


            }
            else
            {
                throw "Path $($Path) does not exist on the target host."
            }
        }
    }
    End
    {
    }
}

# .ExternalHelp Posh-SSH.psm1-Help.xml
function Get-SFTPPathInformation
{
    [CmdletBinding()]
    [OutputType([Renci.SshNet.Sftp.SftpFileSytemInformation])]
    Param
    (
        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Index',
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [Alias('Index')]
        [Int32[]]
        $SessionId,

        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Session',
                   ValueFromPipeline=$true,
                   Position=0)]
        [Alias('Session')]
        [SSH.SFTPSession[]]
        $SFTPSession,

        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   Position=1)]
        [string]
        $Path
    )

    Begin
    {
        $ToProcess = @()
        switch($PSCmdlet.ParameterSetName)
        {
            'Session'
            {
                $ToProcess = $SFTPSession
            }

            'Index'
            {
                foreach($session in $Global:SFTPSessions)
                {
                    if ($SessionId -contains $session.SessionId)
                    {
                        $ToProcess += $session
                    }
                }
            }
        }
    }
    Process
    {
        foreach($session in $ToProcess)
        {

            if (Test-SFTPPath -SFTPSession $session -Path $Path)
            {
                $session.Session.GetStatus($Path)
            }
            else
            {
                throw "Path $($Path) does not exist on the target host."
            }
        }
    }
    End
    {
    }
}

# .ExternalHelp Posh-SSH.psm1-Help.xml
function New-SFTPSymlink
{
    [CmdletBinding(DefaultParameterSetName='Index')]
    Param
    (
        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Index',
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [Alias('Index')]
        [Int32[]]
        $SessionId,

        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Session',
                   ValueFromPipeline=$true,
                   Position=0)]
        [Alias('Session')]
        [SSH.SFTPSession[]]
        $SFTPSession,

        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=1)]
        [String]
        $Path,

        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=2)]
        [String]
        $LinkPath
    )

    Begin
    {
        $ToProcess = @()
        switch($PSCmdlet.ParameterSetName)
        {
            'Session'
            {
                $ToProcess = $SFTPSession
            }

            'Index'
            {
                foreach($session in $Global:SFTPSessions)
                {
                    if ($SessionId -contains $session.SessionId)
                    {
                        $ToProcess += $session
                    }
                }
            }
        }
    }
    Process
    {
        foreach($session in $ToProcess)
        {
            $filepath = Test-SFTPPath -SFTPSession $session -Path $Path
            $linkstatus = Test-SFTPPath -SFTPSession $session -path $LinkPath
            if (($filepath) -and (!$linkstatus))
            {
                try
                {
                    Write-Verbose -Message "Creating symlink for $($Path) to $($LinkPath)"
                    $session.session.SymbolicLink($Path, $LinkPath)
                    $session.session.Get($LinkPath)
                }
                catch
                {
                    Write-Error -Exception $_.Exception
                }

            }
            else
            {
                if ($linkstatus)
                {
                    Write-Error -Message "A file already exists in the path of the link $($linkstatus)"
                }

                if (!$filepath)
                {
                    Write-Error -Message "The path $($Path) to link does not exist"
                }
            }
        }
    }
    End
    {
    }
}


# .ExternalHelp Posh-SSH.psm1-Help.xml
function Get-SFTPContent
{
    [CmdletBinding(DefaultParameterSetName='Index')]
    Param
    (
        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Index',
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [Alias('Index')]
        [Int32[]]
        $SessionId,

        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Session',
                   ValueFromPipeline=$true,
                   Position=0)]
        [Alias('Session')]
        [SSH.SFTPSession[]]
        $SFTPSession,

        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=1)]
        [String]
        $Path,

        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true,
                   Position=2)]
        [ValidateSet('String', 'Byte', 'MultiLine')]
        [string]
        $ContentType = 'String',

        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true)]
        [ValidateSet('ASCII','Unicode', 'UTF7', 'UTF8', 'UTF32', 'BigEndianUnicode')]
        [string]
        $Encoding='UTF8'
    )

    Begin
    {

        $ToProcess = @()
        switch($PSCmdlet.ParameterSetName)
        {
            'Session'
            {
                $ToProcess = $SFTPSession
            }

            'Index'
            {
                foreach($session in $Global:SFTPSessions)
                {
                    if ($SessionId -contains $session.SessionId)
                    {
                        $ToProcess += $session
                    }
                }
            }
        }

        # Set encoding.
        switch ($Encoding)
        {
            'ASCII' {
                $ContentEncoding = [System.Text.Encoding]::ASCII
            }

            'Unicode' {
                $ContentEncoding = [System.Text.Encoding]::Unicode
            }

            'UTF7' {
                $ContentEncoding = [System.Text.Encoding]::UTF7
            }

            'UTF8' {
                $ContentEncoding = [System.Text.Encoding]::UTF8
            }

            'UTF32' {
                $ContentEncoding = [System.Text.Encoding]::UTF32
            }

            'BigEndianUnicode'{
                $ContentEncoding = [System.Text.Encoding]::BigEndianUnicode
            }
        }
    }
    Process
    {
        foreach($session in $ToProcess)
        {

            $attrib = Get-SFTPPathAttribute -SFTPSession $session -Path $Path
            if ($attrib.IsRegularFile)
            {
                try
                {
                switch ($ContentType)
                {
                    'String' {

                        $session.session.ReadAllText($Path, $ContentEncoding)

                     }

                    'Byte' {

                        $session.session.ReadAllBytes($Path)

                     }

                    'MultiLine' {

                        $session.session.ReadAllLines($Path, $Value, $ContentEncoding)

                    }
                    Default {$session.session.ReadAllBytes($Path)}
                }
            }
                catch
                {
                Write-Error -Exception $_.Exception -Message "Failed to get content to file $($Path)"
            }
            }
            else
            {
                Write-Error -Message "The specified path $($Path) is not to a file."
            }
        }
    }
    End
    {
    }
}


# .ExternalHelp Posh-SSH.psm1-Help.xml
function Set-SFTPContent
{
    [CmdletBinding(DefaultParameterSetName='Index')]
    [OutputType([Renci.SshNet.Sftp.SftpFile])]
    Param
    (
        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Index',
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [Alias('Index')]
        [Int32[]]
        $SessionId,

        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Session',
                   ValueFromPipeline=$true,
                   Position=0)]
        [Alias('Session')]
        [SSH.SFTPSession[]]
        $SFTPSession,

        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=1)]
        [String]
        $Path,

        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=2)]
        $Value,

        [Parameter(Mandatory=$false,
                   ValueFromPipelineByPropertyName=$true)]
        [ValidateSet('ASCII','Unicode', 'UTF7', 'UTF8', 'UTF32', 'BigEndianUnicode')]
        [string]
        $Encoding='UTF8',

        [switch]
        $Append


    )

    Begin
    {
        $ToProcess = @()
        switch($PSCmdlet.ParameterSetName)
        {
            'Session'
            {
                $ToProcess = $SFTPSession
            }

            'Index'
            {
                foreach($session in $Global:SFTPSessions)
                {
                    if ($SessionId -contains $session.SessionId)
                    {
                        $ToProcess += $session
                    }
                }
            }
        }

        # Set encoding.
        switch ($Encoding)
        {
            'ASCII' {
                $ContentEncoding = [System.Text.Encoding]::ASCII
            }

            'Unicode' {
                $ContentEncoding = [System.Text.Encoding]::Unicode
            }

            'UTF7' {
                $ContentEncoding = [System.Text.Encoding]::UTF7
            }

            'UTF8' {
                $ContentEncoding = New-Object System.Text.UTF8Encoding $false
            }

            'UTF32' {
                $ContentEncoding = [System.Text.Encoding]::UTF32
            }

            'BigEndianUnicode'{
                $ContentEncoding = [System.Text.Encoding]::BigEndianUnicode
            }
        }

    }
    Process
    {
        foreach($session in $ToProcess)
        {
            $ValueType = $Value.GetType().Name
            write-verbose -message  "Saving a $($ValueType) to $($Path)."
            try
            {
                switch ($ValueType)
                {
                    'string[]' {
                        if ($Append)
                        {
                            $session.session.AppendAllLines($Path, $Value, $ContentEncoding)
                        }
                        else
                        {
                            $session.session.WriteAllLines($Path, $Value, $ContentEncoding)
                        }
                        $session.session.Get($Path)
                     }

                    'byte[]' {
                        if ($Append)
                        {
                            $session.session.WriteAllBytes($Path, $Value)
                        }
                        else
                        {
                            $session.session.WriteAllBytes($Path, $Value)
                        }
                        $session.session.Get($Path)
                     }

                    'string' {
                        if ($Append)
                        {
                            $session.session.AppendAllText($Path, $Value, $ContentEncoding)
                        }
                        else
                        {
                            $session.session.WriteAllText($Path, $Value, $ContentEncoding)
                        }

                        $session.session.Get($Path)
                    }
                    Default {Write-Error -Message "The value of type $($ValueType) is not supported."}
                }
            }
            catch
            {
                Write-Error -Exception $_.Exception -Message "Failed to write content to file $($Path)"
            }
        }
    }
    End
    {
    }
}


# .ExternalHelp Posh-SSH.psm1-Help.xml
function New-SFTPFileStream
{
    [CmdletBinding(DefaultParameterSetName='Index')]
    [OutputType([Renci.SshNet.Sftp.SftpFileStream])]
    Param
    (
        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Index',
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [Alias('Index')]
        [Int32]
        $SessionId,

        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Session',
                   ValueFromPipeline=$true,
                   Position=0)]
        [Alias('Session')]
        [SSH.SFTPSession]
        $SFTPSession,

        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=1)]
        [String]
        $Path,

        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=2)]
        [ValidateSet('Append', 'Create', 'CreateNew', 'Open', 'OpenOrCreate', 'Truncate')]
        [string]
        $FileMode,

        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=3)]
        [ValidateSet('Read', 'ReadWrite', 'Write')]
        [string]
        $FileAccess
    )

    Begin
    {
        $ToProcess = $null
        switch($PSCmdlet.ParameterSetName)
        {
            'Session'
            {
                $ToProcess = $SFTPSession
            }

            'Index'
            {
                $sess = Get-SFTPSession -Index $SessionId
                if ($sess)
                {
                    $ToProcess = $sess
                }
                else
                {
                    Write-Error -Message "Session specified with Index $($SessionId) was not found"
                    return
                }
            }
        }

        # Set FileAccess.
        switch ($FileAccess)
        {
            'Read' {
                $StreamFileAccess = [System.IO.FileAccess]::Read
            }

            'ReadWrite' {
                $StreamFileAccess = [System.IO.FileAccess]::ReadWrite
            }

            'Write' {
                $StreamFileAccess = [System.IO.FileAccess]::Write
            }
        }

        # Set FileMode.
        switch ($FileMode)
        {
            'Append' {
                $StreamFileMode = [System.IO.FileMode]::Append
            }

            'Create' {
                $StreamFileMode = [System.IO.FileMode]::Create
            }

            'CreateNew' {
                $StreamFileMode = [System.IO.FileMode]::CreateNew
            }

            'Open' {
                $StreamFileMode = [System.IO.FileMode]::Open
            }

            'OpenOrCreate' {
                $StreamFileMode = [System.IO.FileMode]::OpenOrCreate
            }

            'Truncate' {
                $StreamFileMode = [System.IO.FileMode]::Truncate
            }
        }
    }
    Process
    {
        $ToProcess.Session.Open($Path, $StreamFileMode, $StreamFileAccess)
    }
    End
    {
    }
}


# .ExternalHelp Posh-SSH.psm1-Help.xml
function New-SFTPItem
{
  [CmdletBinding(DefaultParameterSetName='Index')]
    param(
        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Index',
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [Alias('Index')]
        [Int32[]]
        $SessionId,

        [Parameter(Mandatory=$true,
                   ParameterSetName = 'Session',
                   ValueFromPipeline=$true,
                   Position=0)]
        [Alias('Session')]
        [SSH.SFTPSession[]]
        $SFTPSession,

        [Parameter(Mandatory=$true,
                   Position=1)]
        [string]
        $Path,

        [Parameter(Mandatory=$false,
                   Position=2)]
        [ValidateSet('File', 'Directory')]
        [string]
        $ItemType = 'File',

        [Parameter(Mandatory=$false)]
        [switch]
        $Recurse

    )

    Begin
    {
        $ToProcess = @()
        switch($PSCmdlet.ParameterSetName)
        {
            'Session'
            {
                $ToProcess = $SFTPSession
            }

            'Index'
            {
                foreach($session in $Global:SFTPSessions)
                {
                    if ($SessionId -contains $session.SessionId)
                    {
                        $ToProcess += $session
                    }
                }
            }
        }
    }
    Process
    {
        foreach($sess in $ToProcess)
        {
            if (!$sess.Session.Exists($Path))
            {
                switch($ItemType)
                {
                    'Directory' {
                        Write-Verbose -Message "Creating directory $($Path)"
                        if ($Recurse) {
                            $components = $Path.Split('/')
                            $level = 0
                            $newPath = ''
                            if($Path[0] -eq '.') {
                                $newPath = '.'
                                $level = 1
                            } elseif ($Path[0] -eq '/') {
                                $level = 1
                            } else {
                                $level = 0
                            }

                            for ($level; $level -le ($components.Count -1); $level++ )
                            {
                                if ($level -gt 0)
                                {
                                    $newpath = $newPath + '/' + $components[$level]
                                }
                                else
                                {
                                    $newpath = $newPath + $components[$level]
                                }
                                Write-Verbose -message "Checking if $($newPath) exists."
                                if ($sess.Session.Exists($newPath)) {
                                    write-Verbose -message "$($newPath) exist."
                                    $attr = $sess.Session.GetAttributes($newPath)
                                    if (!$attr.IsDirectory) {
                                        throw "Path in recursive creation is not a directory."
                                    } else {
                                        write-Verbose -message "$($newPath) is directory"
                                    }
                                } else {
                                    Write-Verbose -Message "Creating $($newPath)"
                                    $sess.Session.CreateDirectory($newPath)
                                }
                            }
                            $sess.Session.Get($Path)
                        } else {
                            $sess.Session.CreateDirectory($Path)
                            Write-Verbose -Message 'Directory succesfully created.'
                            $sess.Session.Get($Path)
                        }
                    }

                    'File' {
                        Write-Verbose -Message "Creating file $($Path)"
                        $sess.Session.Create($Path).close()
                        Write-Verbose -Message 'File succesfully created.'
                        $sess.Session.Get($Path)
                    }

                }
            }
            else
            {
                Write-Error -Message "Specified path of $($Path) already exists."
            }
        }
    }
    End
    {
    }
}





##############################################################################################
# SSH Port Forwarding

# .ExternalHelp Posh-SSH.psm1-Help.xml
function New-SSHLocalPortForward
{
    [CmdletBinding(DefaultParameterSetName="Index")]
    param(
        [Parameter(Mandatory=$true,
            Position=1)]
        [String]
        $BoundHost,

        [Parameter(Mandatory=$true,
            Position=2)]
        [Int32]
        $BoundPort,

        [Parameter(Mandatory=$true,
            Position=3)]
        [String]
        $RemoteAddress,

        [Parameter(Mandatory=$true,
            Position=4)]
        [Int32]
        $RemotePort,

        [Parameter(Mandatory=$true,
            ParameterSetName = "Session",
            ValueFromPipeline=$true,
            Position=0)]
        [Alias("Session")]
        [SSH.SSHSession]
        $SSHSession,

        [Parameter(Mandatory=$true,
            ParameterSetName = "Index",
            ValueFromPipeline=$true,
            Position=0)]
        [Alias('Index')]
        [Int32]
        $SessionId
    )

    Begin
    {
        $ToProcess = $null
        switch($PSCmdlet.ParameterSetName)
        {
            'Session'
            {
                $ToProcess = $SSHSession
            }

            'Index'
            {
                $sess = Get-SSHSession -Index $SessionId
                if ($sess)
                {
                    $ToProcess = $sess
                }
                else
                {
                    Write-Error -Message "Session specified with Index $($SessionId) was not found"
                    return
                }
            }
        }

    }
    Process
    {
        $ports = $ToProcess.Session.ForwardedPorts
        foreach($p in $ports)
        {
            if (($p.BoundPort -eq $BoundPort) -and ($p.BoundHost -eq $BoundHost))
            {
                Write-Error -Message "A forward port already exists for port $($BoundPort) with address $($LocalAdress)"
                return
            }
        }
        # Initialize the ForwardPort Object
        $SSHFWP = New-Object Renci.SshNet.ForwardedPortLocal($BoundHost, $BoundPort, $RemoteAddress, $RemotePort)

        # Add the forward port object to the session
        Write-Verbose -message "Adding Forward Port Configuration to session $($ToProcess.Index)"
        $ToProcess.session.AddForwardedPort($SSHFWP)
        Write-Verbose -message "Starting the Port Forward."
        $SSHFWP.start()
        Write-Verbose -message "Forwarding has been started."

    }
    End{}


}

# .ExternalHelp Posh-SSH.psm1-Help.xml
function New-SSHRemotePortForward
{
    [CmdletBinding(DefaultParameterSetName="Index")]
    param(
    [Parameter(Mandatory=$false)]
    [String]$LocalAdress = '127.0.0.1',

    [Parameter(Mandatory=$true)]
    [Int32]$LocalPort,

    [Parameter(Mandatory=$true)]
    [String]$RemoteAddress,

    [Parameter(Mandatory=$true)]
    [Int32]$RemotePort,

    [Parameter(Mandatory=$true,
        ParameterSetName = "Session",
        ValueFromPipeline=$true)]
    [Alias("Session")]
    [SSH.SSHSession]$SSHSession,

    [Parameter(Mandatory=$true,
        ParameterSetName = "Index",
        ValueFromPipeline=$true)]
    [Alias("Index")]
    [Int32]$SessionId = $null
    )

    Begin
    {
        # Initialize the ForwardPort Object
        $SSHFWP = New-Object Renci.SshNet.ForwardedPortRemote($LocalAdress, $LocalPort, $RemoteAddress, $RemotePort)
    }
    Process
    {
        if ($PSCmdlet.ParameterSetName -eq 'Index')
        {
            Write-Verbose "Finding session with Index $SessionId"
            foreach($session in $Global:SshSessions)
            {
                Write-Verbose $session.index
                if ($session.index -eq $SessionId)
                {
                    # Add the forward port object to the session
                    Write-Verbose "Adding Forward Port Configuration to session $SessionId"
                    $session.session.AddForwardedPort($SSHFWP)
                    Write-Verbose "Starting the Port Forward."
                    $SSHFWP.start()
                    Write-Verbose "Forwarding has been started."
                }
            }
        }
        elseif ($PSCmdlet.ParameterSetName -eq 'Session')
        {
            if ($SSHSession -in $Global:SshSessions)
            {
                # Add the forward port object to the session
                Write-Verbose "Adding Forward Port Configuration to session $($SSHSession.index)"
                $SSHSession.session.AddForwardedPort($SSHFWP)
                Write-Verbose "Starting the Port Forward."
                $SSHFWP.start()
                Write-Verbose "Forwarding has been started."
            }
            else
            {
                Write-Error "The Session does not appear in the list of created sessions."
            }
        }

    }
    End{}
}

# .ExternalHelp Posh-SSH.psm1-Help.xml
function New-SSHDynamicPortForward
{
    [CmdletBinding(DefaultParameterSetName="Index")]
    param(
        [Parameter(Mandatory=$false,
            Position=1)]
        [String]
        $BoundHost = 'localhost',

        [Parameter(Mandatory=$true,
            Position=2)]
        [Int32]
        $BoundPort,

        [Parameter(Mandatory=$true,
            ParameterSetName = "Session",
            ValueFromPipeline=$true,
            Position=0)]
        [Alias("Session")]
        [SSH.SSHSession]
        $SSHSession,

        [Parameter(Mandatory=$true,
            ParameterSetName = "Index",
            ValueFromPipeline=$true,
            Position=0)]
        [Alias('Index')]
        [Int32]
        $SessionId
    )

     Begin
    {
        $ToProcess = $null
        switch($PSCmdlet.ParameterSetName)
        {
            'Session'
            {
                $ToProcess = $SSHSession
            }

            'Index'
            {
                $sess = Get-SSHSession -Index $SessionId
                if ($sess)
                {
                    $ToProcess = $sess
                }
                else
                {
                    Write-Error -Message "Session specified with Index $($SessionId) was not found"
                    return
                }
            }
        }
    }
    Process
    {
        $ports = $ToProcess.Session.ForwardedPorts
        foreach($p in $ports)
        {
            if ($p.BoundHost -eq $BoundHost -and $p.BoundPort -eq $BoundPort)
            {
                throw "A forward port already exists for port $($BoundPort) with address $($BoundHost)"
            }
        }

         # Initialize the ForwardPort Object
        $SSHFWP = New-Object Renci.SshNet.ForwardedPortDynamic($BoundHost, $BoundPort)

        # Add the forward port object to the session
        Write-Verbose -message "Adding Forward Port Configuration to session $($ToProcess.Index)"
        $ToProcess.session.AddForwardedPort($SSHFWP)
        $ToProcess.session.KeepAliveInterval = New-TimeSpan -Seconds 30
        $ToProcess.session.ConnectionInfo.Timeout = New-TimeSpan -Seconds 20
        $ToProcess.session.SendKeepAlive()

        [System.Threading.Thread]::Sleep(500)
        Write-Verbose -message "Starting the Port Forward."
        $SSHFWP.start()
        Write-Verbose -message "Forwarding has been started."

    }
    End{}
}

# .ExternalHelp Posh-SSH.psm1-Help.xml
function Get-SSHPortForward
{
    [CmdletBinding(DefaultParameterSetName="Index")]
    param(
        [Parameter(Mandatory=$true,
            ParameterSetName = "Session",
            ValueFromPipeline=$true,
            Position=0)]
        [Alias("Session")]
        [SSH.SSHSession]
        $SSHSession,

        [Parameter(Mandatory=$true,
            ParameterSetName = "Index",
            ValueFromPipeline=$true,
            Position=0)]
        [Alias('Index')]
        [Int32]
        $SessionId
    )

     Begin
    {
        $ToProcess = $null
        switch($PSCmdlet.ParameterSetName)
        {
            'Session'
            {
                $ToProcess = $SSHSession
            }

            'Index'
            {
                $sess = Get-SSHSession -Index $SessionId
                if ($sess)
                {
                    $ToProcess = $sess
                }
                else
                {
                    Write-Error -Message "Session specified with Index $($SessionId) was not found"
                    return
                }
            }
        }
    }
    Process
    {

        $ToProcess.Session.ForwardedPorts

    }
    End{}
}

# .ExternalHelp Posh-SSH.psm1-Help.xml
function Stop-SSHPortForward
{
    [CmdletBinding(DefaultParameterSetName="Index")]
    param(

        [Parameter(Mandatory=$true,
            ParameterSetName = "Session",
            ValueFromPipeline=$true,
            Position=0)]
        [Alias("Session")]
        [SSH.SSHSession]
        $SSHSession,

        [Parameter(Mandatory=$true,
            ParameterSetName = "Index",
            ValueFromPipeline=$true,
            Position=0)]
        [Alias('Index')]
        [Int32]
        $SessionId,

        [Parameter(Mandatory=$true,
            Position=2)]
        [Int32]
        $BoundPort,

        [Parameter(Mandatory=$true,
            Position=1)]
        [string]
        $BoundHost
    )

     Begin
    {
         $ToProcess = $null
        switch($PSCmdlet.ParameterSetName)
        {
            'Session'
            {
                $ToProcess = $SSHSession
            }

            'Index'
            {
                $sess = Get-SSHSession -Index $SessionId
                if ($sess)
                {
                    $ToProcess = $sess
                }
                else
                {
                    Write-Error -Message "Session specified with Index $($SessionId) was not found"
                    return
                }
            }
        }
    }
    Process
    {
        $ports = $ToProcess.Session.ForwardedPorts
        foreach($p in $ports)
        {
            if ($p.BoundPort -eq $BoundPort -and $p.BoundHost -eq $BoundHost)
            {
                $p.Stop()
                $p
            }
        }
    }
    End{}
}#>

# .ExternalHelp Posh-SSH.psm1-Help.xml
function Start-SSHPortForward
{
    [CmdletBinding(DefaultParameterSetName="Index")]
    param(

        [Parameter(Mandatory=$true,
            ParameterSetName = "Session",
            ValueFromPipeline=$true,
            Position=0)]
        [Alias("Session")]
        [SSH.SSHSession]
        $SSHSession,

        [Parameter(Mandatory=$true,
            ParameterSetName = "Index",
            ValueFromPipeline=$true,
            Position=0)]
        [Alias('Index')]
        [Int32]
        $SessionId,

        [Parameter(Mandatory=$true,
            Position=2)]
        [Int32]
        $BoundPort,

        [Parameter(Mandatory=$true,
            Position=1)]
        [string]
        $BoundHost
    )

     Begin
    {
         $ToProcess = $null
        switch($PSCmdlet.ParameterSetName)
        {
            'Session'
            {
                $ToProcess = $SSHSession
            }

            'Index'
            {
                $sess = Get-SSHSession -Index $SessionId
                if ($sess)
                {
                    $ToProcess = $sess
                }
                else
                {
                    Write-Error -Message "Session specified with Index $($SessionId) was not found"
                    return
                }
            }
        }
    }
    Process
    {

        $ports = $ToProcess.Session.ForwardedPorts
        foreach($p in $ports)
        {
            if ($p.BoundPort -eq $BoundPort -and $p.BoundHost -eq $BoundHost)
            {
                $p.Start()
                $p
            }
        }

    }
    End{}
}

# .ExternalHelp Posh-SSH.psm1-Help.xml
function Get-SSHTrustedHost
{
    [CmdletBinding(DefaultParameterSetName = "Local")]
    [OutputType("SSH.Stores.KnownHostRecord")]
    Param(
        # Known Host Store
        [Parameter(Mandatory = $true,
           ParameterSetName = "Store",
           ValueFromPipeline = $true,
           Position = 1)]
        [Alias('KnowHostStore')]
        [SSH.Stores.IStore]
        $KnownHostStore,

        # Host name the key fingerprint is associated with.
        [Parameter(Mandatory = $false,
           Position = 0)
        ]
        [String]
        $HostName
    )

    Begin{
        $Default = [IO.Path]::Combine($Home,".poshssh", "hosts.json")
    }
    Process
    {
       if ($PSCmdlet.ParameterSetName -eq "Local") {
           $Store = Get-SSHJsonKnownHost
            if (-not (Test-Path -PathType Leaf $Default)) {
                Write-Warning -Message "No known host file found, $($Default)"
            }
       } elseif ($PSCmdlet.ParameterSetName -eq "Store") {
            $Store = $KnownHostStore
       }

       if ($PSBoundParameters.Keys -contains "HostName") {
            $k = $Store.GetKey($HostName)
            if ($k) {
                $k | Add-Member -Force -MemberType NoteProperty -Name "HostName" -Value $HostName -TypeName "SSH.Stores.KnownHostRecord" -PassThru
            }
       } else {
            $Store.GetAllKeys() 
       }
    }
    End
    {}
}


# .ExternalHelp Posh-SSH.psm1-Help.xml
 function New-SSHTrustedHost
 {
    [CmdletBinding(DefaultParameterSetName = "Local")]
     Param
     (
         # IP Address of FQDN of host to add to trusted list.
         [Parameter(Mandatory=$true,
                    ValueFromPipelineByPropertyName=$true,
                    Position=0)]
         $HostName,

         # SSH Server Fingerprint. (md5 of host public key)
         [Parameter(Mandatory=$true,
                    ValueFromPipelineByPropertyName=$true,
                    Position=1)]
         $FingerPrint,

         # This is the hostkey cipher name.
         [ValidateSet(
                    "ssh-ed25519",
                    "ecdsa-sha2-nistp256",
                    "ecdsa-sha2-nistp384",
                    "ecdsa-sha2-nistp521",
                    "rsa-sha2-512",
                    "rsa-sha2-256",
                    "ssh-rsa",
                    "ssh-dss"
         )]
         [Parameter(
                    ValueFromPipelineByPropertyName=$true,
                    Position=2)]
         [string]
         [Alias('KeyCipherName')]
         $HostKeyName = "",

         # Known Host Store
        [Parameter(Mandatory = $true,
        ParameterSetName = "Store")]
        [Alias('KnowHostStore')]
        [SSH.Stores.IStore]
        $KnownHostStore
     )

    Begin{
        $Default = [IO.Path]::Combine($Home,".poshssh", "hosts.json")
    }
     Process
     {
        if ($PSCmdlet.ParameterSetName -eq "Local") {
            $Store = Get-SSHJsonKnownHost
            if (-not (Test-Path -PathType Leaf $Default)) {
                Write-Warning -Message "No known host file found, $($Default)"
            }
        } elseif ($PSCmdlet.ParameterSetName -eq "Store") {
             $Store = $KnownHostStore
        }
 
        $Store.SetKey($HostName, $HostKeyName, $FingerPrint)
     }
     End {}
 }

# .ExternalHelp Posh-SSH.psm1-Help.xml
 function Remove-SSHTrustedHost
 {
    [CmdletBinding(DefaultParameterSetName = "Local")]
    Param(
        # IP Address of FQDN of host to add to trusted list.
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [string]
        $HostName,

        # Known Host Store
        [Parameter(Mandatory = $true,
        ParameterSetName = "Store")]
        [Alias('KnowHostStore')]
        [SSH.Stores.IStore]
        $KnownHostStore
     )

    Begin{
        $Default = [IO.Path]::Combine($Home,".poshssh", "hosts.json")
    }
     Process{
        if ($PSCmdlet.ParameterSetName -eq "Local") {
            $Store = Get-SSHJsonKnownHost
            if (-not (Test-Path -PathType Leaf $Default)) {
                Write-Warning -Message "No known host file found, $($Default)"
            }
        } elseif ($PSCmdlet.ParameterSetName -eq "Store") {
            if ($KnownHostStore -isnot [SSH.Stores.OpenSSHStore]) {
                $Store = $KnownHostStore
            } else {
                Write-Error -Message "SSH.Stores.OpenSSHStore are a Read Only store." -ErrorAction Stop 
            }
        }
 
        $Store.RemoveByHost($HostName)
     }
     End{}
 }

 <#
    .SYNOPSIS
       Get KnownHosts from registry (readonly)
    .DESCRIPTION
       Get KnownHosts from registry (readonly)
       It is windows-only compatibility cmdlet
#>

function Get-SSHRegistryKnownHost {
    class SSHRegistryKeyStore: SSH.Stores.MemoryStore {
          [void] OnGetKeys() {
              $p = Get-ItemProperty HKCU:\SOFTWARE\PoshSSH
              $HostKeys = $this.HostKeys
              $p | Get-Member -MemberType NoteProperty |
              Where-Object { $_.Name -notin 'PSPath', 'PSParentPath', 'PSChildName', 'PSDrive', 'PSProvider' } |
              ForEach-Object {
                 $name = $_.Name
                 $hostData = [SSH.Stores.KnownHostValue]@{ HostKeyName='ssh-rsa'; Fingerprint=$p.$name }
                 $HostKeys.AddOrUpdate($name, $hostData, { return $hostData } )
              }
          }
          [bool]SetKey([string]$HostName, [string]$KeyType, [string]$Fingerprint) {
             return $false
          }
          [bool]RemoveByHost([string] $HostName) {
              return $false
          }
          [bool]RemoveByFingerprint([string] $Fingerprint) {
              return $false
          }
    }

    New-Object SSHRegistryKeyStore
}

<#
    .SYNOPSIS
       Convert windows registry key storage to Json
    .DESCRIPTION
       Convert windows registry key storage to Json
       It is windows-only compatibility cmdlet
#>

function Convert-SSHRegistryToJSonKnownHost {
    $JsonStore = Get-SSHJsonKnownHost
    $p = Get-ItemProperty HKCU:\SOFTWARE\PoshSSH
    $p | Get-Member -MemberType NoteProperty |
    Where-Object { $_.Name -notin 'PSPath', 'PSParentPath', 'PSChildName', 'PSDrive', 'PSProvider' } |
    ForEach-Object {
        $name = $_.Name
        Write-Host "Save ssh-rsa key for $name"
        [void]$JsonStore.SetKey($name, 'ssh-rsa', $p.$name)
    }
}