C# System.IO.File.Copy issue - c#

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();

Related

How to dispose/release file being use by another process?

Using filesystemwatcher in the changes event i'm using fileinfo to get the file and then copy the file to a new directory and keep copying the file with overwrite when copy until the file changes end :
private void Watcher_Changes(object sender, FileSystemEventArgs e)
{
try
{
var info = new FileInfo(e.FullPath);
var newSize = info.Length;
string FileN1 = "File Name : ";
string FileN2 = info.Name;
string FileN3 = " Size Changed : From ";
string FileN5 = "To";
string FileN6 = newSize.ToString();
Println(FileN1 + FileN2 + FileN3 + FileN5 + FileN6);
CopyFileOnChanged(System.IO.Path.GetDirectoryName(e.FullPath), e.FullPath);
}
catch (Exception ex)
{
PrintErr(ex);
}
}
And the copy file method :
bool makeonce = false;
string NewFileName = "";
private void CopyFileOnChanged(string Folder, string FileName)
{
if (makeonce == false)
{
string t = "";
string fn = "";
string locationToCreateFolder = Folder;
string folderName;
string date = DateTime.Now.ToString("ddd MM.dd.yyyy");
string time = DateTime.Now.ToString("HH.mm tt");
string format = "Save Game {0} {1}";
folderName = string.Format(format, date, time);
Directory.CreateDirectory(locationToCreateFolder + "\\" + folderName);
t = locationToCreateFolder + "\\" + folderName;
fn = System.IO.Path.GetFileName(FileName);
NewFileName = System.IO.Path.Combine(t, fn);
makeonce = true;
}
File.Copy(FileName, NewFileName, true);
}
The problem is when it's making the File.Copy over again it's throwing exception the file is being using by other process.
[+] File Name : New Text Document (2).txt Size Changed : From To662 At
: 6/3/2022 3:56:14 PM [+] File Name : New Text Document (2).txt Size
Changed : From To662 At : 6/3/2022 3:56:14 PM [-]
System.IO.IOException: The process cannot access the file 'C:\Program
Files (x86)\Win\Save Game Fri 06.03.2022 15.56 PM\New Text Document
(2).txt' because it is being used by another process. at
System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.File.InternalCopy(String sourceFileName, String
destFileName, Boolean overwrite, Boolean checkHost) at
System.IO.File.Copy(String sourceFileName, String destFileName,
Boolean overwrite) at
Watcher_WPF.MainWindow.CopyFileOnChanged(String Folder, String
FileName) in
C:\Users\Chocolade1972\Downloads\Watcher_WPF-master\Watcher_WPF-master\Watcher_WPF\MainWindow.xaml.cs:line
356 at Watcher_WPF.MainWindow.Watcher_Changes(Object sender,
FileSystemEventArgs e) in
C:\Users\Chocolade1972\Downloads\Watcher_WPF-master\Watcher_WPF-master\Watcher_WPF\MainWindow.xaml.cs:line
258
Line 258 is :
CopyFileOnChanged(System.IO.Path.GetDirectoryName(e.FullPath), e.FullPath);
For brevity I will only outline the solution I created in a professional setting for Invoice processing instead of give you the complete solution (I also cannot, because the code is copyrighted).
So that out of the way, here we go:
What I had first was an "Inbox" Folder, I had a FileSystemWatcher watch. I reacted to new files, but that works quite the same for file changed. For each event, I enqueued an Item:
private ConcurrentQueue<string> _queue = new ();
private void Watcher_Changes(object sender, FileSystemEventArgs e)
{
_queue.Enqueue(e.FullPath);
}
That's all the EventHandler did. Objective here is to handle events from the FSW as quickly as any possible. Otherwise you may run into exhaustion and the FSW will discard events! (Yes, I learned it the hard way. Through bug reports and a lot of sweat :D)
The actual work was done in a separate thread, that consumed the Queue.
// Just brief display of the concept.
// This function would be used as Thread run every
// x Time, triggered by a Timer if the Thread is not still running.
private void MyWorkerRun()
{
// My Input came in mostly in batches, so I ran until the queue was empty.
// You may need to adapt to maybe only dequeue N Items for each run ...
// Whatever does the trick.
// while( _queue.Any() )
//
// Maybe only process the N amount of Items the Queue has at the
// start of the current run?
var itemsToProcess = _queue.Count;
if( itemsToProcess <= 0 ) return;
for( int i = 0; i < itemsToProcess; i++)
{
string sourcePath = _queue.Dequeue(); // ConcurrentQueue is Thread-Safe
// No file there anymore? Drop it.
if(!File.Exists(sourcePath)) continue;
// TODO Construct Target-Path
string targetPath = GetTargetPath(sourcePath); // Just a dummy for this example...
// Try to copy, requeue if failed.
if(!TryCopy(sourcePath, targetPath))
{
// Requeue for later
// It will be picked up in _next_ run,
// so there should be enough time in between tries.
_queue.Enqueue(sourcePath);
}
}
}
private bool TryCopy(string source, string target){ /* TODO for OP */ }
I have to add that I did this years ago. Today I would probably consider TPL DataFlow to handle the queueing and requeuing for me.
And of course, you can always spice this up. I tried to keep it as simple as possible, while showing the concept clearly.
I later had more requirements: For example, the program should be able to be exited and pick up from where it stopped when started again. It should only retry for X times then write the file into a "deadletterbox", then more processing steps were added, then it should send an email to a certain adress if the queue exceeded N entries ... you get it. You can always make it more complicated if you need to.
t = locationToCreateFolder + "\\" + folderName;
your "locationToCreateFolder" is a directory name and not a path. be cause it comes from here :
CopyFileOnChanged(System.IO.Path.GetDirectoryName(e.FullPath), e.FullPath);
so when you Combine the global path is not valid :
NewFileName = System.IO.Path.Combine(t, fn);

