string host = #"ftphost";
string username = "user";
string password = "********";
string localFileName = System.IO.Path.GetFileName(#"localfilename");
string remoteDirectory = "/export/";
using (var sftp = new SftpClient(host, username, password))
{
sftp.Connect();
var files = sftp.ListDirectory(remoteDirectory);
foreach (var file in files)
{
if (!file.Name.StartsWith("."))
{
string remoteFileName = file.Name;
if (file.LastWriteTime.Date == DateTime.Today)
Console.WriteLine(file.FullName);
File.OpenWrite(localFileName);
string sDir = #"localpath";
Stream file1 = File.OpenRead(remoteDirectory + file.Name);
sftp.DownloadFile(remoteDirectory, file1);
}
}
}
I am using SSH.NET (Renci.SshNet) library to work with an SFTP server. What I need to do is grab files from a specific folder on the SFTP server based on today's date. Then copy those files from the SFTP server to a local drive a server of mine.
Above is the code I have but it is not working. Sometimes it says file does not exist but sometimes the files I will be downloading will not be on my local servers but I need to download whatever files were uploaded to the remote folder for that day.
Can someone take a look and see what is wrong? I believe it has something to do with the stream portion. I have worked with FTP much besides uploading files which I took some previous code I had and thought I could reverse the process but that isn't working. The code I used is based off of this example.
A simple working code to download a file with SSH.NET library is:
using (Stream fileStream = File.Create(#"C:\target\local\path\file.zip"))
{
sftp.DownloadFile("/source/remote/path/file.zip", fileStream);
}
See also Downloading a directory using SSH.NET SFTP in C#.
To explain, why your code does not work:
The second parameter of SftpClient.DownloadFile is a stream to write a downloaded contents to.
You are passing in a read stream instead of a write stream. And moreover the path you are opening read stream with is a remote path, what cannot work with File class operating on local files only.
Just discard the File.OpenRead line and use a result of previous File.OpenWrite call instead (that you are not using at all now):
Stream file1 = File.OpenWrite(localFileName);
sftp.DownloadFile(file.FullName, file1);
Or even better, use File.Create to discard any previous contents that the local file may have.
I'm not sure if your localFileName is supposed to hold full path, or just file name. So you may need to add a path too, if necessary (combine localFileName with sDir?)
While the example works, its not the correct way to handle the streams...
You need to ensure the closing of the files/streams with the using clause..
Also, add try/catch to handle IO errors...
public void DownloadAll()
{
string host = #"sftp.domain.com";
string username = "myusername";
string password = "mypassword";
string remoteDirectory = "/RemotePath/";
string localDirectory = #"C:\LocalDriveFolder\Downloaded\";
using (var sftp = new SftpClient(host, username, password))
{
sftp.Connect();
var files = sftp.ListDirectory(remoteDirectory);
foreach (var file in files)
{
string remoteFileName = file.Name;
if ((!file.Name.StartsWith(".")) && ((file.LastWriteTime.Date == DateTime.Today))
using (Stream file1 = File.Create(localDirectory + remoteFileName))
{
sftp.DownloadFile(remoteDirectory + remoteFileName, file1);
}
}
}
}
My version of #Merak Marey's Code. I am checking if files exist already and different download directories for .txt and other files
static void DownloadAll()
{
string host = "xxx.xxx.xxx.xxx";
string username = "###";
string password = "123";string remoteDirectory = "/IN/";
string finalDir = "";
string localDirectory = #"C:\filesDN\";
string localDirectoryZip = #"C:\filesDN\ZIP\";
using (var sftp = new SftpClient(host, username, password))
{
Console.WriteLine("Connecting to " + host + " as " + username);
sftp.Connect();
Console.WriteLine("Connected!");
var files = sftp.ListDirectory(remoteDirectory);
foreach (var file in files)
{
string remoteFileName = file.Name;
if ((!file.Name.StartsWith(".")) && ((file.LastWriteTime.Date == DateTime.Today)))
{
if (!file.Name.Contains(".TXT"))
{
finalDir = localDirectoryZip;
}
else
{
finalDir = localDirectory;
}
if (File.Exists(finalDir + file.Name))
{
Console.WriteLine("File " + file.Name + " Exists");
}else{
Console.WriteLine("Downloading file: " + file.Name);
using (Stream file1 = File.OpenWrite(finalDir + remoteFileName))
{
sftp.DownloadFile(remoteDirectory + remoteFileName, file1);
}
}
}
}
Console.ReadLine();
}
Massimiliano's solution has one problem which will lead to files not being completely downloaded. The FileStream must be closed. This is especially a problem for encrypted files. They won't completely decrypt intermittently without closing the stream.
var files = sftp.ListDirectory(remoteVendorDirectory).Where(f => !f.IsDirectory);
foreach (var file in files)
{
var filename = $"{LocalDirectory}/{file.Name}";
if (!File.Exists(filename))
{
Console.WriteLine("Downloading " + file.FullName);
using (var localFile = File.OpenWrite(filename))
sftp.DownloadFile(file.FullName, localFile);
}
}
This solves the problem on my end.
var files = sftp.ListDirectory(remoteVendorDirectory).Where(f => !f.IsDirectory);
foreach (var file in files)
{
var filename = $"{LocalDirectory}/{file.Name}";
if (!File.Exists(filename))
{
Console.WriteLine("Downloading " + file.FullName);
using (var localFile = File.OpenWrite(filename))
sftp.DownloadFile(file.FullName, localFile);
}
}
Without you providing any specific error message, it's hard to give specific suggestions.
However, I was using the same example and was getting a permissions exception on File.OpenWrite - using the localFileName variable, because using Path.GetFile was pointing to a location that obviously would not have permissions for opening a file > C:\ProgramFiles\IIS(Express)\filename.doc
I found that using System.IO.Path.GetFileName is not correct, use System.IO.Path.GetFullPath instead, point to your file starting with "C:\..."
Also open your solution in FileExplorer and grant permissions to asp.net for the file or any folders holding the file. I was able to download my file at that point.
Related
I have developed this C# System that authentically encrypts files using AES-GCM algorithm. However, I don't want the encrypted file to be in same folder as original folder. How can I change it? Below is the sniplet of my code and where exactly I need help.
foreach (string inputFilePath in inputFilePaths)
{
// Proceed if file exists
if (File.Exists(inputFilePath))
{
try
{
// Encrypt
byte[] key = PasswordAsKey();
string[] encryptedFileContents = AesGcmFileEncryption.Encrypt(inputFilePath, key);
// Here is where I need clarification
string outputFilePath = inputFilePath;
outputFilePath += ".AEncrypt";
if (File.Exists(outputFilePath))
{
skippedBecauseFileExists = true;
}
else
{
File.WriteAllLines(outputFilePath, encryptedFileContents);
counter++;
// Status
label10.Text = "Copied and encrypted \"" + Path.GetFileName(inputFilePath) + "\"";
}
}
catch (Exception ex)
You can create subdirectory and write the encrypted files there.
string inputDir = Path.GetDirectoryName(inputFilePath);
string outputDir = Path.Combine(inputDir, "EncryptedFiles");
Directory.CreateDirectory(outputDir);
string outputFileName = Path.GetFileName(inputFilePath) + ".AEncrypt";
string outputFilePath = Path.Combine(outputDir, outputFileName);
Here I extract directory name from inputFilePath and append "EncryptedFiles" to it. Then I create your new file name and append it to the generated directory.
Directory.CreateDirectory() will create directory if it doesn't exist.
Scenario: upload file than try zip it using DotNetZip with password protection, password is generated with Membership.GeneratePassword() method. Everything is working fine except that sometimes user is not able to unzip files with generated password. Wired thing is that this happens only sometimes let's say 1 out of 15 times.
Generate password:
public static String FilePassword()
{
while (_filePassword.Length < 12)
{
_filePassword += string.Concat(Membership.GeneratePassword(1, 0).Where(char.IsLetterOrDigit));
}
return _filePassword;
}
Save file:
if (FileUploadControl.HasFile)
{
fileName = Path.GetFileName(FileUploadControl.FileName);
FileUploadControl.SaveAs(FileSavePath + fileName);
// Archive uploaded file to zip.
using (ZipFile zip = new ZipFile())
{
// File to be archived.
var file = FileUploadControl.PostedFile;
// Enable encryption of file
zip.Encryption = EncryptionAlgorithm.PkzipWeak;
// Set password.
zip.Password = Settings.FilePassword();
// Set temporary folder path for archive process.
zip.TempFileFolder = tempPath;
// Add file to archive with its path.
zip.AddFile(FileSavePath + file.FileName, "");
File objFile = new File(file.FileName, FileSavePath);
// Save zip file with the name as file ID.
zip.Save(FileSavePath + file.FileName);
}
}
I logged password while creating in method and also while protecting zip file with password, they are always matching, I cannot see what's wrong, why sometimes while unzipping file it shows wrong password.
Why do you use the static global variable _filePassword in FilePassword() instead of one in scope?
This way it could be modified from outside, or possible even still contain the last used value. Also it isn't thread safe without lock.
Settle with a local variable and it should be fine.
public static String FilePassword()
{
string retString = string.Empty;
while (retString.Length < 12)
{
retString += string.Concat(Membership.GeneratePassword(1, 0).Where(char.IsLetterOrDigit));
}
return retString;
}
You can log a return Value too.
Sample for understanding
if (FileUploadControl.HasFile)
{
fileName = Path.GetFileName(FileUploadControl.FileName);
FileUploadControl.SaveAs(FileSavePath + fileName);
string filePassword = Settings.FilePassword(); // Contains the Password
using (ZipFile zip = new ZipFile())
{
var file = FileUploadControl.PostedFile;
zip.Encryption = EncryptionAlgorithm.PkzipWeak;
zip.Password = filePassword; // <-- Set password for ZIP
zip.TempFileFolder = tempPath;
zip.AddFile(FileSavePath + file.FileName, "");
File objFile = new File(file.FileName, FileSavePath);
zip.Save(FileSavePath + file.FileName);
}
// Log this value!
Log(filePassword);
}
I am able to zip files from a specific folder using ZipFile.CreateFromDirectory in the following test code (I only used this code to test how zipping works):
// Where the files are located
string strStartPath = txtTargetFolder.Text;
// Where the zip file will be placed
string strZipPath = #"C:\Users\smelmo\Desktop\testFinish\" + strFileNameRoot + "_" + txtDateRange1.Text.Replace(#"/", "_") + "_" + txtDateRange2.Text.Replace(#"/", "_") + ".zip";
ZipFile.CreateFromDirectory(strStartPath, strZipPath);
However, this zips together ALL of the contents in the folder. I am trying to zip together specific items in the folder using ZipArchive in the following code:
// Where the files are located
string strStartPath = txtTargetFolder.Text;
// Where the zip file will be placed
string strZipPath = #"C:\Users\smelmo\Desktop\testFinish\" + strFileNameRoot + "_" + txtDateRange1.Text.Replace(#"/", "_") + "_" + txtDateRange2.Text.Replace(#"/", "_") + ".zip";
using (ZipArchive archive = ZipFile.OpenRead(strStartPath))
{
foreach (ZipArchiveEntry entry in archive.Entries)
{
if (!(entry.FullName.EndsWith(".TIF", StringComparison.OrdinalIgnoreCase)))
{
entry.ExtractToFile(Path.Combine(strZipPath, entry.FullName));
}
}
}
It is giving the error at ZipFile.OpenRead(strStartPath). Why am I able to access the exact folder in the first block of code but not the second? Or is there an easier way to search through a folder and only zip specific items?
You are utilizing the Zip libraries wrong
Effectively you are trying to open a directory as if it were a zip file, then loop over the contents of that directory (which again is actually a zip file) and then attempting to extract each member into a different zip file
Here is a working example of what you have described you are trying to do:
string strStartPath = #"PATH TO FILES TO PUT IN ZIP FILE";
string strZipPath = #"PATH TO ZIP FILE";
if (File.Exists(strZipPath))
File.Delete(strZipPath);
using (ZipArchive archive = ZipFile.Open(strZipPath, ZipArchiveMode.Create))
{
foreach (FileInfo file in new DirectoryInfo(strStartPath).GetFiles())
{
if (!(file.FullName.EndsWith(".TIF", StringComparison.OrdinalIgnoreCase)))
{
archive.CreateEntryFromFile(Path.Combine(file.Directory.ToString(), file.Name), file.Name);
}
}
}
This will take all the root level contents of a folder and put it in the zip file. You will need to implement your own way of getting subfolders and their contents recursively, but that is beyond the scope of this question.
EDIT: Here is a working example with proper folder recursion to select all files even in subdirectories
public void ZipFolder()
{
string strStartPath = #"PATH TO FILES TO PUT IN ZIP FILE";
string strZipPath = #"PATH TO ZIP FILE";
if (File.Exists(strZipPath))
File.Delete(strZipPath);
using (ZipArchive archive = ZipFile.Open(strZipPath, ZipArchiveMode.Create))
{
foreach (FileInfo file in RecurseDirectory(strStartPath))
{
if (!(file.FullName.EndsWith(".TIF", StringComparison.OrdinalIgnoreCase)))
{
var destination = Path.Combine(file.DirectoryName, file.Name).Substring(strStartPath.Length + 1);
archive.CreateEntryFromFile(Path.Combine(file.Directory.ToString(), file.Name), destination);
}
}
}
}
public IEnumerable<FileInfo> RecurseDirectory(string path, List<FileInfo> currentData = null)
{
if (currentData == null)
currentData = new List<FileInfo>();
var directory = new DirectoryInfo(path);
foreach (var file in directory.GetFiles())
currentData.Add(file);
foreach (var d in directory.GetDirectories())
RecurseDirectory(d.FullName, currentData);
return currentData;
}
The application needs to zip up some log files in memory from a remote inetpub\LogFiles\W3SVCXX folder. The application will then extract the zip to a local directory for parsing. This is the code for this:
public static string ArchiveFiles(List<string> filePathsToArchive, string siteName, string chartId)
{
using (var memoryStream = new MemoryStream())
{
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Update,true))
{
int index = 0;
foreach (var filePath in filePathsToArchive)
{
archive.CreateEntryFromFile(filePath, index.ToString() + ".log"); //this line is taking forever when using a remote folder
index++;
}
var dir = CreateOrGetDirectory(ARCHIVE_DIRECTORY+ siteName+ #"\" + chartId + #"\");
archive.ExtractToDirectory(dir.FullName);
return dir.FullName;
}
}
}
It is incredibly inefficient on archive.CreateEntryFromFile() when I pass it a remote folder (works fine when the log files are on my local machine).
Is there any way that to optimize this when zipping up files from a remote location?
I am using C# code for aspx pages. I need to convert multiple files in to single zip file that can be readable by windows default zip software.
Can any one have an idea about it...?
See this tutorial: Creating Zip archives in .NET (without an external library like SharpZipLib)
ZipFile zip= new ZipFile("MyNewZip.zip");
zip.AddDirectory("My Pictures", true); // AddDirectory recurses subdirectories
zip.Save();
Or you can use SharpZipLib.
Use an free library like
http://www.sharpdevelop.net/OpenSource/SharpZipLib/
DotNetZip is a good open source one, without any licensing issue.
//use this library SharpZipLib.
using this you can send multiple file for zipping which user selected and can save it to the physical path you specify either on client.
public string zipfile(string[] files)
{
string[] filenames = new string[files.Length];
for (int i = 0; i < files.Length; i++)
filenames[i] = HttpContext.Current.Request.PhysicalApplicationPath + files[i].Remove(0, 10// set it according to your filename).ToString();
else
for (int i = 0; i < files.Length; i++)
filenames[i] = HttpContext.Current.Request.PhysicalApplicationPath + files[i].Replace(HttpContext.Current.Request.UrlReferrer.ToString(), "");
string DirectoryName = filenames[0].Remove(filenames[0].LastIndexOf('/'));
DirectoryName = DirectoryName.Substring(DirectoryName.LastIndexOf('/') + 1).Replace("\\", "");
try
{
string newFile = HttpContext.Current.Request.PhysicalApplicationPath + "the physical path where you want to save it" + DirectoryName + ".zip";
if (File.Exists(newFile))
File.Delete(newFile);
using (ZipFile zip = new ZipFile())
{
foreach (string file in filenames)
{
string newfileName = file.Replace("\\'", "'");
zip.CompressionLevel = 0;
zip.AddFile(newfileName, "");
}
zip.Save(newFile);
}
}