SSH.NET SftpClient: Copy / Duplicate SftpFile - c#

Is there a way to copy a file in an other directory like a copy/paste.
.MoveTo() method move only the SftpFile and I tried WriteAllBytes() method using SftpFile.Attribues.GetBytes(), but it writes always a corrupted file.
Thank you

You will hardly be able to copy the file directly. For details why, see:
In an SFTP session is it possible to copy one remote file to another location on same remote SFTP server?
So you have to download and re-upload the file.
The easiest way to do that (without creating a temporary local file) is:
SftpClient client = new SftpClient("exampl.com", "username", "password");
client.Connect();
using (Stream sourceStream = client.OpenRead("/source/path/file.dat"))
using (Stream destStream = client.Create("/dest/path/file.dat"))
{
sourceStream.CopyTo(destStream);
}

Here is how to copy remote file to new one:
using (var sftp = new SftpClient(host, username, password))
{
client.Connect();
using (Stream sourceStream = sftp.OpenRead(remoteFile))
{
sftp.UploadFile(sourceStream, remoteFileNew));
}
}

Related

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

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
This is 'exception stacktrace' on sending the empty-stream to Azure
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);

Getting "it is being used by another process" after trying to extract a ZIP file downloaded from SFTP server using SSH.NET

I got a problem when I want to unzip a file.
After I download a file from SFTP, I want to unzip that, but it always tells me it's being used by another process.
I want to find the question by google but it seems nobody has this question.
Can somebody teach me how to solve it?
Thank you a lot.
bool result = false;
string zipFile = "";
using (SftpClient sftp = new SftpClient(ip, user, pw))
{
sftp.Connect();
var sftpFiles = GetFileList("/", sftp);
zipFile = GetZipFile(sftpFiles);
if (zipFile != null)
{
var file = File.OpenWrite(fileName);
sftp.DownloadFile(fileName,file);
result = true;
sftp.Disconnect();
}
}
if (result)
{
using (ZipFile zipList = ZipFile.Read(fileName))
{
ZipEntry zipFile = zipList[fileName];
zipFile.Extract("/", ExtractExistingFileAction.OverwriteSilently);
}
}
You do not close the file after you download it.
The download code should be like:
using (var file = File.OpenWrite(fileName))
{
sftp.DownloadFile(fileName, file);
}

How to download a file from a URL using c#

I want to download a .xlsx file from below address.
http://members.tsetmc.com/tsev2/excel/MarketWatchPlus.aspx?d=1396-08-08
by clicking on the link automatically a file will be downloaded by the browser. I tried to download the file from this codes:
using (var client = new WebClient())
{
client.DownloadFile("http://members.tsetmc.com/tsev2/excel/MarketWatchPlus.aspx?d=1396-08-08", #"D:\Archive\1396-08-08.xlsx");
}
But it will download a weird file different from the file downloaded by the browser at first step.
Also I tried :
System.Diagnostics.Process.Start(
"http://members.tsetmc.com/tsev2/excel/MarketWatchPlus.aspx?d=0");
But this code has two disadvantages:
1- It opens a browser which is not required.
2- I can't determine any path or file name for the downloaded file.
I want to gain exactly the same file that will be downloaded by clicking on the above link address.
How can I download my required file?
Lasse Vågsæther Karlsen is correct. Your browser is smart enough to decompress the file because the response contains the header:
content-encoding:"gzip"
You can download and decompress the file with this code (adjust accordingly for your file name, path, etc.)
void Main()
{
using (var client = new WebClient())
{
client.Headers.Add("accept", "*/*");
byte[] filedata = client.DownloadData("http://members.tsetmc.com/tsev2/excel/MarketWatchPlus.aspx?d=1396-08-08");
using (MemoryStream ms = new MemoryStream(filedata))
{
using (FileStream decompressedFileStream = File.Create("c:\\deleteme\\test.xlsx"))
{
using (GZipStream decompressionStream = new GZipStream(ms, CompressionMode.Decompress))
{
decompressionStream.CopyTo(decompressedFileStream);
}
}
}
}
}

C# Multiple Download from FTP using parallel task - Duplicate Download issue

I am facing a strange issue, I want to download a list of files from FTP. I preferred to go with Parallel Task. Below is my code. The issue is, all the list of files are getting downloaded, but duplicate files with different name are being generated. I am very new to Parallel task concept. Please help me to find out the issue.
Note: I am using SSH.Net for sftp connection and download.
private void ConcurrentDownload()
{
// Declaring Connection Information
PasswordAuthenticationMethod pm = new PasswordAuthenticationMethod("FTPUserName", "Password");
ConnectionInfo connectionInfo = new ConnectionInfo("FTPHost", 22, "FTPUserName", ProxyTypes.Socks5, "127.0.0.1", 8080, string.Empty, string.Empty, pm);
using (SftpClient sfc = new SftpClient(connectionInfo))
{
// Establish the remote connection
sfc.Connect();
// Getting Remote Directory Contents
IEnumerable<SftpFile> sFiles = new List<SftpFile>();
sFiles = sfc.ListDirectory(".\\");
// Building the File List
List<string> remotefiles = new List<string>();
foreach (SftpFile sfile in sFiles)
{
if (!sfile.IsDirectory)
{
string ss = sfile.Name;
remotefiles.Add(ss);
}
}
// Parallel Download
Parallel.ForEach(remotefiles.Distinct(), file => DownloadFile(sfc, file));
sfc.Disconnect();
}
}
private void DownloadFile(SftpClient sf, string RemoteFileName)
{
using (Stream ms = File.OpenWrite(RemoteFileName))
{
sf.DownloadFile(RemoteFileName, ms);
}
}
You better use Distinct like below
Parallel.ForEach(remotefiles.Distinct(), file => DownloadFile(sfc, file));
if you have duplicate file names and when parallel processing start on same file you will get exception on those duplicate files.
And also you are not downloading to another location, what you are doing is download to same ftp source location. is that correct?
I would give diferent download directory and get file name from source file and then download to that location as below
private void DownloadFile(SftpClient sf, string RemoteFileName)
{
string downloadTo = Path.Combine(DownloadDirectoryPath, Path.GetFileName(RemoteFileName));
using (Stream ms = File.OpenWrite(downloadTo))
{
sf.DownloadFile(RemoteFileName, ms);
}
}
Related Reference : SFTP Async Upload in Parallel

How can I create a file that can be accessed by any user?

My software needs to generate files on the filesystem that can be read by any other instance of my program, regardless of the user account it's running under. The problem I'm having is that if a file is created under an admin account then I run into an UnauthorizedAccessException when trying to read the contents of the file under a different account. Here's the code I'm using to create the file
using (var fileStream = File.Create(path))
using (var streamWriter = new StreamWriter(fileStream))
{
streamWriter.Write(/*some data*/);
}
and to read from the file
using (var fileStream = new FileStream(fileName, FileMode.Open))
using (var streamReader = new StreamReader(fileStream))
{
var idLine = streamReader.ReadLine();
if (idLine != null) fileContents = idLine;
}
Thanks
If you want to change the access to your file, you have to use the AccessControl : http://msdn.microsoft.com/en-us/library/9c13ttb3.aspx
Call Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) to get an application specific path which is accessible (read/write etc.) by all users/accounts on that computer.

Categories

Resources