I am currently working on a c# program where I check the creation time of a file and delete it if the file is older than 2 days. I have the following code snippet that should be achieving this.
DateTime creationTime = file.CreationTime.Date;
if (creationTime < DateTime.Now.AddDays(-logAge) && file.Name != currentLog)
{
File.Delete(string.Format("{0}/{1}", directory, file));
}
While my program is running it is constantly creating new files and a separate thread checks that the files are no older than say 2 days. If I have my PC's date set to the 24th April the files are created and kept as expected, if I then change the PC's date to the 25th April I would expect the files to remain as they are not older than 2 days, however, this is not the case as they are being deleted.
Log age is set to so I wouldn't have expected files to be deleted until after I had changed the date to be the 26th April.
What am I doing wrong, I've looked at many examples including another question on Stackoverflow Delete files older than 3 months old in a directory using .NET but its not doing what I would expect it to.
You forced to consider only the date part of the creation time-stamp then condition is satisfied and file will be deleted (earlier) anyway I suggest a few modifications to that code:
static class Helpers {
public static void DeleteOldFiles(string folderPath, uint maximumAgeInDays,
params string[] filesToExclude) {
DateTime minimumDate = DateTime.Now.AddDays(-maximumAgeInDays);
var filesToDelete = Directory.EnumerateFiles(folderPath)
.Where(x => !IsExcluded(x, filesToExclude));
foreach (var eligibleFileToDelete in filesToDelete)
DeleteFileIfOlderThan(eligibleFileToDelete, minimumDate);
}
private const int RetriesOnError = 3;
private const int DelayOnRetry = 1000;
private static bool IsExcluded(string item, string[] exclusions) {
return exclusions.Contains(item, StringComparer.CurrentCultureIgnoreCase);
}
private static void DeleteFileIfOlderThan(string path, DateTime date)
{
for (int i = 0; i < RetriesOnError; ++i) {
try {
var file = new FileInfo(path);
if (file.CreationTime < date)
file.Delete();
}
catch (IOException) {
System.Threading.Thread.Sleep(DelayOnRetry);
}
catch (UnauthorizedAccessException) {
System.Threading.Thread.Sleep(DelayOnRetry);
}
}
}
}
Notes
I'm still using DateTime.Now, I guess for this kind of operations you do not need any precision measurement (and you're talking about days so your thread may have a scheduled time of hours).
If your application uses multiple log files you can specify them all as parameters and they'll be ignored.
If you call DeleteOldFiles with 0 for maximumAgeInDays then you'll delay all log files not in use (as specified in the exclusion list).
Sometimes files can be in use (even if this should happen seldom in your case). The DeleteFileIfOlderThan function will retry to delete them after a short delay (it mimics Explorer.exe behavior).
You can call this function in this way:
Helpers.DeleteOldFiles(#"c:\mypath\", logAge, currentLog);
Few more notes:
This code doesn't combine path and file name but if you have to do it you should use Path.Combine(), I guess you do not want to reinvent the wheel each time to check if a path ends with a trailing backslash or not.
I/O operations can fail! Always check for exceptions.
file.Delete does make more sense than File.Delete(path) and Path.Combine() makes a lot more sense than using string.Format.
I've stumbled across this answer, don't know why I didn't find it before hand after spending ages on google, but this appears to have fixed the problem. DateTime.Compare how to check if a date is less than 30 days old?. The other problem was that I was using the file creation time but for my scenario it made more sense to use lastWriteTime.date.
I guess an additional problem must be in
File.Delete(string.Format("{0}/{1}", directory, file));
Your file is of type FileSystemInfo. Maybe you wanted to use file.Name.
Example: let's say directory is "c:\" and file points to "c:\myfile.log", your code will try to delete "c:/c:\myfile.log". It's hard for me to guess what exactly you have in these variables.
Correct replacement is suggested by #HenkHolterman:
file.Delete();
Related
everyone!
I do a small project for my company and I use C#. I have a script for my project. But before this day, my colleagues and I had an idea that the script would be used by users one by one. For example, if there are a user A and user B, there can be the order where the user B runs the script and only then the user A can run the script.
Today the decision was made to give the users the possibility to run the script freely without the predetermined order. And now I have some thoughts. Here the part of the script:
if (Directory.Exists(#"H:\" + doc_number + #"\detached") == false)
{
Directory.CreateDirectory(#"H:\" + doc_number + #"\detached");
File.WriteAllBytes(#"H:\" + doc_number + #"\detached\1.cms", signature_bytes);
}
else
{
string[] files = Directory.GetFiles(#"H:\" + doc_number + #"\detached"); int files_number = files.Length;
File.WriteAllBytes(#"H:\" + doc_number + #"\detached\" + Convert.ToString(files_number + 1) + ".cms", signature_bytes);
}
Firstly, there is a check of the existence of a directory. If it doesn't exist, the directory will be created and the first file will be added there. Otherwise, we just count the number of files in the directory and then create a new file with a name which is the number of the files in the folder plus one.
However, I'm thinking about the situation when the user A and the user B were at the beginning of this part of the script at the same time and the condition for both would be positive so it wouldn't be executed correctly. Or if one of them started running this part earlier but his or her PC was less powerful so while creating the directory another user would go through the condition, counting files and start creating a file before the first user which would be also incorrect.
I don't know how likely one of these situations are. if so, how can I solve it?
Indeed, you can run into concurrency issues. And you are correct that you can't rely on the existence of a directory to decide what branch to take in your if statement because you might have operations execute in this order:
User A: Checks for directory. Does not exist.
User B: Checks for directory. Does not exist.
User A: Creates directory, enters if branch.
User B: Creates directory, enters if branch.
If the code was running in one process on one machine but in multiple threads, you could use a lock statement.
If the code was running on different processes on the same machine, you could use a cross-process coordination method such as a Mutex.
The question implies that the code runs on different computers but accesses the same file system. In this case, a lock file is a common mechanism to coordinate access to a shared resource. In this approach, you would attempt to create a file and lock it. If that file already exists and is locked by another process, you know someone else got there first. Depending on your needs, a common scenario is to wait for the lock on the file to go away then acquire the lock yourself and continue.
This strategy also works for the other 2 cases above, though is less efficient.
For information about how to create a file with a lock, see
How to lock a file with C#?
There are some issues with your code. For example, what would happen if a file is deleted? The number of files in the directory would be different than the number of the last file, and you can end up trying to write a file that already exists. Also, please use Path.Combine to create paths, it is safer. You also don't need to check if the directory exists, since Directory.Create will do nothing if it already exists.
Common for all solutions bellow:
string baseDir = Path.Combine("H:",doc_number, "detached");
Directory.Create(baseDir);
If you just want any number of users to create files in the same directory, some solutions that are more safe:
Use a GUID:
var guid = Guid.NewGuid();
var file = Path.Combine(baseDir, $"{guid}.cms");
File.WriteAllBytes(file, signature_bytes);
Iterate, trying to create a new file:
bool created = false;
int index = 1;
while(!created)
{
//Check first if the file exists, and gets the next available index
var file = Path.Combine(baseDir, $"{index}.cms");
while(File.Exists(file))
{
file = Path.Combine(baseDir, $"{++index}.cms");
}
//Handle race conditions, if the file was created after we checked
try
{
//Try create the file, not allowing others to acess it while open
using var stream = File.Open(file,FileMode.CreateNew,FileAccess.Write,FileShare.None);
stream.Write(signature_bytes);
created = true;
}
catch (IOException) //If the file already exists, try the next index
{
++index;
}
}
Two functions in our standard ASP.NET app are:
private static void SaveToFileSystem(AttributeFileAttachment attach, int paId)
{
string fileName = GetAttachmentFullName(attach.FileName, paId);
File.WriteAllBytes(fileName, attach.Content);
}
public static string GetAttachmentFullName(string name, int paId)
{
HttpContext ctx = Util.Util.GetHttpContext();
return string.Format("{0}{1}_{2}_{3}",
ctx.Server.MapPath("<some variable to get the path>" + "attributeFileAttachments\\"),
ctx.Session.SessionID,
paId,
name);
}
when File.WriteAllBytes is executed it returns exception:
he process cannot access the file '\\d$\Home\\attributeFileAttachments\' because it is being used by another process.
The essence are two lines:
ctx.Server.MapPath... (Microsoft code)
and File.WriteAllBytes...
that work on the same file.
It turns out that HttpServerUtility.MapPath locks the file and leaves it locked !?
I don't see any comments on that in official documentation nor I see anybody complains on that.
But it can't be anything else, since the two lines are consecutive.
When I modify fileName for File.WriteAllBytes in immediate window just a bit, the writing succeeds, since that new file is not locked.
One more thing I have noticed is that this happens only and always for some of the attachment files.
Thank you for the time and any advice.
I need to get when a file was created - I have tried using:
FileInfo fi = new FileInfo(FilePath);
var creationTime = fi.CreationTimeUtc;
and
var creationTime = File.GetCreationTimeUtc(FilePath);
Both methods generally return the wrong creation time - I guess it is being cached somewhere.
The file is deleted and re-created with the same name and I need to know when/if it has been re-created (by checking if the created date/time has changed) - I had planned to do this by seeing it the file creation time had changed but I have found this to be inaccurate.
I'm working on Win 7 and if I check File Explorer it shows the new file creation time correctly.
I have also tried using the FileSystemWatcher but it doesn't entirely work for my use case. E.g. if my program is not running, the FileSystemWatcher is not running, so when my program starts up again I don't know if the file has been deleted and recreated or not.
I've seen MSDN http://msdn.microsoft.com/en-us/library/system.io.file.getcreationtime.aspx where it says:
This method may return an inaccurate value, because it uses native functions whose values may not be continuously updated by the operating system.
But I have also tried using their alternative suggestion and setting the SetCreationDate after creating a new file but I also find that this doesn't work. See test below:
[Test]
public void FileDateTimeCreatedTest()
{
var binPath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase);
var fullFilePath = Path.Combine(binPath, "Resources", "FileCreatedDatetimeTest.txt");
var fullFilePathUri = new Uri(fullFilePath);
var dateFormatted = "2013-08-17T15:31:29.0000000Z"; // this is a UTC string
DateTime expectedResult = DateTime.MinValue;
if (DateTime.TryParseExact(dateFormatted, "o", CultureInfo.InvariantCulture,
DateTimeStyles.AssumeUniversal, out expectedResult)) // we expect the saved datetime to be in UTC.
{
}
File.Create(fullFilePathUri.LocalPath);
Thread.Sleep(1000); // give the file creation a chance to release any lock
File.SetCreationTimeUtc(fullFilePathUri.LocalPath, expectedResult); // physically check what time this puts on the file. It should get the local time 16:31:29 local
Thread.Sleep(2000);
var actualUtcTimeFromFile = File.GetCreationTimeUtc(fullFilePathUri.LocalPath);
Assert.AreEqual(expectedResult.ToUniversalTime(), actualUtcTimeFromFile.ToUniversalTime());
// clean up
if (File.Exists(fullFilePathUri.LocalPath))
File.Delete(fullFilePathUri.LocalPath);
}
Any help much appreciated.
You need to use Refresh:
FileSystemInfo.Refresh takes a snapshot of the file from the current
file system. Refresh cannot correct the underlying file system even if
the file system returns incorrect or outdated information. This can
happen on platforms such as Windows 98.
Calls must be made to Refresh before attempting to get the attribute
information, or the information will be outdated.
The key bits from MSDN indicate that it takes a snapshot and attribute information..will be outdated.
Try using FileInfo and Refresh method of it
fileInfo.Refresh();
var created = fileInfo.CreationTime;
this should work
File.Create(fullFilePathUri.LocalPath);
Thread.Sleep(1000); // give the file creation a chance to release any lock
That is not how you do it. File.Create creates stream writer which should be closed to release the lock without any waiting. If you find yourself using Thread.Sleep, you will often find that you are doing something wrong.
If the file described in the path parameter does not exist, this method returns 12:00 midnight, January 1, 1601 A.D. (C.E.) Coordinated Universal Time (UTC), adjusted to local time.
https://learn.microsoft.com/en-us/dotnet/api/system.io.file.getcreationtime?view=netframework-4.8
I need some assistance changing a date range on line 13 of a file:
01/01/201101/31/2011
I plan on setting the script to run every day from the windows scheduler.
I would like the script to change the begining date -15 days from current date
and the ending date +15 days from the current date.
I found the DateAdd.cmd written by Rob van der Woude (http://www.robvanderwoude.com)
but I am not sure how to pass the values back to my main (calling) script?
Without any ~batch~ assistance, I did the following in C#:
static void Main(string[] args)
{
string inputFile = Path.Combine("C:/temp","textfile.txt");
string outputFile = Path.Combine("C:/temp","textfile2.txt");
using(StreamReader input = File.OpenText(inputFile))
using(Stream output = File.OpenWrite(outputFile))
using(StreamWriter writer = new StreamWriter(output))
{
int count = 1;
while(!input.EndOfStream)
{
// read line
string line = input.ReadLine();
// Get dates 15 days on either side of current date
if(count == 13)
{
DateTime beginRange = DateTime.Today.AddDays(-15);
DateTime endRange = DateTime.Today.AddDays( 15 );
string strBeginDate = beginRange.ToShortDateString();
string strEndDate = endRange.ToShortDateString();
// replace line with new date range
line = "0001" + strBeginDate + strEndDate + "Report submitted by";
}
// increment counter
count++;
// write the file to temp file
writer.WriteLine(line);
}
}
File.Delete(inputFile); // delete original file
File.Move(outputFile,inputFile); // rename temp file to original file name
The batch file language hasn't been significantly updated decades. You still can't do a conventional for loop. I suggest looking into PowerShell. It is just as powerful (if not more) than the *nix shell languages, but can also leverage the entire .NET framework. If you use PowerShell, this problem would be as simple as
Open file, go to line 13
Parse the line as two dates
Subtract 15 days from one date object and add 15 to the next
Write the file back out
The fact that you're trying to do real programming in a batch file is truly honorable (I would have commited Seppuku). Try to switch to a more powerful shell language that is more feature complete. Besides, PowerShell is the future of Windows scripting.
I found the DateAdd.cmd written by Rob van der Woude (http://www.robvanderwoude.com) but I am not sure how to pass the values back to my main (calling) script?
I would agree with others that you're better off using a different scripting language (VBS, PowerShell, ...), but to answer this specific question, the DateAdd.cmd batch file sets an environment variable DATEADD to the result of its deliberations.
You can do something like:
CALL DATEADD -15 >NUL:
SET FROMDATE=%DATEADD%
CALL DATEADD 15 >NUL:
SET TODATE=%DATEADD%
echo %FROMDATE%%TODATE%
Note that DateAdd.cmd uses the current user's short date format from the registry, so will give different results depending on the user's locale.
It appears that Directory.GetFiles() in C# modifies the Last access date of a file.
I've googled for hours and can't seem to find a work around for this issue. Is there anyway to keep all the MAC (Modified, Accessed, Created) attributes of a file?
I'm using Directory.GetDirectories(), Directory.GetFiles(), and FileInfo.
Also, the fi.LastAccessTime is giving strange results -- the date is correct, however, the time is off by 2 minutes, or a few hours.
Time of function execution: 10/31/2008 8:35 AM
Program Shows As Last Access Time
0_PDFIndex.html - 10/31/2008 8:17:24 AM
AdvancedArithmetic.pdf - 10/31/2008 8:31:05 AM
AdvancedControlStructures.pdf - 10/30/2008 1:18:00 PM
AoAIX.pdf - 10/30/2008 1:18:00 PM
AoATOC.pdf - 10/30/2008 12:29:51 PM
AoATOC2.pdf - 10/30/2008 1:18:00 PM
Actual Last Access Time
0_PDFIndex.html - 10/31/2008 8:17 AM
AdvancedArithmetic.pdf - 10/30/2008 12:29 PM
AdvancedControlStructures.pdf - 10/30/2008 12:29 PM
AoAIX.pdf - 10/30/2008 12:29 PM
AoATOC.pdf - 10/30/2008 12:29 PM
AoATOC2.pdf - 10/30/2008 12:29 PM
Below is the method I'm using. If you require more information, please let me know.
Thanks!
public void PopulateTreeView(string directoryValue, ref TreeNode parentNode)
{
string[] directoryArray = Directory.GetDirectories(directoryValue);
string[] fileArray = Directory.GetFiles(directoryValue, "*.*", SearchOption.AllDirectories);
try
{
#region Directories
if (directoryArray.Length != 0)
{
foreach (string directory in directoryArray)
{
DirectoryInfo di = new DirectoryInfo(directory);
TreeNode dirNode = parentNode.Nodes.Add(di.Name);
FileNode fn = new FileNode();
fn.bIsDir = true;
fn.dir = di;
dirNode.Tag = fn;
PopulateTreeView(directory, ref dirNode);
Application.DoEvents();
}
}
#endregion
#region Files
if (fileArray.Length != 0)
{
foreach (string file in fileArray)
{
FileInfo fi = new FileInfo(file);
TreeNode fileNode = parentNode.Nodes.Add(fi.Name);
FileNode fn = new FileNode();
fn.bIsDir = false;
fn.file = fi;
fileNode.Tag = fn;
fileNode.ImageIndex = 1;
Console.WriteLine(fi.Name + " - " + fi.LastAccessTime);
}
}
#endregion
}
catch (UnauthorizedAccessException)
{
parentNode.Nodes.Add("Access denied");
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
Application.DoEvents();
}
}
i know the differences between the attributes. What i need is for the file to remain exactly the same all attributes and meta-data, as if my program never touched the file; this includes the last access date.
I know this is far from ideal, but u can use fsutil (provided with Windows) to disable last access time writing:
fsutil behavior set disablelastaccess 1
Presumably you'd set it back to 0 once done. You can invoke this using Process.Start from C#, but there must be a better programmatic way (calling into Windows API).
Process.Start("fsutil", "behavior set disablelastaccess 1").WaitForExit();
Do note that this is a global Windows setting and would also affect disk access from outside your app...
Access times are different from last write times. If you use fi.LastWriteTime I think you will find that the times are the same displayed in explorer or cmd window.
Of course the last access and last write could be the same, but they are not necessarily the same.
(Reposting this as a response rather than a comment...)
I've just run this snippet of code here, and it's left the last access time alone - I can't reproduce the problem you're seeing, so Directory.GetFiles isn't broken 100% of the time.
Filemon can check whether some other app is doing this: http://technet.microsoft.com/en-us/sysinternals/bb896642.aspx
If you're doing forensics and you don't want the drive to be modified, why are you mounting it in a writable mode? You should be accessing it read-only to guarantee that you aren't accidentally changing something. Also, I would hope that you're not running your program in the OS of the person who's disk you're examining... you have just added the disk to a machine you control, right?
Not sure if this is related or not, but from MSDN:
When first called, FileSystemInfo
calls Refresh and returns the cached
information on APIs to get attributes
and so on. On subsequent calls, you
must call Refresh to get the latest
copy of the information.
BTW, "LastAccessTime" basically tells you the last time you "looked at" the file. In the absence of stale data, this would always be "now"... Not particularly useful, IMHO.
Access time would show a read only marker, last write would show the file being modified.
I haven't tried this, but Google suggests:
Disable the NTFS Last Access Time Stamp
It's a system-wide change, so be aware of that...
If you're accessing the disk for forensic purposes then you really should be doing it with the entire hard disk write-protected at the hardware level (and hence this isn't really a programming question).
A Google search for hdd "write protect" will reveal plenty of potential solutions.