Access files from Network shared folder

I am accessing one folder with csv file under it by c# code. I have network path like "\NL0000NAS0007.dir.xyz.com\webtest\SERVERS\NLWSL086\personnel\people\PROD". While calling with below code it is appending "c:" earlier to this url so I am not able to get the files.
Below is my code snippet.
{
try
{
WriteLogFile.WriteLog(this.Configuration.GetValue<string>("logFile"), "Copy CSV File to Server", MessageType.Info);
//string projectPath = Directory.GetParent(Directory.GetCurrentDirectory()).Parent.FullName;
string projectPath = #"D:\Paracomcsv\";
string folderName = Path.Combine(projectPath, "CsvFiles_" + DateTime.Now.ToString("MM_dd_yyyy"));
string[] files = Directory.GetFiles(sourcePath);
if (!Directory.Exists(folderName))
{
Directory.CreateDirectory(folderName);
}
if (files.Length > 0)
{
// 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.
var fileName = Path.GetFileName(s);
var destFile = Path.Combine(folderName, fileName);
System.IO.File.Copy(s, destFile, true);
}
}
else
{
WriteLogFile.WriteLog(this.Configuration.GetValue<string>("logFile"), "File Doesn't Exist", MessageType.Error);
}
}
catch (Exception ex)
{
WriteLogFile.WriteLog(this.Configuration.GetValue<string>("logFile"), ex.Message, MessageType.Error);
throw ex;
}
}
I am getting the error while calling Directory.GetFiles. Anyone had the same issue, if yes please let me know how to call remote network shared file.
Thanks
The code snippet cant be the full source code. It is unclear how the variables are initialised. But given your network path of "\NL0000NAS0007.dir.xyz.com\webtest\SERVERS\NLWSL086\personnel\people\PROD" it is very clear, that it doesnt function. The path starts with a single \ character, so the System.IO API assumes you mean a relative path to whatever is the current directory or drive . And this could be "C:"...

