FtpsClient.cs

using System;
using System.IO;
using System.Management.Automation;
using System.Net;
using FluentFTP;

namespace PureStorage.AzureNative.Tools
{
    // Public static helper class designed to be called from PowerShell
    public static class FtpsClient
    {
        // Verifies FTPS connectivity by establishing a TLS-secured FTP session and issuing a NOOP.
        public static void AssertFtpsServerConnectible(string ftpsServer, int ftpsPort, PSCredential ftpsCredential)
        {
            if (string.IsNullOrWhiteSpace(ftpsServer)) throw new ArgumentException("FTPS server is required", nameof(ftpsServer));
            if (ftpsCredential == null) throw new ArgumentNullException(nameof(ftpsCredential));

            using (var client = CreateClient(ftpsServer, ftpsPort, ftpsCredential))
            {
                client.Connect();
                // NOOP is a lightweight connectivity check
                client.Noop();
            }
        }

        // Uploads a single local file to the FTPS server under /<remoteDir>/<filename>
        public static void SendFileToFtps(string ftpsServer, int ftpsPort, string localFilePath, PSCredential ftpsCredential, string remoteDir)
        {
            if (string.IsNullOrWhiteSpace(localFilePath)) throw new ArgumentException("Local file path is required", nameof(localFilePath));
            if (!File.Exists(localFilePath)) throw new FileNotFoundException("Local file not found", localFilePath);

            using (var client = CreateClient(ftpsServer, ftpsPort, ftpsCredential))
            {
                client.Connect();

                if (!client.DirectoryExists(remoteDir))
                {
                    client.CreateDirectory(remoteDir, true);
                }

                var remotePath = $"{remoteDir}/{Path.GetFileName(localFilePath)}";
                var status = client.UploadFile(localFilePath, remotePath, FtpRemoteExists.Overwrite, true, FtpVerify.Retry);
                if (status != FtpStatus.Success)
                {
                    throw new InvalidOperationException($"FTPS upload failed with status: {status}");
                }
            }
        }

        private static FtpClient CreateClient(string server, int port, PSCredential credential)
        {
            var netCred = credential.GetNetworkCredential();

            var client = new FtpClient(server, netCred.UserName, netCred.Password);
            // Use FTPS (TLS). Default to Implicit on 990, else Explicit.
            client.Config.EncryptionMode = port == 990 ? FtpEncryptionMode.Implicit : FtpEncryptionMode.Explicit;
            client.Config.DataConnectionType = FtpDataConnectionType.AutoPassive;
            client.Config.ValidateAnyCertificate = false; // set to true if you need to skip certificate validation
            client.Config.ReadTimeout = 1000 * 60 * 5; // 5 minutes
            client.Config.ConnectTimeout = 1000 * 30; // 30 seconds
            client.Config.SocketKeepAlive = true;
            return client;
        }
    }
}