using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; using System.Reflection; using System.IO; namespace SMFTP { /// /// Wrapper Managed Class around putty sftp. /// /// /// SFTPClient client = new SFTPClient(); /// if(client.Connect("sftp.somedomain.com","user","password","22")) /// bool bFetchedFile = client.CmdExec_Any("get somefile.txt"); /// /// /// public class SFTPClient : IDisposable { #region Private Statics /// /// Used in the all to LoadLibrary * Names here /// * if you change it here * also change DllImports (below) /// private const string PSFTP_FILENAME = "psftp.dll"; /// /// We only want to load the Psftp dll once per process /// private static bool s_PSFTPDLL_IsLoaded = false; /// /// Responsible for creating an active connection /// [DllImport("psftp.dll")] private static extern int dllmain(int argc, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr)] String[] argv); /// /// Responsible for running a command on an active connection /// [DllImport("psftp.dll")] private static extern int do_sftp2([MarshalAs(UnmanagedType.LPStr)] string cmdLine); /// /// Responsible for Putty Cleanup /// [DllImport("psftp.dll")] private static extern void dllmain_cleanup(); /// /// Loads the Putty Dll /// [DllImport("kernel32.dll")] private static extern IntPtr LoadLibrary(String dllname); #endregion #region Construction / Destruction /// /// Create a SFTClient Object /// public SFTPClient() { // Extract the Native Dll to the Temp Directory, // if it is not already there... if (!File.Exists(_pstfpTempFileNameNPath)) { using (BinaryReader br = new BinaryReader(Assembly.GetExecutingAssembly().GetManifestResourceStream(("SMFTP.Components." + PSFTP_FILENAME)))) using (BinaryWriter bw = new BinaryWriter(new FileStream(_pstfpTempFileNameNPath, FileMode.Create))) { byte[] buffer = new byte[64 * 1024]; int numread = br.Read(buffer, 0, buffer.Length); while (numread > 0) { bw.Write(buffer, 0, numread); numread = br.Read(buffer, 0, buffer.Length); } bw.Flush(); } } // Load the dll, if it isn't already if (!s_PSFTPDLL_IsLoaded) { IntPtr hDll = LoadLibrary(_pstfpTempFileNameNPath); s_PSFTPDLL_IsLoaded = (hDll != IntPtr.Zero); } if (!s_PSFTPDLL_IsLoaded) throw new Exception(("Failed to load " + _pstfpTempFileNameNPath + ". Something terrible is wrong")); } /// /// Finalizer /// ~SFTPClient() { Dispose(false); } #endregion #region Private Members private bool _IsConnected = false; private bool _disposed = false; private readonly string _pstfpTempFileNameNPath = Path.GetTempPath() + PSFTP_FILENAME; #endregion #region Main Putty Functions /// /// Start a Connection to the specified Host with the specified credentials /// /// url to sftp host /// user credential /// password credential /// port to connect to (default 22) /// true if connection occured successfully, false otherwise public bool Connect(string hostname, string username, string password, string port = "22") { if (String.IsNullOrEmpty(hostname) || String.IsNullOrEmpty(username) || String.IsNullOrEmpty(password) || String.IsNullOrEmpty(port)) throw new ArgumentException("Empty String not valid for any of the arguments"); Disconnect(); int nResult = dllmain(10, new String[] { Assembly.GetExecutingAssembly().Location, "-l", username, "-pw", password, "-P", port, "-batch", "-y", hostname }); if (nResult != 0) // Error occured connecting to host with specified prms return false; _IsConnected = true; return true; } /// /// End the active Connection /// public bool Disconnect() { if (_IsConnected) { dllmain_cleanup(); _IsConnected = false; } return !_IsConnected; } /// /// Performs the specified Command on the Active Connection /// /// a Command including optional parameters like "dir *.*" that can be understood by psftp /// true, if command run successfully, falser otherwise public bool CmdExec_Any(string Command) { // Perform the Action if (_IsConnected) { bool bPerformedAction = (do_sftp2(Command) == 1); return bPerformedAction; } return false; } #endregion #region Command Helpers (Assumes Active Connection) /// /// Set the Working Directory Locally /// /// path to a local directory /// true to create the local directory, if it doesn't exist, false otherwise /// true, if successful, false otherwise public bool CmdExec_LocalDirSet(string path, bool bCreateIfNotExists = false) { if (!_IsConnected) throw new Exception("Not Connected"); if(String.IsNullOrEmpty(path)) throw new ArgumentException("path can not be empty"); bool bSuccess = CmdExec_Any("lcd " + path); if (!bSuccess && bCreateIfNotExists) { Directory.CreateDirectory(path); if(Directory.Exists(path)) bSuccess = CmdExec_Any("lcd " + path); } return bSuccess; } /// /// Set the Working Directory on the Remote sftp connection /// /// path to a remote directory /// true, if successful, false otherwise public bool CmdExec_RemoteDirSet(string path) { if (!_IsConnected) throw new Exception("Not Connected"); if (String.IsNullOrEmpty(path)) throw new ArgumentException("path can not be empty"); // Always make paths absolute * safer * if (path[0] != '/' && path[0] != '\\') path = '/' + path; bool bSuccess = CmdExec_Any("cd " + path); return bSuccess; } /// /// Checks for existence of the Directory on the Server /// /// path to a remote directory /// true, if remote directory exists, false otherwise public bool CmdExec_RemoteDirExists(string path) { if (!_IsConnected) throw new Exception("Not Connected"); if (String.IsNullOrEmpty(path)) throw new ArgumentException("path can not be empty"); // Always make paths absolute * safer * if (path[0] != '/' && path[0] != '\\') path = '/' + path; bool bSuccess = CmdExec_Any("cd /"); // make sure we are at the root dir if (bSuccess) bSuccess = CmdExec_Any("cd " + path); if (bSuccess) bSuccess = CmdExec_Any("cd /"); // back to root dir return bSuccess; } /// /// Download a Directory at once including all it's files. if bRecursive is set, /// Download all child directories and files /// /// If bRecursive is true, recursively fetch files and directories /// path to a remote directory /// true, if successful, false otherwise public bool CmdExe_MGetDir(bool bRecursive, string path) { if (!_IsConnected) throw new Exception("Not Connected"); if (String.IsNullOrEmpty(path)) throw new ArgumentException("path can not be empty"); // Always make paths absolute * safer * if (path[0] != '/' && path[0] != '\\') path = '/' + path; string mgetCmd = bRecursive ? "mget -r " : "mget "; bool bSuccess = CmdExec_Any(mgetCmd + path); return bSuccess; } /// /// Download multiple files at once /// /// "*.c" "filename1.txt" "*.sln" /// /// /// space seperated list of multiple filenames or wildcards /// true, if successful, false otherwise public bool CmdExe_MGetFiles(string filenames_or_wildcards) { if (!_IsConnected) throw new Exception("Not Connected"); if (String.IsNullOrEmpty(filenames_or_wildcards)) throw new ArgumentException("filenames_or_wildcards can not be empty"); bool bSuccess = CmdExec_Any("mget " + filenames_or_wildcards); return bSuccess; } /// /// Upload a Directory at once including all it's files. if bRecursive is set, /// Upload all child directories and files /// /// If bRecursive is true, recursively put files and directories /// path to a local directory /// true, if successful, false otherwise public bool CmdExe_MPutDir(bool bRecursive, string path) { if (!_IsConnected) throw new Exception("Not Connected"); if (String.IsNullOrEmpty(path)) throw new ArgumentException("path can not be empty"); string mgetCmd = bRecursive ? "mput -r " : "mget "; bool bSuccess = CmdExec_Any(mgetCmd + path); return bSuccess; } /// /// Upload multiple files at once /// /// "*.c" "filename1.txt" "*.sln" /// /// /// space seperated list of multiple filenames or wildcards /// true, if successful, false otherwise public bool CmdExe_MPutFiles(string filenames_or_wildcards) { if (!_IsConnected) throw new Exception("Not Connected"); if (String.IsNullOrEmpty(filenames_or_wildcards)) throw new ArgumentException("filenames_or_wildcards can not be empty"); bool bSuccess = CmdExec_Any("mput " + filenames_or_wildcards); return bSuccess; } /// /// Delete multiple files at once from the server /// /// "*.c" "filename1.txt" "*.sln" /// /// /// space seperated list of multiple filenames or wildcards /// true, if successful, false otherwise public bool CmdExe_MDelFiles(string filenames_or_wildcards) { if (!_IsConnected) throw new Exception("Not Connected"); if (String.IsNullOrEmpty(filenames_or_wildcards)) throw new ArgumentException("filenames_or_wildcards can not be empty"); bool bSuccess = CmdExec_Any("del " + filenames_or_wildcards); return bSuccess; } /// /// Removes a Directory from the server. Directory must be empty in order /// to be removed. /// /// path to a remote directory /// true, if successful, false otherwise public bool CmdExe_MDelDir(string path) { if (!_IsConnected) throw new Exception("Not Connected"); if (String.IsNullOrEmpty(path)) throw new ArgumentException("path can not be empty"); // Always make paths absolute * safer * if (path[0] != '/' && path[0] != '\\') path = '/' + path; bool bSuccess = CmdExec_Any("" + path); return bSuccess; } #endregion #region IDisposable Members public void Dispose() { Dispose(true); // Use SupressFinalize in case a subclass // of this type implements a finalizer GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { // Free Managed Resources } // Free Unmanaged Resources Disconnect(); _disposed = true; } } #endregion } }