More .NET components

File Transfer (SFTP, YMODEM)

Rebex Terminal Emulation supports a legacy file transfer protocol called YMODEM. It makes it possible to transfer files between the remote host (server) and the client using a terminal session. It is (and always has been) far from perfect - we recommend to only use it when there are no other alternatives. A typical scenario in which YMODEM is useful involves a connection over a serial cable or a modem, where more advanced file transfer protocols that run over TCP are not available.

However, in most cases, prefer one of the alternatives such as:

Sharing SSH session with SFTP #

SFTP is the preferred file transfer protocol for use with SSH - it stands for "SSH File Transfer Protocol". It's realiable and more efficiant and it can share a single connection with with SSH. See SSH session sharing for details.

Terminal-based file transfers basics #

Terminal-based file transfers using YMODEM are performed using the FileTransfers object, accessible as a property of ITerminal interface (implemented by TerminalControl and VirtualTerminal classes). It's also available on Scripting object.

Getting FileTransfers from Scripting object

CSharp

// get FileTransfers object for a Scripting object
var scripting = ssh.StartScripting();
FileTransfers transfers = scripting.Terminal.Transfers;

VisualBasic

' get FileTransfers object from scripting
Dim scripting = ssh.StartScripting()
Dim transfers As FileTransfers = scripting.Terminal.Transfers

Getting FileTransfers from VirtualTerminal object

CSharp

// get FileTransfers object for a VirtualTerminal object
VirtualTerminal terminal = ssh.StartVirtualTerminal();
FileTransfers transfers = terminal.Transfers;

VisualBasic

' get FileTransfers object from VirtualTerminal
Dim terminal As VirtualTerminal = ssh.StartVirtualTerminal()
Dim transfers As FileTransfers = terminal.Transfers

Getting FileTransfers from TerminalControl object

By default, TerminalControl uses automatic data processing mode, which is not compatible with terminal-based file transfers. It is necessary to disable automatic data processing before using the TerminalControl object.

CSharp

// disable automatic processing on the TerminalControl object
// (added to a Form using Visual Studio designer)
this.Terminal.SetDataProcessingMode(DataProcessingMode.None);

// get FileTransfers object fora TerminalControl object
FileTransfers transfers = this.Terminal.Transfers;

// use the FileTransfers object
// ...

// enable automatic processing
this.Terminal.SetDataProcessingMode(DataProcessingMode.Automatic);

VisualBasic

' disable automatic processing of the TerminalControl object
' (that was added to the Form using Visual Studio designer)
Me.Terminal.SetDataProcessingMode(DataProcessingMode.None)

' get FileTransfers object from TerminalControl
Dim transfers As FileTransfers = Me.Terminal.Transfers

' use the FileTransfers object
' ...

' enable automatic processing
Me.Terminal.SetDataProcessingMode(DataProcessingMode.Automatic)

Receiving files #

To receive a file, an YMODEM sender process has to be invoked at the remote host. On Linux, this functionality is provided by the lrzsz package's sb command.

Receiving files involves three steps:

  1. Issue command at the server to begin YMODEM sender process.
  2. Process error or initial messages such as "No such command" or "Ready to send".
  3. Start YMODEM receiver by calling FileTransfers.StartReceiver() method.

Example of complete file receiving routine:

CSharp

// start scripting
var scripting = ssh.StartScripting();

// detect shell prompt
scripting.DetectPrompt();

// start YMODEM sender process
scripting.SendCommand("sb FILE_NAME");

// alternatively, use YMODEM-1k by adding "-k" option
//scripting.SendCommand("sb -k FILE_NAME");

// receive all messages sent by the sender process
// (in our scenario there is no message on success -> wait 3sec for error messages)
string response = scripting.ReadUntil(ScriptEvent.AnyText, ScriptEvent.Delay(3000));
if (!string.IsNullOrWhiteSpace(response))
{
    // we received a text -> read whole response to prompt
    response += scripting.ReadUntilPrompt();
    throw new Exception("An unexpected response received: " + response);
}

// alternatively, use YMODEM-g
//scripting.Terminal.Transfers.EnableStreaming = true;

// start YMODEM receiver
using (var receiver = scripting.Terminal.Transfers.StartReceiver())
{
    // receive all files
    while (receiver.ReadNext())
    {
        // print some info about the current file
        Console.WriteLine(receiver.FileName);
        Console.WriteLine(receiver.Length);
        Console.WriteLine(receiver.LastWriteTime);

        // save the file
        string file = Path.Combine(@"C:\data", receiver.FileName);
        receiver.Receive(file);
    }
}

// some YMODEM implementations are unreliable - send CTRL+C to request new prompt
scripting.Send(ConsoleKey.C, ConsoleModifiers.Control);

// wait for prompt before sending any additional commands
scripting.WaitFor(ScriptEvent.Prompt);

VisualBasic

' start scripting
Dim scripting = ssh.StartScripting()

' detect shell prompt
scripting.DetectPrompt()

' start YMODEM sender process
scripting.SendCommand("sb FILE_NAME")

' alternatively, use YMODEM-1k by adding "-k" option
'scripting.SendCommand("sb -k FILE_NAME");

' receive all messages sent by the sender process
' (in our scenario there is no message on success -> wait 3sec for error messages)
Dim response As String = scripting.ReadUntil(ScriptEvent.AnyText, ScriptEvent.Delay(3000))
If Not String.IsNullOrWhiteSpace(response) Then
    ' we received a text -> read whole response to prompt
    response &= scripting.ReadUntilPrompt()
    Throw New Exception("An unexpected response received: " & response)
