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.
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;
}
}
The title says it all.
What is the best way to determine if the PC has been rebooted since my program's last run?
Context:
C# - Win XP and on
A piece of hardware (PCIe board) that I'm configuring requires the settings to only be sent once per power cycle but users may close and open the program multiple times before power cycling the PC.
I'm thinking I need some sort of a global reference that my program starts/sets while starting then it can check if said global reference is running/true at each start up and act accordingly.
Ideas? Suggestions?
See How to know when was Windows started or shutdown on how to get the last boot time.
You can write the boot time to a file. When you start your program you can check if the saved value match the current value or not, and update the file with the new value if needed.
See also Getting last reboot time
As user Panagiotis Kanavos commented, an event log entry is written when Windows boots.
If you have a look in Event Viewer in the System log, you will find those entries have Event ID == 12, which is accessed in code by using the InstanceId property of an EventLogEntry (the EventID property is deprecated).
So you can get all those log entries, sort them, and get the latest one, like this:
using System;
using System.Diagnostics;
using System.Linq;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
EventLog systemLog = new EventLog("System");
var bootEntry = systemLog?.Entries.Cast<EventLogEntry>().
Where(x => x.InstanceId == 12).
OrderByDescending(x => x.TimeGenerated).
FirstOrDefault();
if (bootEntry != null)
{
Console.WriteLine("Last boot: " + bootEntry.TimeGenerated.ToString("yyyy-MM-dd HH:mm:ss"));
}
else
{
Console.WriteLine("Could not open System log or no boot event found.");
}
Console.ReadLine();
}
}
}
(Tested as working on Windows 10 20H2.)
Now, my computer has a fast SSD, and my experience of reading the event logs from an HDD is that that can be sloooooow, so you might want to get the entry in some background task.
It looks possible to look up the boot time in the Windows Event Log. I haven't verified this, but maybe this can work for you.
Manually, you can use the Event Viewer to find the PC's boot time. This will get you the information you need to make the programmatics call to get the relevant data. You'd do something like this (not a complete solution):
var logs = EventLog.GetEventLogs();
// not sure about this; you may need to change the search below...
var log = logs.Single(l => l.LogDisplayName == "Application and Services Log");
// search entries
var bootEntries = log.Entries.Where(x => ...);
...
This uses these APIs
EventLog class
EventLog.GetEventLogs() method
EventLog.Entries property
EventLogEntry class
So, it looks like it's possible to drill down to the entry you need, grab out the timestamp and do your various checks. Good luck!
Save the last boot time (e.g. in user.config) and then compare it with the current value. Environment.TickCount64 keeps track of the time since the system started.
var storedLastBootTime = LoadBootTimeFromSettings();
var currentTime = DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond;
var lastBootTime = currentTime - Environment.TickCount64;
if (lastBootTime > storedLastBootTime)
{
// A system reboot must have taken place!
}
SaveBootTimeToSettings(lastBootTime);
If the saved value is greater, a restart must have taken place. To handle that you need to save the current time too.
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 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();
New to C# Compact edition 6.5. I am trying to set the datetime on a file which seems to be off by 5 hours from the actual system time. I am doing only this to create the file:
FileStream fs= File.Create(name);
Just doing this the Created date is 5 hours ahead...if I try and set the CreationTime I get a compile error saying the Attribute is Readonly, seriously?
FileInfo fi = new FileInfo(name);
fi.CreationTime = date;
So my question is since I am new to C# how do you get access to a "readonly" Attribute in the CE framework? I see mentioning of P/Invoke but seems to work on methods only and not attributes. Anyone can given a quick demo on how to do this?
I've tried this solution and still get the file writing UTC even though I send it the current local time
I just ran this:
[MTAThread]
static void Main()
{
var name = "\\foo.txt";
var info = new FileInfo(name);
using (info.Create()) { }
info.Refresh();
var createTime = info.CreationTime;
var now = DateTime.Now;
var delta = now - createTime;
Debug.WriteLine(delta.ToString());
}
And got this output:
00:00:00.0140000
Which seems to be correct to me.
You can't modify the CreationTime of a file. It's set once and only once when the file is created. If you're willing to use P/Invoke to set the time, you can check out this similar question - c# - Change file LastWriteDate in Compact Framework
Instead of hacking the problem, though, you should fix the root cause. If there's an issue with the creation time of the file, I would consider checking your system's time settings (including timezone).