0 votes
by (450 points)
edited

Hello Guys,

Is it possible to unzip the archive (.zip) in remote server right after uploading it? I am using SFTP for the upload process.

Best Regards, Jepoy

Applies to: Rebex ZIP, Rebex SFTP

3 Answers

+1 vote
by (70.2k points)
edited
 
Best answer

Although this operation (unzip file after uploading) is not supported by the SFTP protocol (and Rebex SFTP), it is often possible to achieve it by utilizing the fact that SFTP runs over SSH. This makes it possible to use SSH's "remote execute" feature to run arbitrary commands at the server (if it was configured to allow that, of course).

To give it a try, use the following class:

public class SftpCommandRunner
{
    private readonly Sftp _sftp;
    private readonly StringBuilder _stderr;
    private long _exitCode;

    public long ExitCode
    {
        get { return _exitCode; }
    }

    public string Errors
    {
        get { return _stderr.ToString(); }
    }

    public SftpCommandRunner(Sftp sftp)
    {
        _sftp = sftp;
        _stderr = new StringBuilder();
    }

    public string RunCommand(string command)
    {
        _stderr.Length = 0;
        SshChannel channel = null;
        try
        {
            // start an exec session
            channel = _sftp.Session.OpenSession();
            channel.RequestExec(command);

            // get extended data (used for stderr)
            channel.ExtendedDataReceived += new EventHandler<SshExtendedDataReceivedEventArgs>(channel_ExtendedDataReceived);

            // receive all response
            StringBuilder response = new StringBuilder();
            byte[] buffer = new byte[4096];
            while (channel.State == SshChannelState.Connected)
            {
                if (!channel.Poll(_sftp.Timeout * 1000, SocketSelectMode.SelectRead))
                    break;

                int n = channel.Receive(buffer, 0, buffer.Length);
                response.Append(_sftp.Encoding.GetString(buffer, 0, n));
            }

            SshChannelExitStatus exitStatus = channel.ExitStatus;
            if (exitStatus != null)
                _exitCode = exitStatus.ExitCode;

            return response.ToString().TrimEnd();
        }
        finally
        {
            if (channel != null)
                channel.Close();
        }
    }

    private void channel_ExtendedDataReceived(object sender, SshExtendedDataReceivedEventArgs e)
    {
        // get extended data
        byte[] data = e.GetData();

        // convert it to text
        string response = _sftp.Encoding.GetString(data, 0, data.Length);

        // add it to stderr StringBuilder
        _stderr.Append(response);
    }
}

To unzip a file located at the server, establish an SFTP connection, upload the file and call SftpCommandRunner.RunCommand, supplying a command to it.

In the sample below, I used the unzip remote utility to extract the specified file. Of course you can use your favourite unzip utility.

Sftp client = new Sftp();
client.Connect(...);
client.Login(...);

client.ChangeDirectory("upload");

client.PutFile("C:/test.zip", "test.zip");

SftpCommandRunner runner = new SftpCommandRunner(client);

// note the RunCommand has working directory in initial directory
// previous call of client.ChangeDirectory("upload") has no effect to SftpCommandRunner
string result = runner.RunCommand("unzip -o upload/test.zip -d upload");

if (runner.ExitCode == 0)
{
    // process command output ...
    Console.WriteLine(result);
}
else
{
    // process command error output ...
    Console.WriteLine("Unzip failed ({0}):", runner.ExitCode);
    Console.WriteLine(runner.Errors);
}

If you prefer VB.NET, please let me know.

Does this work for you?

by (450 points)
edited

Thanks very much. This tool is amazing plus with outstanding support.

I'm good with C# i can easily convert it to vb.net for my current project.

0 votes
by (450 points)
edited

Hello Matyska,

What do you mean by exit code = 1 but the Error message is blank? Does it mean that the process is successful?

I didn't change a think regarding to the SFTPCommandRunner class but i made a change to the later code you've shared as follow:

   Try

        Dim runner As New SFTPCommandRunner(_AkamaiConnection)

        'note the RunCommand has working directory in initial directory
        'previous call of client.ChangeDirectory("upload") has no effect to SftpCommandRunner

        'string result = runner.RunCommand("unzip -o upload/test.zip -d upload");
        'Dim result As String = runner.RunCommand("quote ""site az2z""")
        Dim result As String = runner.RunCommand("site az2z")

        If runner.ErrorCode <> 0 Then

            ' process command error output ...                
            _HasError = True
            _ErrorSource = ErrorSource.SFTPCommand
            _ErrorMsg = "Error Code (" & runner.ErrorCode & "): " & runner.ErrorMessage

        End If

    Catch ex As Exception

        _HasError = True
        _ErrorSource = ErrorSource.SFTPCommand
        _ErrorMsg = ex.Message

    End Try

    Return _HasError

So when i execute this command: runner.RunCommand("site az2z")
the error code becomes: 1
the error message is: blank

Any idea?

by (144k points)
edited

