0

To start with, my goal is to use FTP to retrieve a file and put it on multiple servers. This is intended as a backup procedure where we're taking a file and putting it onto two different backup servers then removing it from the FTP server.

In case this is relevant, the program runs as a service on a server.

I have tried two different methods and gotten 3 different errors so I will detail those. I am open to trying different libraries, whole different methods, and that's why I'm focusing on the overall goal instead of the specific code and errors.

So the first method worked successfully when I tested it locally on my dev laptop; I was able to do everything I wanted. It's worth noting that I debugged as the same domain account that the service runs on in the environments. When I deployed to test I received the error "The underlying connection was closed: The server committed a protocol violation."

I have (of course) trimmed this code down to what I believe are the relevant parts.

This gets called for each path that's being delivered to. They read similar to:
first case: \\fqdn\directory
second case: \\192.168.123.123\directory$

private bool CopyFTPToPath(string file, string path)
{
    try
    {
        string filePath = "ftp://" + Settings["Host"].Value + "/" + Settings["Path"].Value + "/" + file;
        FtpWebRequest request = (FtpWebRequest)WebRequest.Create(filePath);
        request.Method = WebRequestMethods.Ftp.DownloadFile;
        request.Credentials = new NetworkCredential("user", "pass");
        using (FtpWebResponse response = (FtpWebResponse)request.GetResponse())
        {
            using (Stream responseStream = response.GetResponseStream())
            {
                using (StreamReader reader = new StreamReader(responseStream))
                {
                    using (StreamWriter writer = new StreamWriter(path + "/" + file, false))
                    {
                        writer.Write(reader.ReadToEnd());
                    }
                }
            }
        }
        return File.Exists(path + "/" + file);
    catch(Exception ex)
    {
        Utility.LogError(ErrorType.Alert, ex);
    }
}

Again this is successful when I run it locally, it fails when run on the server with the same credentials, with the above protocol violation error. The error occurs when it attempts to create the StreamWriter.

So I tried using a library to handle the connection and grabbed the WinSCP library because I've seen it recommended on here frequently. The code I wrote for that is this:

private bool CopyFTPToPath(string file, string path)
{
    try
    {
        string filePath = "ftp://" + Settings["Host"].Value + "/" + Settings["Path"].Value + "/" + file;
        SessionOptions sessionOptions = new SessionOptions() {
            Protocol = Protocol.Ftp,
            HostName = Settings.Settings["Host"].Value,
            UserName = "user",
            Password = "pass"
        };
        using(Session session = new Session())
        {
            session.Open(sessionOptions);
            TransferOptions transferOptions = new TransferOptions();
            transferOptions.TransferMode = TransferMode.Binary;
            TransferEventArgs result = session.GetFileToDirectory(filePath, path,false,transferOptions);
        }
        return File.Exists(path + "/" + file);
    catch(Exception ex)
    {
        Utility.LogError(ErrorType.Alert, ex);
    }
}

Now this block of code fails at the session.GetFileToDirectory call, throwing a System.IO.DirectoryNotFoundException on the path. In this case I'm wondering if WinSCP is unable to handle a network path as the local directory path.

Here is a sanitized stacktrace from the error, for what that's worth. If the local directory parameter is actually local it seems to work, so I think that's what's going on there.

{System.IO.DirectoryNotFoundException: \\192.***.***.***\Path\Path
   at WinSCP.Session.DoGetFilesToDirectory(String remoteDirectory, String localDirectory, String filemask, Boolean remove, TransferOptions options, String additionalParams)
   at WinSCP.Session.GetEntryToDirectory(String remoteFilePath, String localDirectory, Boolean remove, TransferOptions options, String additionalParams)
   at WinSCP.Session.GetFileToDirectory(String remoteFilePath, String localDirectory, Boolean remove, TransferOptions options)
   at Program.RoutineTasks.Backup.CopyFTPToPath(String file, String path) in C:\Projects\Program\RoutineTasks\Backup.cs:line 114}

FTP is my only option to access this file. The file is 130gb, I don't have disk space on the server that runs this to copy it local to hand out. Both of those are outside of my control.

EDIT: I have found a solution that will definitely work if I can figure out how to manage the streams better, because the files are huge so I need to break them up to prevent running out of memory.

That code is this, in place of the using Session block above

using(Session session = new Session())
{
    session.Open(sessionOptions);
    TransferOptions transferOptions = new TransferOptions();
    transferOptions.TransferMode = TransferMode.Binary;
    using (StreamReader reader = new StreamReader(session.GetFile(filePath, transferOptions)))
    {
        using (StreamWriter writer = new StreamWriter(path + "/" + file, false))
        {
            while (!reader.EndOfStream)
            {
                writer.Write(reader.Read());
            }
        }
    }
}
Jingo
  • 21
  • 3
  • So does the `\\192.***.***.***\Path\Path` exist? Internally the `Session.GetFileToDirectory` is [doing `Directory.Exists(localDirectory)` test](https://github.com/winscp/winscp/blob/5.21.1/dotnet/Session.cs#L961). As it fails, it does not look like the folder exists. I'm sure the .NET `Directory.Exists` can work with UNC paths. + I do not understand what you mean by *"If the local directory parameter is actually local it seems to work, so I think that's what's going on there."* – Martin Prikryl Aug 31 '22 at 20:59
  • Yes it does exist. In the first instance I was able to actually move the files from FTP to the server, I just can't identify what's causing the protocol violation. – Jingo Aug 31 '22 at 21:10
  • Well, did you try calling `Directory.Exists(path)` just before `session.GetFileToDirectory`? What would it return? Because that's exactly that the `Session.GetFileToDirectory` does and it apparently return `false` there. – Martin Prikryl Sep 01 '22 at 09:48
  • Regarding your stream question for `FtpWebRequest`, see [my answer to FtpWebRequest Download File](https://stackoverflow.com/q/2781654/850848#44794524) => Use `WebClient.DownloadFile` or if you want to stick with `FtpWebRequest`, use `Stream.CopyTo`. – Martin Prikryl Sep 01 '22 at 09:48
  • Alright so I called Directory.Exists and that came back false on the server, true local. This was enough for me to get the sys admin to go double check that the service accounts was configured properly. Apparently there's some extra configuration he had to do for it as a service account, I don't understand or know what it was unfortunately. But that's resolved, I am past that hurdle. And your stream.CopyTo just saved me a lot of headache trying to read through it in chunks, thank you! Thank you so much! – Jingo Sep 01 '22 at 18:14
  • As such, you didn't have a [programming question](https://stackoverflow.com/help/on-topic) in the first place. – Martin Prikryl Sep 02 '22 at 06:16

1 Answers1

0

In the end this wound up being related to permissions on the account. When I presented to the sysadmin that the service account's access to the path was behaving inconsistently between my laptop and the test environment here is what he said: "you have to configure all non windows shares through the mmc snapin"

The paths I am delivering to are non-windows. He made a change there and the SA was then able to access the paths from the test server. This was not something that could have been solved simply by code.

Jingo
  • 21
  • 3