I'm trying to download multiple files from an SFTP server and save them to the install path (or actually, ANY path at the moment just to get it working). However, I get an UnauthorizedAccess Exception no matter where I try to save the files.
As far as was aware, there are no special permissions required to save files to the install dir (Hence why I chose this folder).
Thread myThread = new Thread(delegate() {
string host;
string username;
string password;
// Path to folder on SFTP server
string pathRemoteDirectory = "public_html/uploads/17015/";
// Path where the file should be saved once downloaded (locally)
StorageFolder localFolder = Windows.ApplicationModel.Package.Current.InstalledLocation;
string pathLocalDirectory = localFolder.Path.ToString();
var methods = new List<AuthenticationMethod>();
methods.Add(new PasswordAuthenticationMethod(username, password));
//TODO - Add SSH Key auth
var con = new ConnectionInfo(host, 233, username, methods.ToArray());
using (SftpClient sftp = new SftpClient(con))
{
try
{
sftp.Connect();
var files = sftp.ListDirectory(pathRemoteDirectory);
// Iterate over them
foreach (SftpFile file in files)
{
Console.WriteLine("Downloading {0}", file.FullName);
using (Stream fileStream = File.OpenWrite(Path.Combine(pathLocalDirectory, file.Name)))
{
sftp.DownloadFile(file.FullName, fileStream);
Debug.WriteLine(fileStream);
}
}
sftp.Disconnect();
}
catch (Exception er)
{
Console.WriteLine("An exception has been caught " + er.ToString());
}
}
});
Connection to the server is all fine, the exception occurs on this line.
using (Stream fileStream = File.OpenWrite(Path.Combine(pathLocalDirectory, file.Name)))
I'm must be missing something obvious here but it's worth noting that I've also tried writing to Special Folders like the Desktop, the users Document folder and also direct to the C:/ drive, all with the same exception. I'm also running with Administrator privileges and I have the correct permissions set in the folders.
It turns out that SFTP was counting '.' and '..' as files and trying to download those, when obviously '.' is the set SFTP folder and '..' is the previous folder. This was causing a permissions exception, not 100% sure why. Simply iterating over the files to make sure they're not named '.' or '..' fixed the issue. Code below.
sftp.Connect();
var files = sftp.ListDirectory(pathRemoteDirectory);
// Iterate over them
foreach (SftpFile file in files)
{
if (!file.IsDirectory && !file.IsSymbolicLink)
{
using (Stream fileStream = File.OpenWrite(Path.Combine(pathLocalDirectory, file.Name)))
{
sftp.DownloadFile(file.FullName, fileStream);
Debug.WriteLine(pathLocalDirectory);
}
}
else if (file.Name != "." && file.Name != "..")
{
Debug.WriteLine("Directory Ignored {0}", file.FullName);
}
else if (file.IsSymbolicLink)
{
Debug.WriteLine("Symbolic link ignored: {0}", file.FullName);
}
}
sftp.Disconnect();
You have multiple problems here. The parent folder ("..") reference you answered is one blocker, but that doesn't address the deeper problem that the InstalledLocation is read-only.
UWP apps do not have direct access to most file system locations. By default they can read and write to their ApplicationData directory and they can read from (but not write to) the InstalledLocation. The failures you saw for Desktop, Documents, and C:\ are all expected.
Other locations (including Desktop, Documents, and C:) may be granted access by the user either explicitly or via the app's declared capabilities. They can be accessed via the file broker through the StorageFile object.
See the UWP File access permissions documentation:
The app's install directory is a read-only location. You can't gain
access to the install directory through the file picker.
For the long term you'll want to download your files somewhere else: probably into one of the ApplicationData folders. These folders are the only ones with no special permission requirements for UWP apps.
So why does this work for you now?
You're running into a debugging quirk where your app is not fully installed but is staged from your VS project directory. This allows the app to write to the staged install directory, but once it is properly deployed into Program Files\WindowsApps writing to the InstalledLocation will fail.
Try Path.GetTempPath();. You should have permission there.
When it says you don't have permission, you don't. 8-)
Also, there's no such thing as "no special permissions". Everything requires some level of permission for access.
Related
I want to copy an xml file from a server and then connect to all user profiles and overwrite the user profile xml file they have with the one copied from the server. I would like this to be an executable so it can be run with SCCM for all users. I know there will have to be administration privileges but I'm not certain how to code it. I'm also open to ideas as how to do this differently, but I do want to do this with C# and make it into an executable for SCCM.
namespace copy_delete_move_files
{
public class SimpleFileCopy
{
public static object Logger { get; private set; }
static void Main()
{
string fileName = "Customize.xml";
string sourcePath = #"\\pathToServer\c$\TestFolder";
string targetPath = #"\\pathToUserProfiles\c$\%USERPROFILE%\APPDATA\Roaming\Folder\Customize";
// Use Path class to manipulate file and directory paths.
string sourceFile = Path.Combine(sourcePath, fileName);
string destFile = Path.Combine(targetPath, fileName);
// To copy a folder's contents to a new location:
// Create a new target folder, if necessary.
if (!Directory.Exists(targetPath))
{
Directory.CreateDirectory(targetPath);
}
// To copy a file to another location and
// overwrite the destination file if it already exists.
File.Copy(sourceFile, destFile, true);
// To copy all the files in one directory to another directory.
// Get the files in the source folder.
// Note: Check for target path was performed previously
// in this code example.
if (Directory.Exists(sourcePath))
{
string[] files = Directory.GetFiles(sourcePath);
// Copy the files and overwrite destination files if they already exist.
foreach (string s in files)
{
// Use static Path methods to extract only the file name from the path.
fileName = Path.GetFileName(s);
destFile = Path.Combine(targetPath, fileName);
File.Copy(s, destFile, true);
}
}
else
{
Console.WriteLine("Source path does not exist!");
}
// Keep console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
}
With SCCM you can achieve this using the following steps:
Create your program in the way that it works for one user (the whole multi user part will be left to sccm you just write it in a way it works for any user you double click it with). As appdata path you just use something like:
string fileNameWithExt = "your folderpath and filename";
string destPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), fileNameWithExt);
In SCCM when you create your program in the wizard you select "Program can run: Only when a user is logged on" and "Run mode: Run with user's rights". Once created you enter the properties of the program go to "Advanced" and chose "When this program is assigned to a computer: Run once for every user that logs on"
This makes sure that not only all users now but all in the future will get the program. It also help you if you really want to copy from a server because by default SCCM uses the local system account which has no access rights to your server shares (although you could grant them). The should also be possible with an application instead of a program.
Downsides are:
Your users ALL have to be allowed to access that share (could be
bypassed by using sccms distribution point method but would be more
complicated if the file on the server is updated often)
If you need admin rights for any operations it is not possible this
way. If the admin rights are only for parts that are not per user
specific you could split the program into two parts one per machine,
one per computer.
IIS 8, ASP.NET MVC 4, .NET 4.5
private static string SaveProfilePicFile(UserProfileViewModel model)
{
var tempFilename = Path.GetTempFileName();
model.ProfilePic.Profile.UploadedFile.SaveAs(tempFilename);
var staticContentFilename = Helpers.GetStaticContentFilename(
StaticContentType.Avatar, model.ProfilePic.Profile.UserId);
var destinationFilename = Path.Combine(
ConfigurationManager.AppSettings["StaticContentPath"],
"profile",
staticContentFilename);
if (File.Exists(destinationFilename))
File.Delete(destinationFilename);
if (!HasJpegHeader(tempFilename)) // convert temp file into JPG
{
using (var image = new Bitmap(tempFilename))
image.Save(destinationFilename, ImageFormat.Jpeg);
File.Delete(tempFilename);
}
else
{
File.Move(tempFilename, destinationFilename);
}
return staticContentFilename;
}
I'm not interested in a code review, I know things could be done better. Right now I've hit an unusual problem. StaticContentPath points to c:\inetpub\wwwroot\static.domain.com, which is being served by a different application pool which is configured to disable scripting and cache things heavier. If I manually place a file in the static content folder, it will serve correctly. If the above code (from a different application pool) saves a file there, the permissions are very unusual. I'll attach screenshots.
The "default" file is one I pasted manually. It properly inherited permissions from the parent folder. The hashed filename was saved by the above code, and it does not inherit permissions properly. When I attempt to access the file, I get a very basic error message from IIS, the entirety of which is "The page cannot be displayed because an internal server error has occurred." No styling, nothing I'm used to seeing with IIS errors. If I manually add read permissions to the IIS_IUSRS account everything works as I'd expect.
Why is this happening, what can I do to mitigate it, and does my code need to be updated?
I suspect the problem is with the use of Path.GetTempFileName followed by File.Move. When the uploaded file is saved to tempFilename, the temporary file gets whatever permissions are assigned to the temporary folder. Moving the file preserves those permissions as is instead of recalculating the inheritable permissions based on the destination.
Instead of File.Move, try using File.Copy followed by File.Delete:
//File.Move(tempFilename, destinationFilename);
File.Copy(tempFilename, destinationFilename);
File.Delete(tempFilename);
I'm using ASP(C#).NET
Need to access some video files that are kept in another server
say I'm using 111.222.33.33 & files are kept in 'repository' folder of F: drive in 222.111.12.12
below is my code segment to pull file name & path into an asp gridview so that I can download these files by clicking on the download link.
I'm able to download the files when they are in the same IP address.
But I'm having trouble with the file path. Need some help with the UR. Thnx.
String fileSearchPatern = "*.*";
DirectoryInfo directory = new DirectoryInfo(Server.MapPath(#"\\222.111.12.12\F:\repository\"));
if (gvSource == null)
{
gvSource = DisplayFilesInGridViewTwo();
}
DataRow gvRow;
FileInfo[] files = directory.GetFiles(fileSearchPatern, SearchOption.AllDirectories);
foreach (FileInfo fileInfo in files)
{
gvRow = gvSource.NewRow();
gvRow["Name"] = fileInfo.Name;
gvRow["FilePath"] = #"\\222.111.12.12\F:\repository\" + fileInfo.Name;
gvSource.Rows.Add(gvRow);
}
if (files.Length > 0)
{
this.GridView2.DataSource = gvSource;
this.GridView2.DataBind();
}
else
{
this.GridView2.DataSource = null;
this.GridView2.DataBind();
}
Not sure exactly, but this might help, I can see clearly that on the network access you cannot use F: but have to use F$ instead, try to do the following steps please and tell me:
1- Check the network weather you can access the F drive by click on Windows + R, and write #\\222.111.12.12\F$\repository
2- If not, then check the network first connection, then weather this is shared or permission given to you or not..
3- If you want to store on this folder, you should give permission of Network Service to it, if only reading then no need, the exists permission should be enough then...
Wow, is this way more complicated than it needs to be. Can someone explain to me why the following code works:
string stringToWrite = "SomeStuff";
Windows.ApplicationModel.Package package = Windows.ApplicationModel.Package.Current;
Windows.Storage.StorageFolder installedLocation = package.InstalledLocation;
var files = await installedLocation.GetFilesAsync();
foreach (Windows.Storage.StorageFile sf in files)
{
if (sf.Name.Equals("log.txt"))
{
await FileIO.AppendTextAsync(sf, stringToWrite);
}
}
And yet the following fails with AccessDenied:
Windows.ApplicationModel.Package package = Windows.ApplicationModel.Package.Current;
Windows.Storage.StorageFolder installedLocation = package.InstalledLocation;
var log = await installedLocation.GetFileAsync("log.txt");
await FileIO.AppendTextAsync(log, stringToWrite);
The only difference is looping through the files returned by the GetFilesAsync method vs getting the file by name. By the way, getting the file by name works because if I misspell log.txt in GetFileAsync, I get an exception.
Very confusing....
You should not be using your installed location to write any files. It is supposed to be read-only as per MSDN: File Access/Permissions in Windows Store Apps:
The app's install directory is a read-only location. You can’t gain access to the install directory through the file picker.
You should be using either the Local, Roaming, or Temporary storage locations.
See this link: MSDN: Quickstart Local Application Data
What is the best way to download all files in a remote directory using C# and FTP and save them to a local directory?
Thanks.
downloading all files in a specific folder seems to be an easy task. However, there are some issues which has to be solved. To name a few:
How to get list of files (System.Net.FtpWebRequest gives you unparsed list and directory list format is not standardized in any RFC)
What if remote directory has both files and subdirectories. Do we have to dive into the subdirs and download it's content?
What if some of the remote files already exist on the local computer? Should they be overwritten? Skipped? Should we overwrite older files only?
What if the local file is not writable? Should the whole transfer fail? Should we skip the file and continue to the next?
How to handle files on a remote disk which are unreadable because we don’t have sufficient access rights?
How are the symlinks, hard links and junction points handled? Links can easily be used to create an infinite recursive directory tree structure. Consider folder A with subfolder B which in fact is not the real folder but the *nix hard link pointing back to folder A. The naive approach will end in an application which never ends (at least if nobody manage to pull the plug).
Decent third party FTP component should have a method for handling those issues. Following code uses our Rebex FTP for .NET.
using (Ftp client = new Ftp())
{
// connect and login to the FTP site
client.Connect("mirror.aarnet.edu.au");
client.Login("anonymous", "my#password");
// download all files
client.GetFiles(
"/pub/fedora/linux/development/i386/os/EFI/*",
"c:\\temp\\download",
FtpBatchTransferOptions.Recursive,
FtpActionOnExistingFiles.OverwriteAll
);
client.Disconnect();
}
The code is taken from my blogpost available at blog.rebex.net. The blogpost also references a sample which shows how ask the user how to handle each problem (e.g. Overwrite/Overwrite older/Skip/Skip all).
Using C# FtpWebRequest and FtpWebReponse, you can use the following recursion (make sure the folder strings terminate in '\'):
public void GetAllDirectoriesAndFiles(string getFolder, string putFolder)
{
List<string> dirIitems = DirectoryListing(getFolder);
foreach (var item in dirIitems)
{
if ( item.Contains('.') )
{
GetFile(getFolder + item, putFolder + item);
}
else
{
var subDirPut = new DirectoryInfo(putFolder + "\\" + item);
subDirPut.Create();
GetAllDirectoriesAndFiles(getFolder + item + "\\", subDirPut.FullName + "\\");
}
}
}
The "item.Contains('.')" is a bit primitive, but has worked for my purposes. Post a comment if you need an example of the methods:
GetFile(string getFileAndPath, string putFileAndPath)
or
DirectoryListing(getFolder)
For FTP protocol you can use FtpWebRequest class from .NET framework. Though it does not have any explicit support for recursive file operations (including downloads). You have to implement the recursion yourself:
List the remote directory
Iterate the entries, downloading files and recursing into subdirectories (listing them again, etc.)
Tricky part is to identify files from subdirectories. There's no way to do that in a portable way with the FtpWebRequest. The FtpWebRequest unfortunately does not support the MLSD command, which is the only portable way to retrieve directory listing with file attributes in FTP protocol. See also Checking if object on FTP server is file or directory.
Your options are:
Do an operation on a file name that is certain to fail for file and succeeds for directories (or vice versa). I.e. you can try to download the "name". If that succeeds, it's a file, if that fails, it's a directory. But that can become a performance problem, when you have a large number of entries.
You may be lucky and in your specific case, you can tell a file from a directory by a file name (i.e. all your files have an extension, while subdirectories do not)
You use a long directory listing (LIST command = ListDirectoryDetails method) and try to parse a server-specific listing. Many FTP servers use *nix-style listing, where you identify a directory by the d at the very beginning of the entry. But many servers use a different format. The following example uses this approach (assuming the *nix format)
void DownloadFtpDirectory(
string url, NetworkCredential credentials, string localPath)
{
FtpWebRequest listRequest = (FtpWebRequest)WebRequest.Create(url);
listRequest.UsePassive = true;
listRequest.Method = WebRequestMethods.Ftp.ListDirectoryDetails;
listRequest.Credentials = credentials;
List<string> lines = new List<string>();
using (WebResponse listResponse = listRequest.GetResponse())
using (Stream listStream = listResponse.GetResponseStream())
using (StreamReader listReader = new StreamReader(listStream))
{
while (!listReader.EndOfStream)
{
lines.Add(listReader.ReadLine());
}
}
foreach (string line in lines)
{
string[] tokens =
line.Split(new[] { ' ' }, 9, StringSplitOptions.RemoveEmptyEntries);
string name = tokens[8];
string permissions = tokens[0];
string localFilePath = Path.Combine(localPath, name);
string fileUrl = url + name;
if (permissions[0] == 'd')
{
Directory.CreateDirectory(localFilePath);
DownloadFtpDirectory(fileUrl + "/", credentials, localFilePath);
}
else
{
var downloadRequest = (FtpWebRequest)WebRequest.Create(fileUrl);
downloadRequest.UsePassive = true;
downloadRequest.UseBinary = true;
downloadRequest.Method = WebRequestMethods.Ftp.DownloadFile;
downloadRequest.Credentials = credentials;
var response = downloadRequest.GetResponse();
using (Stream ftpStream = response.GetResponseStream())
using (Stream fileStream = File.Create(localFilePath))
{
ftpStream.CopyTo(fileStream);
}
}
}
}
The url must be like:
ftp://example.com/ or
ftp://example.com/path/
Or use 3rd party library that supports recursive downloads.
For example with WinSCP .NET assembly you can download whole directory with a single call to Session.GetFiles:
// Setup session options
SessionOptions sessionOptions = new SessionOptions
{
Protocol = Protocol.Ftp,
HostName = "example.com",
UserName = "user",
Password = "mypassword",
};
using (Session session = new Session())
{
// Connect
session.Open(sessionOptions);
// Download files
session.GetFiles("/home/user/*", #"d:\download\").Check();
}
Internally, WinSCP uses the MLSD command, if supported by the server. If not, it uses the LIST command and supports dozens of different listing formats.
(I'm the author of WinSCP)
You could use System.Net.WebClient.DownloadFile(), which supports FTP. MSDN Details here
You can use FTPClient from laedit.net. It's under Apache license and easy to use.
It use FtpWebRequest :
first you need to use WebRequestMethods.Ftp.ListDirectoryDetails to get the detail of all the list of the folder
for each files you need to use WebRequestMethods.Ftp.DownloadFile to download it to a local folder