I don't think it's possible to run 'quote "site az2z"' or 'size az2z' through SSH remote exec. SSH is not FTP and unless you have some very special server, its SSH part doesn't understand either 'quote' or 'site' commands. Try connecting to the server using an SSH client such as PuTTY to get into a shell session and test which commands are available.

0 votes
by (450 points)
edited

Just to be sure here's my sftpcommandrunner class converted to vb.net

Public Class SFTPCommandRunner

#Region "Class Variable"

    Private ReadOnly _sftp As Rebex.Net.Sftp
    Private ReadOnly _errorMessage As StringBuilder
    Private _errorCode As Long

#End Region

#Region "Properties"

    Public ReadOnly Property ErrorCode() As Long
        Get
            Return _errorCode
        End Get
    End Property

    Public ReadOnly Property ErrorMessage() As String
        Get
            Return _errorMessage.ToString()
        End Get
    End Property

#End Region

#Region "Functions"

    Public Sub New(ByVal sftp As Rebex.Net.Sftp)

        _sftp = sftp
        _errorMessage = New StringBuilder()

    End Sub

    Public Function RunCommand(ByVal command As String) As String

        Dim channel As Rebex.Net.SshChannel = Nothing
        _errorMessage.Length = 0

        Try

            ''# Start an exec session
            channel = _sftp.Session.OpenSession()
            channel.RequestExec(command)

            ''# Get extended data (used for stderr)
            AddHandler channel.ExtendedDataReceived, AddressOf channel_ExtendedDataReceived

            ''# Receive all response
            Dim response As New StringBuilder()
            Dim buffer As Byte() = New Byte(4095) {}

            While channel.State = Rebex.Net.SshChannelState.Connected

                If Not channel.Poll(_sftp.Timeout * 1000, Rebex.Net.SocketSelectMode.SelectRead) Then
                    Exit While
                End If

                Dim n As Integer = channel.Receive(buffer, 0, buffer.Length)
                response.Append(_sftp.Encoding.GetString(buffer, 0, n))

            End While

            ''# Get the Error code if any
            Dim exitStatus As Rebex.Net.SshChannelExitStatus = channel.ExitStatus
            If exitStatus IsNot Nothing Then
                _errorCode = exitStatus.ExitCode
            End If

            Return response.ToString().TrimEnd()

        Finally
            If channel IsNot Nothing Then
                channel.Close()
            End If
        End Try

    End Function

#End Region

#Region "Events"

    Private Sub channel_ExtendedDataReceived(ByVal sender As Object, ByVal e As Rebex.Net.SshExtendedDataReceivedEventArgs)

        ''# get extended data
        Dim data As Byte() = e.GetData()

        ''# convert it to text
        Dim response As String = _sftp.Encoding.GetString(data, 0, data.Length)

        ''# add it to stderr StringBuilder
        _errorMessage.Append(response)

    End Sub

#End Region

End Class

I also notice when i debug the code it never reach the event that we attached to the session. I would appreciate any correction and explanation as to what went wrong. thanks very much.

by (450 points)
edited

hmmm... but matyska i tried this approach but it didn't unzip the file. and it did not went inside the event that's included in the sftpcommandrunner class.

by (70.2k points)
edited

There are three standard streams: input, output, error. The Errors (ErrorMessage) property represents the standard error output.

There is no rule, a program have to print it's error messages to the error output. Some programs prints its error messages to the standard output (mixed together with regular output). Therefore the ErrorMessage property can be empty while the ErrorCode property is not zero.

I suggest you to write your if statement as follows:

If runner.ErrorCode <> 0 Then

    ''# process command error output ...                
    _HasError = True
    _ErrorSource = ErrorSource.SFTPCommand
    _ErrorMsg = "Error Code (" & runner.ErrorCode & "): "

    If String.IsNullOrEmpty(runner.ErrorMessage) Then
        _ErrorMsg = _ErrorMsg & result
    Else
        _ErrorMsg = _ErrorMsg & runner.ErrorMessage
    End If

End If

If the ErrorCode property is not zero some error occurred (process was not successful).

by (144k points)
edited

I would say the most likely explanation is that 'quote' and 'site' commands are simply not available in the servers SSH subsystem. Are you able to run anything else, such as `echo test message'?

by (450 points)
edited

no i can't either. so you are saying that in order for me to run ftp command is to use rebex ftp class? otherwise that is impossible?

looking forward to you answer :)

by (144k points)
edited

Yes, that seems to be the case with this server. FTP commands executed through 'site' command are usually FTP-only, and although many FTP servers make it possible to define custom FTP commands, these are usually not accessible through SSH remote exec even when both FTP and SFTP/SSH subsystems are provided by the same server.

Depending on the server software, there might be some way to make the same or equivalent commands available through SSH as well, but we would need some information about the actual FTP and SFTP/SSH software installed at the server to be able to point you in the right direction.

by (450 points)
edited

Thank you very much guys. This tool is really the answer. Really appreciate your help guys.

by (450 points)
edited

Thank you very much pokorny. :)

...