System.IO.File.Move error - Could not find a part of the path

I have a sync software, which loads CSV files from "Incoming" folder, processes them and then moves them to the "Archive" folder.
Today, I saw the following error with this sync software:
[23/06/2014 00:06:04 AM] : Failed to move file from
D:\IBI_ORDER_IMPORTER_FTP_SERVER\Template3\Fifty &
Dean\Incoming\5A040K___d6f1ca45937b4ceb98d29d0db4601bf4.csv to
D:\IBI_ORDER_IMPORTER_FTP_SERVER\Template3\Fifty &
Dean\Archive\5A040K___d6f1ca45937b4ceb98d29d0db4601bf4.csv - Could not
find a part of the path.
Here's a snippet taken out of the sync software, where the file is processed and moved:
public static void ProcessSingleUserFile(Int32 TemplateId, String ImportedBy, String FilePath)
{
// Always Rename File To Avoid Conflict
string FileName = Path.GetFileNameWithoutExtension(FilePath);
String NewFilePath = FilePath.Replace(FileName, Utils.RandomString() + "___" + FileName);
File.Move(FilePath, NewFilePath);
FilePath = NewFilePath;
// Log
SyncUtils.ConsoleLog(String.Format("Processing [ {0} as {1} ] By [ {2} ] On Template [ #{3} ]",
FileName + ".csv",
Path.GetFileName(FilePath),
ImportedBy,
TemplateId));
// Init
List<OrderDraft> myOrderDrafts = new List<OrderDraft>();
// Parsed Based On Template Id
if (TemplateId == Settings.Default.Multi_Order_Template_Id)
{
// Try Parse File
myOrderDrafts = Utils.ParseMultiImportFile(TemplateId, ImportedBy, FilePath, true);
}
else
{
// Try Parse File
myOrderDrafts.Add(Utils.ParseImportFile(TemplateId, ImportedBy, FilePath, true));
}
// Process Orders
foreach (OrderDraft myOrderDraft in myOrderDrafts)
{
/* code snipped */
}
// Archive File
File.Move(FilePath, FilePath.Replace("Incoming", "Archive"));
}
Any idea what this error means? and how to circumvent it?
I wrote a cut down version of the above to test this in a controlled environment and I am not getting the error with this code:
static void Main(string[] args)
{
try
{
string baseDir = #"C:\Users\Administrator\Desktop\FTP_SERVER\Template3\Fifty & Dean\Incoming\";
string[] filePaths = Directory.GetFiles(baseDir, "*.csv");
foreach (string filePath in filePaths)
{
// do some work here ...
// move file
string newFilePath = filePath.Replace("Incoming", "Archive");
File.Move(filePath, newFilePath);
Console.WriteLine("File successfully moved");
}
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.Message);
}
Console.ReadKey();
}
You need to include the checks to make sure that the paths exist at runtime and check the output, something very simple like:
if(!Directory.Exists(Path.GetDirectoryName(filePath)))
{
Console.WriteLine("filePath does not exist: " + filePath);
}
if(!Directory.Exists(Path.GetDirectoryName(newFilePath)))
{
Console.WriteLine("newFilePath does not exist: " + newFilePath);
}
File.Move(filePath, newFilePath);
The reason I am suggesting this method is due to a possibility that the paths momentarily become available or not under the multi-tasking OSs depending on a multitude of factors: network connectivity, permissions (pushed down by GPO at any time), firewall rules, AV exclusions getting blown away etc. Even running low on CPU or RAM may create issues. In short, you never know what exactly occurred when your code was running if you are only checking the paths availability after the fact.
Or if your issue is intermittent, you can try and catch the error and write information to some sort of a log similarly to below:
try
{
File.Move(filePath, newFilePath);
}
catch(Exception ex)
{
if(!Directory.Exists(Path.GetDirectoryName(filePath)))
{
Console.WriteLine("filePath does not exist: " + filePath);
}
if(!Directory.Exists(Path.GetDirectoryName(newFilePath)))
{
Console.WriteLine("newFilePath does not exist: " + newFilePath);
}
}
"Could not find a part of the path" exception could also thrown from File.Move if argument used was longer than MAX_PATH (260) in .NET Framework.
So I prepend the path I used with long path syntax before passing to File.Move and it worked.
// Prepend long file path support
if( !packageFile.StartsWith( #"\\?\" ) )
packageFile = #"\\?\" + packageFile;
See:
How to deal with files with a name longer than 259 characters?
I have this
Could not find a part of the path
happened to me when I
File.Move(mapfile_path , Path.Combine(newPath, mapFile));
. After some testing, I find out our server administrator has blocked any user application from writing to that directory in that [newPath]!
So, right click on that directory to observe the rights matrix on Security tab to see anything that would block you.
Another cause of DirectoryNotFoundException "could not find a part of the path" thrown by File.Move can be spaces at the end of the directory name. Consider the following code:
string destinationPath = "C:\\Folder1 "; //note the space on the end
string destinationFileNameAndPath = Path.Combine(destinationPath, "file.txt");
if (!Directory.Exists(destinationPath))
Directory.CreateDirectory(destinationPath);
File.Move(sourceFileNameAndPath, destinationFileNameAndPath);
You may expect this code to succeed, since it creates the destination directory if it doesn't already exist, but Directory.CreateDirectory seems to trim the extra space on the end of the directory name, whereas File.Move does not and gives the above exception.
In this circumstance, you can workaround this by trimming the extra space yourself, e.g. (depending on how you load your path variable, the below is obviously overkill for a hard-coded string)
string destinationPath = "C:\\Folder1 ".Trim();

Renaming a File

Guys I am trying to remane a file (adding _DONE to its name)
my researches showed that File.move(OLDNAME,NEWNAME) is what I needed.
Thus,
I did,
try
{
string oldname = name;
//XYZ_ZXX_ZZZ
string newName = ToBeTested + "_DONE.wav";
//rename file
//NOTE : OldName is already in format XYZ_III_SSS.wav
File.Move(oldname, newName);
}
catch (Exception exRename)
{
string ex1 = exRename.ToString();
//logging an error
string m = "File renaming process failed.[" + ex1 + "]";
CreateLogFile(p, m);
}
But It does not bears any result (File is not renamed) but the exception is logged.
as such
System.IO.FileNotFoundException: Could not find file 'C:\Users\Yachna\documents\visual studio 2010\Projects\FolderMonitoringService_RCCM\FolderMonitoringService_RCCM\bin\Debug\54447874_59862356_10292013_153921_555_877_400_101.wav'.
File name: 'C:\Users\Yachna\documents\visual studio 2010\Projects\FolderMonitoringService_RCCM\FolderMonitoringService_RCCM\bin\Debug\54447874_59862356_10292013_153921_555_877_400_101.wav'
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.File.Move(String sourceFileName, String destFileName)
at RCCMFolderMonitor.Monitor.OnChanged(Object source, FileSystemEventArgs e) in C:\Users\Yachna\documents\visual studio 2010\Projects\FolderMonitoringService_RCCM\RCCMFolderMonitor\Monitor.cs:line 209]
What did i do wrong ?
I guess that the file does not exist in the same folder as the application.
You will have to include the path in addition to the filename.
File.Move(path + oldname, path + newName);
From the StackTrace it seems that you are trying to move/rename the file whilst you receive the OnChanged event of a FileSystemWatcher component. If this is true this means that another application is writing/changing the file that you are trying to move/rename.
This could result in the above error message. The file exists, but you cannot get access to it until the other application closes it.
Without including the path of your file, Visual Studio looks for the file in your Debug directory. This is the reason of the error.
You have to include the full path of your file using the method Path.Combine of System.IO namespace:
string myDirectory = #"C:\Files";
string myFileName = "myFile.wav";
string myNewFileName = "myFileNew.wav";
string myFileFullPath = Path.Combine(myDirectory, myFileName);
string myNewFileFullPath = Path.Combine(myDirectory, myNewFileName);
Console.WriteLine(myFileFullPath); // it writes to Console: C:\Files\myFile.wav
//Then you can rename the file
File.Move(myFileFullPath, myNewFileFullPath);

Get path to executable from command (as cmd does)

Given a command-line style path to a command such as bin/server.exe or ping, how can I get the full path to this executable (as cmd or Process.Start would resolve it)?
I tried Path.GetFullPath, but it always expands relative to the working directory. It expands bin/server.exe correctly, however given ping it returns c:\users\matt\ping (non-existent). I want c:\Windows\system32\ping.exe.
Edit: I would like the same behaviour as cmd. Some considerations:
When there is a local executable with the same name as one in the path, cmd prefers the local one
cmd can expand the command server to server.bat or server.exe (adding the file extension)
I also tried Windows' command-line tool called where . It does almost I want:
Displays the location of files that match the search pattern. By default, the search is done along the current directory and in the paths specified by the PATH environment variable.
>where ping
C:\Windows\System32\PING.EXE
>where bin\server
INFO: Could not find files for the given pattern(s).
(This question is hard to search around because of the two different meanings of the word 'path')
Considering PATHEXT too, stealing from Serj-Tm's answer (sorry! +1 to him):
public static string WhereSearch(string filename)
{
var paths = new[]{ Environment.CurrentDirectory }
.Concat(Environment.GetEnvironmentVariable("PATH").Split(';'));
var extensions = new[]{ String.Empty }
.Concat(Environment.GetEnvironmentVariable("PATHEXT").Split(';')
.Where(e => e.StartsWith(".")));
var combinations = paths.SelectMany(x => extensions,
(path, extension) => Path.Combine(path, filename + extension));
return combinations.FirstOrDefault(File.Exists);
}
Sorry the indentation's a bit all-over-the-place - I was trying to make it not scroll. I don't know if the StartsWith check is really necessary - I'm not sure how CMD copes with pathext entries without a leading dot.
public static string GetFullPath(string filename)
{
return new[]{Environment.CurrentDirectory}
.Concat(Environment.GetEnvironmentVariable("PATH").Split(';'))
.Select(dir => Path.Combine(dir, filename))
.FirstOrDefault(path => File.Exists(path));
}
If you're only interested in searching the current directory and the paths specified in the PATH environment variable, you can use this snippet:
public static string GetFullPath(string fileName)
{
if (File.Exists(fileName))
return Path.GetFullPath(fileName);
var values = Environment.GetEnvironmentVariable("PATH");
foreach (var path in values.Split(';'))
{
var fullPath = Path.Combine(path, fileName);
if (File.Exists(fullPath))
return fullPath;
}
return null;
}
You have to search the entire disk.
Windows can respond to things like, iexplore, ping, cmd, etc, because they are in the registry under this key:
HKEY_LOCAL_MACHINE
SOFTWARE
Microsoft
Windows
CurrentVersion
App Paths
The only other way is to search the entire disk for the application.
EDIT: My understanding was, that you want to search for any random executable name, not the ones that are already known to Windows..
internal class Program
{
static void Main(string[] args)
{
string fullPath = GetExactPathFromEnvironmentVar("ping.exe");
if (!string.IsNullOrWhiteSpace(fullPath))
Console.WriteLine(fullPath);
else
Console.WriteLine("Not found");
}
static string GetExactPathFromEnvironmentVar(string program)
{
var pathVar = System.Environment.GetEnvironmentVariable("PATH");
string[] folders = pathVar.Split(';');
foreach (var folder in folders)
{
string path = Path.Combine(folder, program);
if (File.Exists(path))
{
return path;
}
}
return null;
}
}
HTH

Categories