Streaming from SFTP using WinSCP Session.GetFile fails – ((WinSCP.PipeStream)stream).Length' threw an exception of type 'System.NotSupportedException'

Viewed 187

My app requires copying file using SFTP from a location directly to Azure storage. Our app is using C# with .NET 4.6 and our WinSCP version is 5.21.1.

My old code works using Session.GetFileToDirectory() method, but the problem is it need to store the file on temp folder inside our hosting.

using (Session session = new Session())
{
    session.Open(sessionOptions);

    TransferOptions transferOptions = new TransferOptions();
    transferOptions.TransferMode = TransferMode.Binary;

    var transfer = session.GetFileToDirectory(FilePath, fullPath);
    using (Stream stream = File.OpenRead(transfer.Destination))
    {  
        UploadToAzure(stream, Filename, Foldername); 
    }
}

As we planned to entirely use Azure storage, I change my code like this

using (Session session = new Session())
{
    session.Open(sessionOptions);
    TransferOptions transferOptions = new TransferOptions();
    transferOptions.TransferMode = TransferMode.Binary;

    using (Stream stream = session.GetFile(FilePath, transferOptions))
    {
        UploadToAzure(stream, Filename, Foldername);   
    }
}

Here my library that uploads the file using Stream to Azure. This code is working fine using my old code that still save to temp folder before send to Azure.

public static string UploadToAzure(Stream attachment, string Filename, string Foldername)
{
    System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;

    var connectionString = $"{ConfigurationManager.AppSettings["AzureFileShareConnectionString"]}";
    string shareName = $"{ConfigurationManager.AppSettings["AzureFileShareFolderName"]}";
    string dirName = $"files\\{Foldername}";
    string fileName = Filename;

    try
    {  
        ShareClient share = new ShareClient(connectionString, shareName);
        share.CreateIfNotExists();
                 
        ShareDirectoryClient directory = share.GetDirectoryClient(dirName);
        directory.CreateIfNotExists();

        // Get a reference to a file and upload it
        ShareFileClient file = directory.GetFileClient(fileName);

        file.Create(attachment.Length);
        file.UploadRange(
            new HttpRange(0, attachment.Length), attachment);

    }
    catch (Exception e)
    {
        return $"Uploaded {Filename} failed : {e.ToString()}";
    }

    return $"{Filename} Uploaded";
}

But currently my new code not working with error message

'((WinSCP.PipeStream)stream).Length' threw an exception of type 'System.NotSupportedException'.

This is the object description on creating stream using Session.GetFile method enter image description here

This is 'exception stacktrace' on sending the empty-stream to Azure enter image description here

1 Answers

The Stream returned by WinSCP Session.GetFile does not implement the Stream.Length property, because WinSCP cannot guarantee that the size of the file is fixed. The remote file might be changing while you are downloading the file. Not to mention ASCII transfer mode, when the file is converted while being transferred, with unpredictable impact on the final size.

You use the size (Stream.Length) in two places:

  • When creating the file:

    file.Create(attachment.Length);
    

    The parameter of ShareFileClient.Create is maxSize. So it does not look like it's a real size. You can possibly just put an arbitrary large number here.

    Or if you prefer (and know that the file is not changing), read the current size of the remote file using Session.GetFileInfo and RemoteFileInfo.Length:

    file.Create(session.GetFileInfo(FilePath).Length);
    
  • When uploading the contents:

    file.UploadRange(new HttpRange(0, attachment.Length), attachment);
    

    The above can be replaced with simple ShareFileClient.Upload:

    file.Upload(attachment);
    
Related