Alright, I've seen tons of questions about this thing, but still, no one answers my question. In fact, each one of the questions I saw differs from the other, this access thing really seems to be hassling programmers.
Please check out the code:
DirectoryInfo Dir1 = Directory.CreateDirectory(Desktop + "\\DIR1");
DirectoryInfo Dir2 = Directory.CreateDirectory(Desktop + "\\DIR2");
//* Lets Create a couple of SubDirs in DIR1
for (int i = 0; i < 5; i++)
{
// this will create 5 SubDirs in DIR1, named Sub1, Sub2 ... Sub5.
Dir1.CreateSubdirectory("Sub" + (i + 1).ToString());
//* lets create 5 text files in each SubDir:
for (int j = 0; j < 5; j++)
{
File.Create(Dir1.FullName + "\\Sub"+(i+1).ToString() + "\\text"+(j+1).ToString() + ".txt");
}
}
//* Lets Move all what we created in DIR1 to DIR2 (THIS IS WHERE I'M GETTING THE EXCEPTION
Directory.Move(Dir1.FullName, Dir2.FullName + "\\DIR1");
// I also Tried Dir1.MoveTo(Dir2.FullName + "\\DIR1");
Stack Trace:
at System.IO.DirectoryInfo.MoveTo(String destDirName)
at Directory_Class.Program.Main(String[] args) in c:\users\vexe\documents\visual studio 2010\Projects\Directory_Class\Directory_Class\Program.cs:line 207
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
And of course, I tried the usual:
DirectorySecurity DirSec = Dir1.GetAccessControl();
string user = Environment.UserName;
DirSec.ResetAccessRule(new FileSystemAccessRule(user, FileSystemRights.FullControl, AccessControlType.Allow));
Dir1.SetAccessControl(DirSec);
But it didn't change a bit!
I also tried changing the permissions manually, by right clicking dir1 -> properties -> security -> edit -> add -> typed everyone (in the enter object names to select) -> ok -> fullcontrol to everyone. (I also saw that my user account had full control as well)
Any hints would be deeply appreciated
While it is an Access Denied exception, it sounds like the text files are in use and cannot be moved because there are open references to the file.
The File.Create method returns a FileStream object which I'd imagine must be closed/disposed before the files can be modified.
Try the following for your inner loop:
for (int j = 0; j < 5; j++)
{
using(var fs = File.Create(Dir1.FullName + "\\Sub"+(i+1).ToString() + "\\text"+(j+1).ToString() + ".txt"))
{
//fs.WriteByte(...);
fs.Close();
}
}
First off, you should use Path.Combine instead of doing string concatenation.
Second off, the stack trace isn't as helpful as the exception being thrown.
I imagine your problem might be fixed by doing this though:
Directory.Move(Dir1.FullName, Dir2.FullName);
If that fixes it, then the issue is with the DIR1 subdirectory you're trying to move it to.
As a debugging, step you should set failure auditing on the two folders (under advanced security settings). Just set everyone to audit all failures, then try your operation again. Depending on the OS version that you are running you should get the user account being used for the operation and what privilege was missing. Also make sure that there are no deny permissions set on the folder as they override all other permissions. You will want to look at the security event log. If there are no failure audits for the operation then it is not a permissions issues.
It seems to me Windows loves blocking deletes (renames) of directories for literally no reason. I'm sure it has one, but I don't care what it is. I've found that deleting the contents of the directory, then deleting the empty folder works every time. It works with the following rename as well.
I came up with this, as windows was giving me access to path denied for the FROM directory. I was just renaming it within the app, not really moving locations. Anyway, based on the above information, I came up with this and it works.
public static void MoveTo_BruteItAsNecessary(this DirectoryInfo FROM, string TO, bool recycle = false)
{
try
{
FROM.MoveTo(TO);
}
catch (IOException ex)
{
if (ex.Contains($"Access to the path '{FROM.FullName}' is denied."))
{ // Contains checks the Message & InnerException.Message(s) recursively
System.IO.Directory.CreateDirectory(TO);
foreach (var dir in FROM.GetDirectories())
dir.MoveTo(Path.Combine(TO, dir.Name));
foreach (var file in FROM.GetFiles())
file.MoveTo(Path.Combine(TO, file.Name));
if (recycle)
FROM.Recycle();
else
FROM.Delete();
}
else
throw;
}
}
Related
I have the following code to rename files in the following tree as from 00000001.pdf to the last file with this 8 character left padding, e.g: 00000100.pdf
Folder1
subfolder1
childfolder1
pdffile1
pdffile2
childfolder2
pdffile3
pdffile4
subfolder2
childfolder3
pdffile5
pdffile6
But for some reason in some of those child folders it keeps renaming them with no end.
Some times it just jumps to another number, as if it was an async operation. But if I stop and start again it goes okay until the second next folder, when it messes up again.
But this error only happened within 19 folders.
Indeed their pdf names are different from the others, but I don't see how it is related.
The other files were named something like "DOCUMENT_01" and so on, but these are:
0000000100000001.pdf
0000000200000001.pdf
0000000300000001.pdf
etc
static void Main(string[] args)
{
Console.WriteLine("Digite a pasta 'pai' onde serão buscados pdfs dentro das pastas 'filhas':");
string path = Console.ReadLine();
foreach (string dir in Directory.EnumerateDirectories(path))
{
foreach (string subdir in Directory.EnumerateDirectories(dir))
{
Console.WriteLine($"{dir} - {subdir}");
int n = 1;
foreach (string pdffile in Directory.EnumerateFiles(subdir, "*.pdf", SearchOption.AllDirectories))
{
Console.WriteLine(n.ToString().PadLeft(8, '0') + " " + new FileInfo(pdffile).Length);
File.Move(pdffile, subdir + $"\\{n.ToString().PadLeft(8, '0')}.pdf");
n++;
}
Console.WriteLine("\n\n");
}
}
}
What could be going wrong?
It should await for the File.Move method to end to add the n + 1 and then moving to the next pdffile as a synchronous operation. So why does it jumps numbers after a random time and why it keeps going forever other times?
And just to remember, if I stop the program and start again and put the folder that was messed up as the first one, it goes ok and only when it goes to the next folder, or the folder after next that it start to give me this error again.
Hope that I could make myself clear... Thanks for your attention!
EDIT: will try using FileInfo class to give me the parent folder with the SearchOption.AllDirectories option and exclude this 3 stage loop plus actually working for any kind of tree structure
EDIT2: Tried, worked as a "tree indepent" script but getting the same result with the files name after the first folder... As it's really fast, in 3 seconds it goes from 00000169.pdf to 00006239.pdf in a folder with just 330 items.
As commented already, it is not a good idea to move or rename files “WHILE” the code is enumerating though the list of those files as the posted code appears to do. This will cause obvious problems and you should simply mark the files somehow, then later come back and rename or move them.
More importantly, the big issue related to renaming/moving files is exactly as you describe with your current issue. The problem is that the errors are erratic and not consistent. Making it very difficult to trace. However, the problems you describe are classic trademarks of moving/renaming files while enumerating through those files.
With that said, the best way and easiest way to traverse an unknown number of folder levels given a starting folder is by using recursion. In a lot of cases, recursion can be avoided with some well though out loops, however when we do not know how many levels of folders there are, then, using a simple loop or foreach loop paradigm may be doable, however, you will most likely be adding variables and code that only makes this more complex. This is shown in the current code with the addition of the dir variable to keep track of “when” a different folder is used. Recursion is suited ideally for this situation.
In this case, this recursive method will be called ONCE for each folder and subfolders from a given “starting” folder location. This means that each time this recursive method is called is when a different folder is beginning to be processed. So n would always start at 1 and we do not need to keep track of the current folders path.
So the signature of this method will take a DirectoryFolder object as a “starting” folder. First we create some variables; a FileInfo array pdffiles to hold the pdf files in the given folder; in addition to a DirectoryInfo array foldersInThisFolder to hold all the other folders in this starting folder. Lastly an int n to index the files as the posted code is doing.
Next we get all the pdf files in this “starting” folder. If there are pdf files in this folder, then we loop through those files and process them. Next, we get all the other folders in this “starting” folder. Then start a loop through each folder. For each folder in this collection we will make the recursive call back to this method using the next folder as the “starting” folder, then the whole process continues until the loop through those folders ends.
static void TraverseDirectoryTree(DirectoryInfo startingFolder) {
FileInfo[] pdffiles = null;
DirectoryInfo[] foldersInThisFolder = null;
int n = 1;
Console.WriteLine(startingFolder.FullName);
// get all the pdf files in this folder
try {
pdffiles = startingFolder.GetFiles("*.pdf");
}
catch (Exception e) {
// you may want to catch specific exceptions
// however in this example we do not care what
// the exception is, we will simply ignore this.
// in most cases pdffiles will be null if an exception is thrown
Console.WriteLine(e.Message);
}
if (pdffiles != null) {
foreach (FileInfo pdffile in pdffiles) {
Console.WriteLine(pdffile.FullName + " -> " + n.ToString().PadLeft(8, '0') + " " + pdffile.Length);
//File.Move(pdffile.FullName, pdffile.DirectoryName + $"\\{n.ToString().PadLeft(8, '0')}.pdf");
// add file path to a list of files to rename later?
n++;
}
// start over wiith the sub folders in this folder
foldersInThisFolder = startingFolder.GetDirectories();
foreach (DirectoryInfo dirInfo in foldersInThisFolder) {
TraverseDirectoryTree(dirInfo);
}
}
}
Usage…
Console.WriteLine("Type the folder you want to start with:");
string path = Console.ReadLine();
DirectoryInfo di = new DirectoryInfo(path);
TraverseDirectoryTree(di);
Edit… after further testing it appears that what you are wanting to do is simply “rename” the pdf files. As suggested a simple solution is to save the files that we want to rename, then, after we collect the files we want to rename, we simply loop through those files and rename them. This should eliminate any problems by renaming files while enumerating though the files collection.
To help, I created a Dictionary<string, int> called filesToRename. While recursively looping through all the folders, we will add the full path of each pdf file we want to rename as the Key and the int value n as the Value. After the dictionary is filled we would simply loop through it and rename the files.
private static Dictionary<string, int> filesToRename = new Dictionary<string, int>();
Then replace the commented-out line in the recursive method TraverseDirectoryTree…
//File.Move(pdffile.FullName, pdffile.DirectoryName + $"\\{n.ToString().PadLeft(8, '0')}.pdf");
With…
filesToRename.Add(pdffile.FullName, n);
Then after the dictionary is filled we would loop through it and rename the files, something like…
DirectoryInfo di = new DirectoryInfo(path);
TraverseDirectoryTree(di);
foreach (KeyValuePair<string, int> kvp in filesToRename) {
int index = kvp.Key.ToString().LastIndexOf(#"\");
string dir = kvp.Key.ToString().Substring(0, index);
File.Move(kvp.Key, dir + $"\\{kvp.Value.ToString().PadLeft(8, '0')}.pdf");
}
I am hoping this makes sense…
Answer as Klaus Gütter helped me, I just added .ToList() to the Directory.EnumerateFiles so it made a fixed list first, and then made the foreach for each file
It will rename every pdf within the folder and it's subfolders
Console.WriteLine("Type the folder you want to start with:");
string path = Console.ReadLine();
string dir = "";
int n = 1;
foreach (string pdffile in Directory.EnumerateFiles(path, "*.pdf", SearchOption.AllDirectories).ToList())
{
FileInfo fi = new FileInfo(pdffile);
if (fi.DirectoryName == dir)
{
Console.WriteLine("\t" + n.ToString().PadLeft(8, '0'));
File.Move(pdffile, dir + $"\\{n.ToString().PadLeft(8, '0')}.pdf");
n++;
}
else
{
n = 1;
dir = fi.DirectoryName;
Console.WriteLine("\n\n" + dir);
File.Move(pdffile, dir + $"\\{n.ToString().PadLeft(8, '0')}.pdf");
Console.WriteLine("\t" + n.ToString().PadLeft(8, '0'));
n++;
}
}
So I needed to make a quick Windows Form app to process a list of files with their directories and copy them to a new dir. Normally I would use a batch file to do this e.g.
#echo off
mkdir "C:\Users\%username%\Desktop\inst"
:A
ping 127.0.0.1 -n 1 > nul
xcopy "C:\Users\%username%\Desktop\M14.0.1512.400-enu-x64.exe" "C:\Users\%username%\Desktop\inst" /y
xcopy "C:\Users\%username%\AppData\Local\Temp\vcredist.exe" "C:\Users\%username%\Desktop\inst" /y
GOTO A
I know I'm not using the best practices etc. but its a quick script I came up with to help speed up my work. Now doing this for a couple of files is fine but some of the applications I work on havelike 40+ files that I need to copy over and its a bit of a pain having to write a batch file each time.
So I slapped up a simple WF app with a simple input field and a button to start the process.
The user places a list of files (with the path and dir e.g. C:\foo\bar\hello.txt) into the input field and then the app takes each line from the text box and shoves it into a list after doing some basic filtering e.g. removing \n, \t, \r and encapsulaing the strings with double quotes if they are not in place already.
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
while (run)
{
foreach (string path in paths)
{
Thread.Sleep(100);
try
{
File.Copy(path, dir, true);
}
catch (Exception e)
{
g.log.WriteToLog("Failed to copy asset: " + e.ToString());
}
}
};
}).Start();
When I run that this is what I get in the logs:
//LOGS
21/03/2019 11:25:56 - Failed to copy asset: System.ArgumentException: Illegal characters in path. at System.IO.LongPathHelper.Normalize(String path, UInt32 maxPathLength, Boolean checkInvalidCharacters, Boolean expandShortPaths) at System.IO.Path.NormalizePath(String path, Boolean fullCheck, Int32 maxPathLength, Boolean expandShortPaths) at System.IO.Path.GetFullPathInternal(String path) at System.IO.File.InternalCopy(String sourceFileName, String destFileName, Boolean overwrite, Boolean checkHost) at S2_PackagingTool.Application_Extractor.ExtractAssets() in C:\Users\xxxx\Desktop\GitHub\S2-EnvPrepTool\Application_Extractor.cs:line 98
21/03/2019 11:25:56 - Path: "C:\Program Files\Freedom Scientific\Runtime JAWS\18.0\jrt.exe" Dir: "C:\Users\xxxx\Desktop\GitHub\S2-EnvPrepTool\bin\Debug\Extracted Assets"
The second line in the logs is a value dump from the path and dir variables.
When I run the code without the while loop and add the path and dir in manually e.g.
File.Copy(#"C:\foo\bar\hello.txt", #"C:\hello\world", true);
or
File.Copy("C:\\foo\\bar\\hello.txt", "C:\\hello\\world", true);
It works fine.
I will also attatch the filter method incase you guys want to see it. Keep in mind this is quick and dirty so yeah:
public string QuoteEncapsulationFilter(string s)
{
s = s.Replace("\n", String.Empty);
s = s.Replace("\r", String.Empty);
s = s.Replace("\t", String.Empty);
s = s.Replace("\\", "\\\\");
if (!s.Contains("\""))
{
s = "\"" + s + "\"";
}
return s;
}
I have tried looking for an answer everywhere with no luck can someone please shed some light on what I am doing wrong here please. If you need me to provide anymore information please let me know.
Thanks!
You're missing the file name within the File.Copy (string sourceFileName, string destFileName, bool overwrite); function. Your dir path needs the file name.
https://learn.microsoft.com/en-us/dotnet/api/system.io.file.copy?view=netframework-4.7.2
says the following:
destFileName
String The name of the destination file. This cannot be a
directory.
Edit:
To answer your second question in the comments:
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
while (run)
{
foreach (string path in paths)
{
Thread.Sleep(100);
try
{
var fileName = Path.GetFileName(path); // Get the file name
var fullDestination = dir + fileName; // Complete the uri
File.Copy(path, fullDestination, true);
}
catch (Exception e)
{
g.log.WriteToLog("Failed to copy asset: " + e.ToString());
}
}
};
}).Start();
I know this question has been asked many times, but I have almost tried everything but got no luck. So please help.
Some global variables:
private string _DirectoryForLogin = "LoginFiles", _FileForLogin = "Login.startup";
private IsolatedStorageFile _GlobalAccessRight = IsolatedStorageFile.GetUserStoreForApplication();
_DirectoryForLogin stores the name of the directory that stores login info and _FileForLogin stores txt file saved as .startup(I do not think this is the problem).
Later after InitializeCompolnent() I am doing this:
using(_GlobalAccessRight){
//Checking if directory exists; case when user had already been logged in.
if (_GlobalAccessRight.DirectoryExists(_DirectoryForLogin))
{
//Check if login startup file is already created.
if (_GlobalAccessRight.FileExists(_DirectoryForLogin + "\\" + _FileForLogin))
{
//Not of much concern for you people.
_UserNameLabel.Text = "Please wait.... Logging in.";
_UserNameHolder.BorderThickness = new Thickness(0);
_UserNameHolder.IsEnabled = false;
_PasswordLabel.Text = "";
_PasswordHolder.BorderThickness = new Thickness(0);
_PasswordHolder.IsEnabled = false;
_LoginButton.BorderThickness = new Thickness(0);
_LoginButton.Content = "";
_LoginButton.IsEnabled = false;
}
else
_GlobalAccessRight.CreateFile(_DirectoryForLogin + "\\" + _FileForLogin);
//This part basically only creates the file, if directory was already existing.
}
else
{
//Creates directory as well as file.
_GlobalAccessRight.CreateDirectory(_DirectoryForLogin);
_GlobalAccessRight.CreateFile(_DirectoryForLogin + "\\" + _FileForLogin);
}
}
Things were working fine till now. Problem is that if I try to delete the directory anywhere after creating directory, it sends me an error: "Operation not permitted on IsolatedStorage." But I resolved it for time being by just closing my emulator all the time so that I get a fresh IsolatedStorage location.
Next I am taking input from the user in the text and password boxes, and writing them on a file above through this code:
using(_GlobalAccessRight){
//I have also tried by giving the FileShare argument below.
using(IsolatedStorageFileStream WritingStreamForLoginInfo = new IsolatedStorageFileStream(_DirectoryForLogin + "\\" + _FileForLogin, FileMode.OpenOrCreate, FileAccess.ReadWrite, _GlobalAccessRight)){
using(StreamWriter WritingLoginInfo = new StreamWriter(WritingStreamForLoginInfo)){
String _LoginCredentials = "UserName: ";
_LoginCredentials += _UserNameHolder.Text;
_LoginCredentials += "\n";
_LoginCredentials = "Password: ";
_LoginCredentials += _PasswordHolder.Password;
//I added this afterwards. Before it was not here. And still it was not working.
WritingLoginInfo.Flush();
WritingLoginInfo.Write(_LoginCredentials.ToCharArray(), 0, _LoginCredentials.Length);
WritingLoginInfo.Close();
WritingStreamForLoginInfo.Close();
}//End of 1st using clause
}//End of second using clause
}//End of third using clause
The above writing code is executed in the function that is executed on a Tap event of a Login button.
The code is sending this error:
Store must be open for this operation.
Exception detail:
System.ObjectDisposedException was unhandled
Message=Store must be open for this operation.
StackTrace:
at System.IO.IsolatedStorage.IsolatedStorageFileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, IsolatedStorageFile isf)
at System.IO.IsolatedStorage.IsolatedStorageFileStream..ctor(String path, FileMode mode, FileAccess access, IsolatedStorageFile isf)
at SafeMessenger.MainPage._LoginButton_Tap(Object sender, GestureEventArgs e)
at MS.Internal.CoreInvokeHandler.InvokeEventHandler(Int32 typeIndex, Delegate handlerDelegate, Object sender, Object args)
at MS.Internal.JoltHelper.FireEvent(IntPtr unmanagedObj, IntPtr unmanagedObjArgs, Int32 argsTypeIndex, Int32 actualArgsTypeIndex, String eventName)
Now one last thing that I should tell you people. Before I was doing like, creating a separate IsolatedStorageFile object for each thing. That is in above code, and in directory creation code. At that time I was getting this error:
"Operation not permitted on IsolatedStorage."
Right now I tried to go through the debug details of various variables and I found this which could be of some help for you people. Remember _GlobalAccessRight is a global private variable of IsolatedStorageFile type.
Quota 'this._GlobalAccessRight.Quota' threw an exception of type 'System.ObjectDisposedException' long {System.ObjectDisposedException}
Kindly help me with this. Thank you for your time and any help, in advance. It would be a great help.
So after reading and thinking a lot I have finally got my code working on my own.
First the errors that operation not permitted were raised because of my bad handling of streams.
First I changed these lines:
.
.
.
}
else
_GlobalAccessRight.CreateFile(_DirectoryForLogin + "\\" + _FileForLogin);
//This part basically only creates the file, if directory was already existing.
}
else
{
//Creates directory as well as file.
_GlobalAccessRight.CreateDirectory(_DirectoryForLogin);
_GlobalAccessRight.CreateFile(_DirectoryForLogin + "\\" + _FileForLogin);
}
To:
.
.
.
}
else
_GlobalAccessRight.CreateFile(_DirectoryForLogin + "\\" + _FileForLogin).Dispose();
//This part basically only creates the file, if directory was already existing.
}
else
{
//Creates directory as well as file.
_GlobalAccessRight.CreateDirectory(_DirectoryForLogin);
_GlobalAccessRight.CreateFile(_DirectoryForLogin + "\\" + _FileForLogin).Dispose();
}
The second mistake was this:
I globally declared the IsolatedStorageFile object. After using it in my First using block:
using(_GlobalAccessRight){
//Checking if directory exists; case when user had already been logged in.
if (_GlobalAccessRight.DirectoryExists(_DirectoryForLogin))
{
.
.
.
.
It got disposed at the end of it.
Therefore when I used it again in the function:
using(_GlobalAccessRight){
//I have also tried by giving the FileShare argument below.
using(IsolatedStorageFileStream WritingStreamForLoginInfo = new IsolatedStorageFileStream(_DirectoryForLogin + "\\" + _FileForLogin, FileMode.OpenOrCreate, FileAccess.ReadWrite, _GlobalAccessRight)){
using(StreamWriter WritingLoginInfo = new StreamWriter(WritingStreamForLoginInfo)){
String _LoginCredentials = "UserName: ";
.
.
.
it threw me error stating:
"Store must be open for that."
So I changed it to be this:
using(_GlobalAccessRight = IsolatedStorageFile.GetUserStoreForApplication()){
using(IsolatedStorageFileStream WritingStreamForLoginInfo = new IsolatedStorageFileStream(_DirectoryForLogin + "\\" + _FileForLogin, FileMode.OpenOrCreate, FileAccess.ReadWrite, _GlobalAccessRight)){
using(StreamWriter WritingLoginInfo = new StreamWriter(WritingStreamForLoginInfo)){
String _LoginCredentials = "UserName: ";
Thats it! Bravo! :)
Try to remove these two lines:
WritingLoginInfo.Close();
WritingStreamForLoginInfo.Close();
using block supposed to open and close/dispose stream automatically. Maybe the error "Store must be open for this operation" caused by the code trying to close already closed connection at the end of using block.
I have a windows service (C# .Net 3.5) that grabs data from a network share and does a copy to the host of the service.
The size of the data copied ranges from 50KB to 750MB, and the number of files copied varies. In perhaps 20% of the copies I am getting System.IO.IOException: The specified network name is no longer available.
My google-fu is failing to turn up an answer as to what might cause this during a File.Copy. Has anyone seen/solved this before?
Here is the recursive method that does the copy. The exception occurs on line File.Copy(fromFile, toFile, overwrite);
private static int RecursiveCopyDirectory(string from, string to, bool merge, bool overwrite, int depth)
{
depth++;
if (!from.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
to += Path.DirectorySeparatorChar;
}
if (!to.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
to += Path.DirectorySeparatorChar;
}
System.Diagnostics.Debug.WriteLine(string.Format("RecursiveDirectoryCopy( {0}, {1}, {2} )", from, to, merge));
if (Directory.Exists(to))
{
if (!merge)
{
return (int)EventEnum.FileSystemError_DirectoryAlreadyExists;
}
}
else
{
Directory.CreateDirectory(to);
}
string[] directories = Directory.GetDirectories(from);
foreach (string fromDirectory in directories)
{
string [] fromDirectoryComponents = fromDirectory.Split(Path.DirectorySeparatorChar);
string toDirectory = to + fromDirectoryComponents[fromDirectoryComponents.Length - 1];
RecursiveCopyDirectory(fromDirectory, toDirectory, merge, overwrite, depth);
}
string[] files = Directory.GetFiles(from);
foreach (string fromFile in files)
{
string fileName = Path.GetFileName(fromFile);
//System.Diagnostics.Debug.WriteLine(string.Format("Name: {0}", to + fileName));
string toFile = to + fileName;
File.Copy(fromFile, toFile, overwrite);
}
return (int)EventEnum.GeneralSuccess;
}
File.Copy() opens up underline streams. You might have lost connection while File.Copy() is in progress. So, it can't flush and close the stream.
One possibility to recover from this, is to use the FileStream class and
call Win32 API CloseHandle when such exception occurs, doing so will release
the OS file handle so you can re-open the file.
[ DllImport("Kernel32") ]
public static extern bool CloseHandle(IntPtr handle);
FileStream fs;
try {
...
}
catch(IOException)
{
// If resource no longer available, or unable to write to.....
if(...)
CloseHandle(fs.Handle);
}
Also, MSDN recommends not to rely on overwrite. Try deleting existing file and creating new one when copying them.
File.Copy(..., ..., TRUE) does not work properly.
Be very careful with this method, as the Overwrite = True does NOT work properly.
I had an existing destination file that had some information inside it that was somehow preserved and carried over to the source file that was supposed to copy over it. This should be impossible, but I confirmed it for myself.
The error seems to indicate that the network connection is lost partway through and probably isn't to do with the code at all. If the same folder copy succeeds sometimes and fails other times then this would back up that it's not the code to blame and must be a resource access issue.
It turns out that the customer that was using this software was running two instances of it simultaneously, against the same data set. Once the redundant instance was stopped it resolved the error. Thanks everyone who answered.
We had a rare exception occur when reading the standard .Net user settings (this are the ones found in "project properties" in VS 2008):
System.Configuration.ConfigurationErrorsException was caught
Message="Configuration system failed to initialize"
Source="System.Configuration"
BareMessage="Configuration system failed to initialize"
Line=0
StackTrace:
at System.Configuration.ConfigurationManager.PrepareConfigSystem()
at System.Configuration.ConfigurationManager.GetSection(String sectionName)
at System.Configuration.PrivilegedConfigurationManager.GetSection(String sectionName)
at System.Diagnostics.DiagnosticsConfiguration.GetConfigSection()
at System.Diagnostics.DiagnosticsConfiguration.Initialize()
at System.Diagnostics.DiagnosticsConfiguration.get_IndentSize()
at System.Diagnostics.TraceInternal.InitializeSettings()
at System.Diagnostics.TraceInternal.get_Listeners()
InnerException: System.Configuration.ConfigurationErrorsException
Message="Unexpected end of file has occurred. The following elements are not closed: setting, SettingsTest.Properties.Settings, userSettings, configuration. Line 7, position 1. (C:\\Documents and Settings\\USER\\Local Settings\\Application Data\\Hitcents\\SettingsTest.vshost.exe_Url_ghwhc20utv4toanuinmj0pfsljthcugo\\1.0.0.0\\user.config line 7)"
Source="System.Configuration"
BareMessage="Unexpected end of file has occurred. The following elements are not closed: setting, SettingsTest.Properties.Settings, userSettings, configuration. Line 7, position 1."
Filename="C:\\Documents and Settings\\USER\\Local Settings\\Application Data\\Hitcents\\SettingsTest.vshost.exe_Url_ghwhc20utv4toanuinmj0pfsljthcugo\\1.0.0.0\\user.config"
Line=7
StackTrace:
at System.Configuration.ConfigurationSchemaErrors.ThrowIfErrors(Boolean ignoreLocal)
at System.Configuration.BaseConfigurationRecord.ThrowIfParseErrors(ConfigurationSchemaErrors schemaErrors)
at System.Configuration.BaseConfigurationRecord.ThrowIfInitErrors()
at System.Configuration.ClientConfigurationSystem.OnConfigRemoved(Object sender, InternalConfigEventArgs e)
InnerException: System.Xml.XmlException
Message="Unexpected end of file has occurred. The following elements are not closed: setting, SettingsTest.Properties.Settings, userSettings, configuration. Line 7, position 1."
Source="System.Xml"
LineNumber=7
LinePosition=1
SourceUri=""
StackTrace:
at System.Xml.XmlTextReaderImpl.Throw(Exception e)
at System.Xml.XmlTextReaderImpl.Throw(String res, String arg)
at System.Xml.XmlTextReaderImpl.Throw(Int32 pos, String res, String arg)
at System.Xml.XmlTextReaderImpl.ThrowUnclosedElements()
at System.Xml.XmlTextReaderImpl.ParseElementContent()
at System.Xml.XmlTextReaderImpl.Read()
at System.Xml.XmlTextReader.Read()
at System.Xml.XmlTextReaderImpl.Skip()
at System.Xml.XmlTextReader.Skip()
at System.Configuration.XmlUtil.StrictSkipToNextElement(ExceptionAction action)
at System.Configuration.BaseConfigurationRecord.ScanSectionsRecursive(XmlUtil xmlUtil, String parentConfigKey, Boolean inLocation, String locationSubPath, OverrideModeSetting overrideMode, Boolean skipInChildApps)
at System.Configuration.BaseConfigurationRecord.ScanSectionsRecursive(XmlUtil xmlUtil, String parentConfigKey, Boolean inLocation, String locationSubPath, OverrideModeSetting overrideMode, Boolean skipInChildApps)
at System.Configuration.BaseConfigurationRecord.ScanSections(XmlUtil xmlUtil)
at System.Configuration.BaseConfigurationRecord.InitConfigFromFile()
InnerException:
*NOTE: this is re-created from a test app.
I pulled up the user.config file, and half of it was missing.
I expect our application was terminated abruptly for some reason or another.
This seems very rare, here is how we interact with the settings:
//How we read
Settings settings = Settings.Default;
_ourStaticMemberVariable = settings.OurValue;
//How we save
Settings settings = Settings.Default;
settings.OurValue = "Our Value";
settings.Save();
Is there anything wrong with how we're using it? Both calls have a try-catch that place some default values, but the values need to be able to reset from our application.
When in this state, our application cannot save new settings--and I cannot figure out a good way to programmatically recover. I had to manually find the user.config and delete it.
I also tried calling Settings.Reset(), etc. but get the same exception.
Any ideas on how to fix this? Or are we better off writing our own settings system or saving persistent settings in another way?
EDIT: A workaround is to delete the file from code, if you get a ConfigurationErrorsException.
Anyone know how to get the full path of the user.config file?
Here's a solution that does not require you to exit the application with kudos to Jarle (http://www.codeproject.com/Articles/30216/Handling-Corrupt-user-config-Settings?msg=3608682#xx3608682xx). Early on, before Settings ever gets called, use this
public static bool CheckSettings()
{
var isReset = false;
try
{
ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal);
}
catch (ConfigurationErrorsException ex)
{
string filename = string.Empty;
if (!string.IsNullOrEmpty(ex.Filename))
{
filename = ex.Filename;
}
else
{
var innerEx = ex.InnerException as ConfigurationErrorsException;
if (innerEx != null && !string.IsNullOrEmpty(innerEx.Filename))
{
filename = innerEx.Filename;
}
}
if (!string.IsNullOrEmpty(filename))
{
if (System.IO.File.Exists(filename))
{
var fileInfo = new System.IO.FileInfo(filename);
var watcher
= new System.IO.FileSystemWatcher(fileInfo.Directory.FullName, fileInfo.Name);
System.IO.File.Delete(filename);
isReset = true;
if (System.IO.File.Exists(filename))
{
watcher.WaitForChanged(System.IO.WatcherChangeTypes.Deleted);
}
}
}
}
return isReset;
}
Essentially, rather than relying on Sittings to throw the error, read the file with the ConfigurationManager, that way the system's version never gets into a bad state.
The way to programmatically recover is to do what you did manually - delete the user settings file. Then call Settings.Reset. (You could also write a new user settings file with default values instead of deleting it, but if you're using the configuration manager properly that's essentially the same thing.)
This is a pretty rare occurrence, but it's not totally unheard of. Not only can your program crash while writing the user settings file, the file itself is user-writeable, so other programs the user runs could mess with it.
To avoid this particular vulnerability, persist user settings in a durable store with transactional integrity, i.e. a database. (You'll still have vulnerabilities, just not this one.) That's a lot of work for what in most cases will be a marginal improvement in reliability. But "in most cases" doesn't mean "in all cases;" yours may warrant it.
[STAThread]
private static void Main(string[] args)
{
try
{
// ...
}
catch (System.Configuration.ConfigurationErrorsException ex)
{
var config = ((System.Configuration.ConfigurationErrorsException)ex.InnerException).Filename;
// notify user, ask them to restart
System.IO.File.Delete(config);
Application.Exit();
}
}