End If

' alternatively, use YMODEM-g
'scripting.Terminal.Transfers.EnableStreaming = true;

' start YMODEM receiver
Using receiver = scripting.Terminal.Transfers.StartReceiver()
    ' receive all files
    While receiver.ReadNext()
        ' print some info about the current file
        Console.WriteLine(receiver.FileName)
        Console.WriteLine(receiver.Length)
        Console.WriteLine(receiver.LastWriteTime)

        ' save the file
        Dim file As String = Path.Combine("C:\data", receiver.FileName)
        receiver.Receive(file)
    End While
End Using

' some YMODEM implementations are unreliable - send CTRL+C to request New prompt
scripting.Send(ConsoleKey.C, ConsoleModifiers.Control)

' wait for prompt before sending any additional commands
scripting.WaitFor(ScriptEvent.Prompt)

In this sample, no initial message is expected. The sample code below shows how to handle a non-empty initial message.

Sending files #

To send a file, YMODEM receiver process has to be invoked at the remote host (server). For UNIX it is available using e.g. lrzsz package, command rb.

Sending files is done at three steps:

  1. Issue command at the server to begin YMODEM receiver process.
  2. Process error or initial message, e.g. "No such command." or "Waiting to receive." message.
  3. Start YMODEM receiver by calling FileTransfers.StartSender() method.

Example of complete file sending routine

CSharp

// start scripting
var scripting = ssh.StartScripting();

// wait for prompt
scripting.DetectPrompt();

// start YMODEM receiver process
scripting.SendCommand("rb");

// receive all messages sent by the receiver process
// (in our scenario, the message is "waiting to receive." -> wait at most 5sec)
string expected = "waiting to receive.";
var match = scripting.WaitFor(expected, ScriptEvent.Delay(5000));
if (!match.IsEventMatched(expected))
    throw new Exception("An unexpected response received: " + scripting.ReceivedData);

// alternatively, use YMODEM-1k
//scripting.Terminal.Transfers.BlockSize = FileTransferBlockSize.OneKilobyte;

// start YMODEM sender
using (var sender = scripting.Terminal.Transfers.StartSender())
{
    // send some files
    foreach (var file in Directory.GetFiles(@"C:\data"))
    {
        sender.Send(file);
    }

    // inform the receiver process that we are done
    sender.Finish();
}

// some YMODEM implementations are unreliable - send CTRL+C to request new prompt
scripting.Send(ConsoleKey.C, ConsoleModifiers.Control);

// wait for prompt before sending any additional commands
scripting.WaitFor(ScriptEvent.Prompt);

VisualBasic

' start scripting
Dim scripting = ssh.StartScripting()

' wait for prompt
scripting.DetectPrompt()

' start YMODEM receiver process
scripting.SendCommand("rb")

' receive all messages sent by the receiver process
' (in our scenario, the message is "waiting to receive." -> wait at most 5sec)
Dim expected As String = "waiting to receive."
Dim match = scripting.WaitFor(expected, ScriptEvent.Delay(5000))
If Not match.IsEventMatched(expected) Then
    Throw New Exception("An unexpected response received: " + scripting.ReceivedData)
End If

' alternatively, use YMODEM-1k
'scripting.Terminal.Transfers.BlockSize = FileTransferBlockSize.OneKilobyte;

' start YMODEM sender
Using sender = scripting.Terminal.Transfers.StartSender()
    ' send some files
    For Each file In Directory.GetFiles("C:\data")
        sender.Send(file)
    Next

    ' inform the receiver process that we are done
    sender.Finish()
End Using

' some YMODEM implementations are unreliable - send CTRL+C to request New prompt
scripting.Send(ConsoleKey.C, ConsoleModifiers.Control)

' wait for prompt before sending any additional commands
scripting.WaitFor(ScriptEvent.Prompt)

A non-empty initial message is expected in this example. The sample code above shows how to handle an empty initial message.

TransferProgressChanged event #

Register a FileTransfers.TransferProgressChanged event handler to get informed about transfer progress:

CSharp

terminal.Transfers.TransferProgressChanged += (s, e) =>
{
    switch (e.State)
    {
        case FileTransferProgressState.FileTransferring:
            Console.WriteLine("Starting transfer of file '{0}'.", e.FileName);
            break;
        case FileTransferProgressState.DataBlockTransferred:
            Console.WriteLine(" * data transferred: {1:F}% {2}B of '{0}'.",
                e.FileName, e.ProgressPercentage, e.BytesTransferred);
            break;
        case FileTransferProgressState.FileTransferred:
            Console.WriteLine("File transferred '{0}'.", e.FileName);
            break;
        case FileTransferProgressState.TransferCompleted:
            Console.WriteLine("Transfer completed.");
            break;
    }
};

VisualBasic

AddHandler terminal.Transfers.TransferProgressChanged,
    Sub(s, e)
        Select Case e.State
            Case FileTransferProgressState.FileTransferring
                Console.WriteLine("Starting transfer of file '{0}'.", e.FileName)
                Exit Select
            Case FileTransferProgressState.DataBlockTransferred
                Console.WriteLine(" * data transferred: {1:F}% {2}B of '{0}'.", e.FileName, e.ProgressPercentage, e.BytesTransferred)
                Exit Select
            Case FileTransferProgressState.FileTransferred
                Console.WriteLine("File transferred '{0}'.", e.FileName)
                Exit Select
            Case FileTransferProgressState.TransferCompleted
                Console.WriteLine("Transfer completed.")
                Exit Select
        End Select
    End Sub