I'm running into some issues trying to move flat HTML files around on a server.
In short, the process says that it should take whatever file is currently in the newpath (if there is one) and move it to the backup folder. Then take the file in the old path and move it to the new path. Then it checks to see if it was successful (i.e. does the newpath exist) and if it wasn't, it replaces the backup. I've pasted the method below for your viewing pleasure.
public bool MoveContent(string oldPath, string newPath, string backupDirectoryPath, out string backupPath)
{
backupPath = String.Empty;
var oldFilePath = HttpContext.Current.Server.MapPath(oldPath);
var newFilePath = HttpContext.Current.Server.MapPath(newPath);
var newDirectory = Path.GetDirectoryName(newFilePath);
// If the file we're moving doesn't exist, fail.
if (!File.Exists(oldFilePath))
throw new InvalidPathException(oldFilePath);
// If no destination is found, fail.
if (string.IsNullOrWhiteSpace(newDirectory))
throw new InvalidPathException(newFilePath);
if (!Directory.Exists(newDirectory))
Directory.CreateDirectory(newDirectory);
var backupPhysicalPath = String.Empty;
// If there is a file in our destination, back that one up.
if (File.Exists(newFilePath) && !String.IsNullOrWhiteSpace(backupDirectoryPath))
{
var backupFilePath = HttpContext.Current.Server.MapPath(backupDirectoryPath);
var backupDirectory = Path.GetDirectoryName(backupFilePath);
// If the backup destination doesn't exist, fail.
if(string.IsNullOrWhiteSpace(backupDirectory))
throw new InvalidPathException(backupDirectory);
if(!Directory.Exists(backupDirectory))
Directory.CreateDirectory(backupDirectory);
var fileName = Path.GetFileNameWithoutExtension(newFilePath);
var currentDateTime = DateTime.Now.ToString(FileHelpers.TempFileDateFormat);
var fileExtension = Path.GetExtension(newFilePath);
// Example Result: hardware-2015-01-30-08-35-26-475.html
backupPath = backupDirectoryPath.Replace(fileName, fileName + "-" + currentDateTime);
backupPhysicalPath = String.Format("{0}\\{1}-{2}{3}", backupDirectory, fileName, currentDateTime, fileExtension);
// If there is already a file in our backup destination, fail.
if (File.Exists(backupPhysicalPath))
throw new InvalidPathException(backupPhysicalPath);
// Backup the file that currently exists in our new destination.
File.Move(newFilePath, backupPhysicalPath);
}
// Move our file to the new destination.
File.Move(oldFilePath, newFilePath);
// Return false if the new file doesn't exist.
if (!File.Exists(newFilePath))
{
// If we made a backup, return the backup to the original loction, since there's nothing in the destination.
if (!String.IsNullOrWhiteSpace(backupPhysicalPath) && File.Exists(backupPhysicalPath))
{
File.Move(backupPhysicalPath, newFilePath);
}
throw new Exception(String.Format("Failed to move content. OldPath: '{0}'; NewPath: '{1}'; BackupPath: '{2}'", oldFilePath, newFilePath, backupPhysicalPath));
}
return true;
}
Here's an example of the parameters being passed in:
oldPath: "/client/content/en/unpublished/Anpan.html"
newPath: "/client/content/en/Anpan.html"
backupDirectoryPath: "/client/content/en/backups/Anpan.html"
The problem that I'm running into is that sometimes the backup file will be made (it will move from newpath to backuppath), but it won't move from oldpath to newpath.
I've been unable to actually reproduce the issue as it happens so infrequently and without any exceptions being thrown, but the symptoms exist (I can see the files on the filesystem when the client reports the issue) and it's been reported multiple times.
I put some logging around it and wrapped the entire method in a try/catch. It never fails unexpectedly (except when I specifically throw the InvalidPathException). There is nothing in my logs when it happens.
Can anyone help me to diagnose this issue, or tell me if I'm doing something very wrong in my method that would cause the problem?
Thanks so much!
Related
I'm currently working on a dll library project.
if (!Directory.Exists(MenGinPath))
{
Directory.CreateDirectory(MenGinPath + #"TimedMessages");
File.WriteAllLines(MenGinPath + #"TimedMessages\timedmessages.txt", new string[] { "Seperate each message with a new line" });
}
else if (!File.Exists(MenGinPath + #"TimedMessages\timedmessages.txt"))
{
Directory.CreateDirectory(MenGinPath + #"TimedMessages");
File.WriteAllLines(MenGinPath + #"TimedMessages\timedmessages.txt", new string[] { "Seperate each message with a new line" });
}
As you can see if the statement Directory.Exists is false a specific directory (MenGinPath) will be created. However, if the same path, with another file in addition is false, the second functions will be called.
My question is the following: is there any way to make this shorter?
Because as you can see I'm calling 2 times the same functions:
Directory.CreateDirectory(MenGinPath + #TimedMessages\timedmessages.txt
and
File.WriteAllLines(MenGinPath + #"\TimedMessages\timedmessages.txt"))
Any help would be welcome!!
You don't need to check if directory exists because Directory.CreateDirectory automatically creates the directory if it does not exists and does nothing if the directory already exists.
Also, do not include the filename when creating the directory. Yes, it wont error but just for clarity sake.
Another one is to use Path.Combine instead of hardcoding the path. This will improve readability of your code.
So, here's what I can come up with:
string dir = Path.Combine(MenGinPath, #"Groups\TimesMessages");
string file = Path.Combine(dir, "timedmessages.txt");
// this automatically creates all directories in specified path
// unless it already exists
Directory.CreateDirectory(dir);
//of course, you still need to check if the file exists
if (!File.Exists(file) {
File.WriteAllLines(filePath, new string[] { "Seperate each message with a new line" });
}
/* or if file exists, do your stuff (optional)
* else {
* //do something else? maybe edit the file?
* }
*/
You can make your code shorter given the fact that CreateDirectory does nothing when the directory exists. Moreover do not pullute your code with all that string concatenations to create the path and the file names.
Just do it one time before entering the logic using the appropriate method to create filenames and pathnames (Path.Combine).
string messagePath = Path.Combine(MenGinPath, "TimedMessages");
string fileName = Path.Combine(messagePath, "timedmessages.txt");
// call the create even if it exists. The CreateDirectory checks the fact
// by itself and thus, if you add your own check, you are checking two times.
Directory.CreateDirectory(messagePath);
if (!File.Exists(fileName)
File.WriteAllLines(fileName, new string[] { "Seperate each message with a new line" });
Would something like this work?
string strAppended = string.Empty;
if (!Directory.Exists(MenGinPath))
{
strAppended = MenGinPath + #"Groups\timedmessages.txt";
}
else if (!File.Exists(MenGinPath + #"TimedMessages\timedmessages.txt"))
{
strAppended = MenGinPath + #"TimedMessages\TimedMessages.txt";
}
else
{
return;
}
Directory.CreateDirectory(strAppended);
File.WriteAllLines(strAppended, new string[] { "Seperate each message with a new line" });
I have found that it is a great idea to reuse blocks of code like this instead of hiding them in if statements because it makes code maintenance and debugging easier and less prone to missed bugs.
It seems the only difference between the 2 cases is the path. So just get only this path in your if-else
const string GroupsPath = #"Groups\timedmessages.txt";
const string TimedMessagesTxt = #"TimedMessages\TimedMessages.txt";
string addPath = null;
if (!Directory.Exists(MenGinPath)) {
addPath = GroupsPath;
} else if (!File.Exists(Path.Combine(MenGinPath, TimedMessagesTxt))) {
addPath = TimedMessagesTxt;
}
If (addPath != null) {
Directory.CreateDirectory(Path.Combine(MenGinPath, addPath));
File.WriteAllLines(Path.Combine(MenGinPath, TimedMessagesTxt),
new string[] { "Seperate each message with a new line" });
}
Note: Using Path.Combine instead of string concatenation has the advantage that missig or extra \ are added or removed automatically.
My application creates files and directories throughout the year and needs to access the timestamps of those directories to determine if it's time to create another one. So it's vital that when I move a directory I preserve its timestamps. I can do it like this when Directory.Move() isn't an option (e.g. when moving to a different drive).
FileSystem.CopyDirectory(sourcePath, targetPath, overwrite);
Directory.SetCreationTimeUtc (targetPath, Directory.GetCreationTimeUtc (sourcePath));
Directory.SetLastAccessTimeUtc(targetPath, Directory.GetLastAccessTimeUtc(sourcePath));
Directory.SetLastWriteTimeUtc (targetPath, Directory.GetLastWriteTimeUtc (sourcePath));
Directory.Delete(sourcePath, true);
However, all three of these "Directory.Set" methods fail if File Explorer is open, and it seems that it doesn't even matter whether the directory in question is currently visible in File Explorer or not (EDIT: I suspect this has something to do with Quick Access, but the reason isn't particularly important). It throws an IOException that says "The process cannot access the file 'C:\MyFolder' because it is being used by another process."
How should I handle this? Is there an alternative way to modify a timestamp that doesn't throw an error when File Explorer is open? Should I automatically close File Explorer? Or if my application simply needs to fail, then I'd like to fail before any file operations take place. Is there a way to determine ahead of time if Directory.SetCreationTimeUtc() for example will encounter an IOException?
Thanks in advance.
EDIT: I've made a discovery. Here's some sample code you can use to try recreating the problem:
using System;
using System.IO;
namespace CreationTimeTest
{
class Program
{
static void Main( string[] args )
{
try
{
DirectoryInfo di = new DirectoryInfo( #"C:\Test" );
di.CreationTimeUtc = DateTime.UtcNow;
Console.WriteLine( di.FullName + " creation time set to " + di.CreationTimeUtc );
}
catch ( Exception ex )
{
Console.WriteLine( ex );
//throw;
}
finally
{
Console.ReadKey( true );
}
}
}
}
Create C:\Test, build CreationTimeTest.exe, and run it.
I've found that the "used by another process" error doesn't always occur just because File Explorer is open. It occurs if the folder C:\Test had been visible because C:\ was expanded. This means the time stamp can be set just fine if File Explorer is open and C:\ was never expanded. However, once C:\Test becomes visible in File Explorer, it seems to remember that folder and not allow any time stamp modification even after C:\ is collapsed. Can anyone recreate this?
EDIT: I'm now thinking that this is a File Explorer bug.
I have recreated this behavior using CreationTimeTest on multiple Windows 10 devices. There are two ways an attempt to set the creation time can throw the "used by another process" exception. The first is to have C:\Test open in the main pane, but in that case you can navigate away from C:\Test and then the program will run successfully again. But the second way is to have C:\Test visible in the navigation pane, i.e. to have C:\ expanded. And once you've done that, it seems File Explorer keeps a handle open because the program continues to fail even once you collapse C:\ until you close File Explorer.
I was mistaken earlier. Having C:\Test be visible doesn't cause the problem. C:\Test can be visible in the main pane without issue. Its visibility in the navigation pane is what matters.
Try this:
string sourcePath = "";
string targetPath = "";
DirectoryInfo sourceDirectoryInfo = new DirectoryInfo(sourcePath);
FileSystem.CopyDirectory(sourcePath, targetPath, overwrite);
DirectoryInfo targetDirectory = new DirectoryInfo(targetPath);
targetDirectory.CreationTimeUtc = sourceDirectoryInfo.CreationTimeUtc;
targetDirectory.LastAccessTimeUtc = sourceDirectoryInfo.LastAccessTimeUtc;
targetDirectory.LastWriteTimeUtc = sourceDirectoryInfo.LastWriteTimeUtc;
Directory.Delete(sourcePath, true);
This will allow you to set the creation/access/write times for the target directory, so long as the directory itself is not open in explorer (I am assuming it won't be, as it has only just been created).
I am suspecting FileSystem.CopyDirectory ties into Explorer and somehow blocks the directory. Try copying all the files and directories using standard C# methods, like this:
DirectoryCopy(#"C:\SourceDirectory", #"D:\DestinationDirectory", true);
Using these utility methods:
private static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs)
{
// Get the subdirectories for the specified directory.
DirectoryInfo dir = new DirectoryInfo(sourceDirName);
if (!dir.Exists)
{
throw new DirectoryNotFoundException("Source directory does not exist or could not be found: " + sourceDirName);
}
if ((dir.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
{
// Don't copy symbolic links
return;
}
var createdDirectory = false;
// If the destination directory doesn't exist, create it.
if (!Directory.Exists(destDirName))
{
var newdir = Directory.CreateDirectory(destDirName);
createdDirectory = true;
}
// Get the files in the directory and copy them to the new location.
DirectoryInfo[] dirs = dir.GetDirectories();
FileInfo[] files = dir.GetFiles();
foreach (FileInfo file in files)
{
if ((file.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
continue; // Don't copy symbolic links
string temppath = Path.Combine(destDirName, file.Name);
file.CopyTo(temppath, false);
CopyMetaData(file, new FileInfo(temppath));
}
// If copying subdirectories, copy them and their contents to new location.
if (copySubDirs)
{
foreach (DirectoryInfo subdir in dirs)
{
string temppath = Path.Combine(destDirName, subdir.Name);
DirectoryCopy(subdir.FullName, temppath, copySubDirs);
}
}
if (createdDirectory)
{
// We must set it AFTER copying all files in the directory - otherwise the timestamp gets updated to Now.
CopyMetaData(dir, new DirectoryInfo(destDirName));
}
}
private static void CopyMetaData(FileSystemInfo source, FileSystemInfo dest)
{
dest.Attributes = source.Attributes;
dest.CreationTimeUtc = source.CreationTimeUtc;
dest.LastAccessTimeUtc = source.LastAccessTimeUtc;
dest.LastWriteTimeUtc = source.LastWriteTimeUtc;
}
I have a DirectoryNotFoundException on a .txt file if I use the full path it's working but I don't want to use the full path because I want the program work no matter where it is placed (compatibilty with the maximum of computer)
Here's my code
private void SaveClose_Click(object sender, RoutedEventArgs e)
{
if (Windowed.IsChecked == true)
windowed = true;
else
windowed = false;
string textWriteWindowed;
if (windowed == true)
{
textWriteWindowed = "-screen-fullscreen 0" + Environment.NewLine;
}
else
{
textWriteWindowed = "-screen-fullscreen 1" + Environment.NewLine;
}
var selectedResolution = ResolutionBox.SelectedItem.ToString();
var split = selectedResolution.Split('x');
widthChoose = Int32.Parse(split[0]);
heightChoose = Int32.Parse(split[1]);
string textWriteWidth;
textWriteWidth = "-screen-width " + widthChoose + Environment.NewLine;
string textWriteHeight;
textWriteHeight = "-screen-height " + heightChoose + Environment.NewLine;
File.WriteAllText(#"\Resources\arguments.txt", textWriteWindowed);
File.AppendAllText(#"\Resources\arguments.txt", textWriteWidth);
File.AppendAllText(#"\Resources\arguments.txt", textWriteHeight);
this.Close();
}
The first argument of File.WriteAllText takes a path as input. Whatever you have mentioned is not the absolute path but it is just the relative path of the file. WriteAllText creates the file but doesn't create the directory by itself. So something like:
File.WriteAllText(#"\arguments.txt", textWriteWindowed);
shall work (and create the file in the respective drive), but
File.WriteAllText(#"\Resources\arguments.txt", textWriteWindowed);
shall not work. Hence, if you want to create a file in the path where the application resides, you can do something like:
string folder=Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
File.WriteAllText(#"\arguments2.txt", "ABC");
If you want to create a directory, then you could do something like:
System.IO.FileInfo file = new System.IO.FileInfo(filePath);
file.Directory.Create();// If the directory already exists, this method does nothing.
System.IO.File.WriteAllText(file.FullName, textWriteWindowed);
Hope this answers your query.
you have to check whether the folder is exist before save the file,
if folder not exist create it using
Directory.CreateDirectory(...)
Directory.Exists(..)
you can use to check folder existence
IF you wanted to get the local path of the file you are executing use this:
var fInfo = new FileInfo(System.Reflection.Assembly.GetCallingAssembly().Location);
From there, you would do the following:
var parentDir = new DirectoryInfo(fInfo.DirectoryName);
var subDir = new DirectoryInfo(parentDir.FullName + "Resource");
if(!subDir.Exists)
subDir.Create();
This would ensure that you always have a folder in the directory of your executable. But just so you know, this is absolutely horrible code and should never ever be implemented in a production like environment. What if some knucklehead sysAdmin decides to place your program/folder in an area that the current user does not have access/writes too? The best place to write to is %APPDATA%, this will ensure the user always has read/write permissions to what you are trying to accomplish.
I don't know how but doing that worked for me :
File.WriteAllText(#"./arguments.txt", textWriteWindowed);
File.AppendAllText(#"./arguments.txt", textWriteWidth);
File.AppendAllText(#"./arguments.txt", textWriteHeight);
So something must be wrong with my string.
I am grabbing the value from a SQL database which comes out like this:
while (reader.Read())
{
// Row Values
// 0 = UID
// 1 = CreatedDate
// 2 = Location
documentID = reader.GetGuid(0);
fileName = reader.GetSqlValue(0).ToString() + ".zip";
location = reader.GetString(2);
createdDate = reader.GetDateTime(1);
The values returned from the database are as follows:
GUID: DC5A30D7-D528-4BA4-AA5A-5ECEB2CD9006
fileName: DC5A30D7-D528-4BA4-AA5A-5ECEB2CD9006.zip
Location: \\192.168.22.1\documentation
if (!DoesFileExist(location + fileName))
{
// Log error to database
}
static bool DoesFileExist(string location)
{
bool doesExist = false;
if (File.Exists(location))
{
doesExist = true;
}
return doesExist;
}
When it gets to the part File.Exists(location) it passes over it as if it dind't exist. Bur it does... When I navigate to it in an Explorer I find the zip file just fine...
What am I doing wrong here?
UID CreatedDate Location
DC5A30D7-D528-4BA4-AA5A-5ECEB2CD9006 2009-10-28 11:17:06.690 \\192.168.22.1\documentation
As it is written in the example above, the Location + Filename doesn't produce a correct full filename. There is no backslash to separate the path from the filename.
I suggest to use the appropriate method Path.Combine from the class (System.IO.Path) to make the correct full filename
if (!DoesFileExist(Path.Combine(location, fileName)))
The way I see it, you're sending: "\192.168.22.1\documentationDC5A30D7-D528-4BA4-AA5A-5ECEB2CD9006.zip" to the method.
Try putting another "\" there.
Most of the time I have a problem like that, it is because of permissions. Often, the user for the file explorer is different that is trying to find the file with file exists. If everything is correct with the location, the next place to look would be the permissions.
If I have a web method that deletes a file when called and it accepts three parameters (cNum, year, and fileName). Do I need to be worried about exploits of this method. The only thing I could think of would be using ..\..\..\ to drive the delete further up the folder structure. that should be pretty easy to remove that. But is there anything else that I should be worried about?
[WebMethod(EnableSession = true,
Description = "Method for deleting files uploaded by customers")]
[ScriptMethod(ResponseFormat = ResponseFormat.Xml)]
public Boolean deleteCustFiles(string cNum, string year, string fileName)
{
try
{
if (String.IsNullOrEmpty(cNum)
|| String.IsNullOrEmpty(year)
|| String.IsNullOrEmpty(fileName))
throw new Exception();
string path = Server.MapPath(#"~\docs\custFiles\"
+ year + #"\"
+ cNum + #"\" + fileName);
File.Delete(path);
}
catch
{
throw new Exception("Unable to delete file");
}
return true;
}
I would recommend using the GetFileName method on the Path class to cleanse the filename parameter, like so:
public Boolean deleteCustFiles(string cNum, string year, string fileName)
{
// Cleanse fileName.
fileName = Path.GetFileName(fileName);
The GetFileName method strips all directory information from a path, which is exactly what you want to do here.
With input like:
..\..\..\filename.ext
You would get:
filename.ext
In return, you don't have to worry about someone injecting a path which would escape the directory that you are targeting (assuming that this filename is user-input or from an open endpoint where someone could enter any input they want).
This then allows you to then append your custom path to fileName.
This only works of course if all of your files are in a pre-defined directory, which it seems it is.
This does not however, do anything to handle deleting files that a user doesn't have access to. If the files belong to another user in that directory, then there's no check here to see if that's the case (but if all users have rights to delete these files, then it's ok).
Also, you might want to use the Combine method on the Path class to combine your paths, like so:
string path = Server.MapPath(#"~\docs\custFiles\")
path = Path.Combine(path, year);
path = Path.Combine(path, cNum);
path = Path.Combine(path, fileName);
If you're using .NET 4.0 or above, you can use the overload of the Combine method that takes the parts of the path as a parameter array:
string path = Path.Combine(
Server.MapPath(#"~\docs\custFiles\"),
year, cNum, fileName);
Finally, as Shai points out, if possible (for a complete solution), to make this even more secure you should be enabling permissions on the file-system level.
If you are impersonating the user or using a constrained user account to handle all of the requests, then you should grant that user access to just the ~\docs\custFiles\ directory (and any sub directories).
Anything above that directory the user account should have no access to.
It is a good idea to check the file names and directory names if they are valid file names or not, check them against this char array:
Path.GetInvalidFileNameChars
EDIT:
And you should probably also validate the year and number like this:
bool valid = int.TryParse(num, out temp);
You may also want to consider using built in security on the file system to prevent users from deleting files in unwanted directories. If the web app is running under a specific user that has rights to delete files in only one directory, no matter what the user tries, the app will not have the rights to perform the delete.
In addition, this would make maintenance (ie: adding new directories) pretty easy without redeploying the app.
You could then catch the attempt to access the invalid access attempt and do something with it if you so desire.
[WebMethod(EnableSession = true,
Description = "Method for deleting files uploaded by customers")]
[ScriptMethod(ResponseFormat = ResponseFormat.Xml)]
public Boolean deleteCustFiles(string cNum, string year, string fileName)
{
try
{
if (String.IsNullOrEmpty(cNum) || String.IsNullOrEmpty(year) ||
String.IsNullOrEmpty(fileName))
throw new Exception();
string path =
Server.MapPath(#"~\docs\custFiles\" + year + #"\" + cNum +
#"\" + fileName);
File.Delete(path);
}
catch (System.Security.SecurityException e)
{
throw new Exception("Unauthorized attempt to delete file");
}
catch
{
throw new Exception("Unable to delete file");
}
return true;
}