Update: I be glad to drop the C# requirement, and just see any program that can list all the files running as Admin or System, my question is has anyone seen such a thing?
There are numerous methods of enumerating files in a directory, but all suffer the same problems:
"The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters."
"Access to the path 'C:\Users\All Users\Application Data' is denied"
etc.
Even running under admin, single user machine, it seems impossible to list all the files without encountering exceptions\errors.
Is it really an impossible task just to get list of all the files under windows? Has anyone ever been able to obtain the complete list of all files on their machine using C# or any other method?
This link from MS with the title "Enumerate Directories and Files" , does not show how to Enumerate Directories and Files, it only show a subset of what that will not throw : DirectoryNotFoundException, UnauthorizedAccessException, PathTooLongException,
Update : Here is sample code to run over C and attempt to enumerate all the files and errors. Even when running this as admin there are folders that not only can be access, but I even can't change their ownership to Admin! for example : "C:\Windows\CSC"
just have look at "Errors {0}.csv" log file to see how many places are inaccessible to admin.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
class Program
{
static System.IO.StreamWriter logfile;
static System.IO.StreamWriter errorfile;
static void Main(string[] args)
{
string directory = #"C:\";
logfile = new System.IO.StreamWriter(string.Format(#"E:\Files {0}.csv", DateTime.Now.ToString("yyyyMMddHHmm")));
errorfile = new System.IO.StreamWriter(string.Format(#"E:\Errors {0}.csv", DateTime.Now.ToString("yyyyMMddHHmm")));
TraverseTree(directory, OnGotFileInfo, OnGotException);
logfile.Close();
errorfile.Close();
}
public static void OnGotFileInfo(System.IO.FileInfo fileInfo)
{
logfile.WriteLine("{0},{1},", fileInfo.FullName, fileInfo.Length.ToString("N0"));
}
public static void OnGotException(Exception ex, string info)
{
errorfile.WriteLine("{0},{1}", ex.Message, info);
}
public static void TraverseTree(string root, Action<System.IO.FileInfo> fileAction, Action<Exception, string> errorAction)
{
// Data structure to hold names of subfolders to be
// examined for files.
Stack<string> dirs = new Stack<string>(20);
if (!System.IO.Directory.Exists(root))
{
throw new ArgumentException();
}
dirs.Push(root);
while (dirs.Count > 0)
{
string currentDir = dirs.Pop();
string[] subDirs;
try
{
subDirs = System.IO.Directory.GetDirectories(currentDir);
}
// An UnauthorizedAccessException exception will be thrown if we do not have
// discovery permission on a folder or file. It may or may not be acceptable
// to ignore the exception and continue enumerating the remaining files and
// folders. It is also possible (but unlikely) that a DirectoryNotFound exception
// will be raised. This will happen if currentDir has been deleted by
// another application or thread after our call to Directory.Exists. The
// choice of which exceptions to catch depends entirely on the specific task
// you are intending to perform and also on how much you know with certainty
// about the systems on which this code will run.
catch (System.Exception e)
{
errorAction(e, currentDir);
continue;
}
string[] files = null;
try
{
files = System.IO.Directory.GetFiles(currentDir);
}
catch (System.Exception e)
{
errorAction(e, currentDir);
continue;
}
// Perform the required action on each file here.
// Modify this block to perform your required task.
foreach (string file in files)
{
try
{
// Perform whatever action is required in your scenario.
System.IO.FileInfo fi = new System.IO.FileInfo(file);
fileAction(fi);
}
catch (System.Exception e)
{
// If file was deleted by a separate application
// or thread since the call to TraverseTree()
// then just continue.
errorAction(e ,file);
continue;
}
}
// Push the subdirectories onto the stack for traversal.
// This could also be done before handing the files.
foreach (string str in subDirs)
dirs.Push(str);
}
}
}
Yes, it is at very least hard to enumerate all files without exceptions.
Several set of issues here:
some path (long ones- PathTooLongException) are not supported by CLR
security restrictions on folders/files
junctions/hard links that introduce duplicates (and in theory cycles to case StackOverflow in recursive iteration).
basic sharing violation restrictions (if you try to read files).
For PathTooLongException: I think you'll need to deal with PInvoke of corresponding Win32 functions. All path related methods in CLR are restricted to 256 characters long.
Security restrictions - you may be able to enumerate everything if you run under system (not sure) or with backup permissions, but any other account is guaranteed to not being able to access all files on system configured by default.
Instead of getting exceptions you can PInvoke native versions and handle error codes instead. You may be able to decrease number of exceptions on going into directories by checking ACL on the directly first.
Related
I want to check if a folder exists and if not then create it. But I don't know if the path supplied will even valid. When the path is not valid the following happens.
string path = "this is an invalid path";
if (!Directory.Exists(path))
Directory.CreateDirectory(path); //Exception thrown here
If you supply an invalid path, it will throw a DirectoryNotFoundException exception.
How can I stop this exception from occurring? I don't want to use a try-catch. I want to detect that this exception will occur even before the exception happens.
Use Directory.Exists method to check if a folder exists
if(Directory.Exists(path))
{
//Directory exists
}
else
{
// doesn't exist
}
Remember to include System.IO;
The explanation for the failure of your code is that the path is invalid. The documentation says:
DirectoryNotFoundException
The specified path is invalid (for example, it is on an
unmapped drive).
Trying to predict in advance whether or not a directory can be created is a devil of a job. You'd need to account for security, OS name rules and limits, file system name rules and limits, and whether or not drives are mapped. And probably lots more concerns. I would not contemplate re-implementing what the system provides for free.
In any case, whilst you can call Directory.Exists, you do still need to allow for an exception being thrown. If the file system changes between the call to Directory.Exists and the subsequent call to Directory.CreateDirectory, then an exception will be raised. For example, if another process creates the directory that you are trying to create. Granted, this is a rather unlikely event, but it's perfectly possible.
In summary, the best option, by a distance, is to catch the exception. As the well known saying goes, it's better to ask for forgiveness than to ask for permission.
Why don't you want to catch (specific) Exceptions? It is considered as a good practice... anyway, these are my solutions without try/catch:
solution:
string path = "C:\test";
if (!Directory.Exists(path) && path.IndexOfAny(Path.GetInvalidFileNameChars()) == -1)
{
Directory.CreateDirectory(path);
}
solution:
string path = "C:\test";
var canCreate = true;
foreach (var c in path.Where(Path.GetInvalidFileNameChars().Contains))
{
canCreate = false;
}
if (canCreate && !Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
solution:
if (path.Any(c => Path.GetInvalidFileNameChars().Contains(c)) && !Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
Please, be aware that this code can still fail... e.g. think about SecurityExeption (do you have the credentionals to create a directory there?!?
Also be aware there is still a (little) chance that the directory has been created (by another process/thread/...) after your test with Exists() but before your call of CreateDirectory(). These are two calls and they are not atomic (together) when querying/modifying the file system.
You can also use Path.GetInvalidFileNameChars() to check whether the directory name supplied is valid for a new directory name:
// Directory.Exists will return false because path is not even valid
var path = "1:1 comparison?";
var firstBad = Path.GetInvalidFileNameChars().Cast<char?>()
.FirstOrDefault(c => path.Contains(c.Value));
if (firstBad != null)
Console.WriteLine("Char '{0}' is invalid for a directory name", firstBad.Value);
try this
string path = "this is an invalid path";
if (Path.IsPathRooted(path)
{
Directory.CreateDirectory(path);
}
I think the simplest way to test if a path is valid and the file exists without raising an exception in C# is to call the unmanaged function PathFileExists directly in shlwapi.dll.
[DllImport("shlwapi.dll", EntryPoint = "PathFileExistsW", SetLastError = true, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool PathFileExists([MarshalAs(UnmanagedType.LPTStr)]string pszPath);
Note that as David writes in his answer, you might nevertheless get an exception when you try to create the directory.
You can use a single if-else statement
if(!Directory.Exists(path))
Directory.CreateDirectory(path)
else
//Show user a custom error message
I have an ASP.NET application which requires write access on the App_Data subfolder. The MSI used to deploy the application tries to set the permissions correctly, but in spite of this, it seems the permissions are sometimes wrong. Most of the application works fine without this permission. I would prefer that the application fails to start if the permissions are wrong.
What is the best practice for ensuring that the necessary permissions are correct for the IIS user context? Ideally I want to display some simple instructions for fixing whatever is wrong. And I want the message to appear in as many incorrect configurations as possible.
The following describes what I've tried so far, until I realised there's a probably a better or standard way.
I tried putting this in Application_Start()
protected void Application_Start(Object sender, EventArgs e)
{
// Assert permissions on writeable folders are correct
var permissionsChecker = new AppDataPermissionsChecker();
permissionsChecker.AssertFolderIsWriteable(
HttpContext.Current.Server.MapPath("~/App_Data"));
// remainder of Application_Start()...
}
where AppDataPermissionsChecker is defined as follows:
public class AppDataPermissionsChecker
{
private bool CanWriteAccessToFolder(string folderPath)
{
try
{
// Attempt to get a list of security permissions from the folder.
// This will raise an exception if the path is read only or do not have access to view the permissions.
DirectorySecurity directorySecurity = Directory.GetAccessControl(folderPath);
return true;
}
catch (UnauthorizedAccessException)
{
return false;
}
}
public void AssertFolderIsWriteable(string folderPath)
{
if (!Directory.Exists(folderPath))
throw new Exception(String.Format("The {0} folder does not exist.", folderPath));
if (!CanWriteAccessToFolder(folderPath))
throw new Exception(String.Format("The ASPNET user does not have "
+ "access to the {0} folder. Please ensure the ASPNET user has "
+ "read/write/delete access on the folder. See 'The App_Data folder' "
+ "here: http://msdn.microsoft.com/en-us/library/06t2w7da.aspx'",
folderPath));
}
}
I thought this would throw an ugly exception if the rights are incorrect (which is better than nothing), but in some situations I just get an HTTP Error 503.
I found this implementation of a diagnostics page which does exactly what I was looking for (and more besides).
Currently I have a program that searches a user set directory and sub-directories for music files and adds them to a collection. However if one of the directories it comes accross is protected then the program falls over. I wanted to know how I can check if the user has access to the directory before trying to search it to avoid this problem.
Below is the code I'm using for the search, it currently contains a basic work around for "System Volume Information" but as there is a possibility that there may be other protected directories I wanted to change this to include them.
public void SearchForMusic()
{
//Searches selected directory and its sub directories for music files and adds their path to ObservableCollection<string> MusicFound
foreach (string ext in extentions)
{
foreach (string song in Directory.GetFiles(SearchDirectory, ext))
{
musicFound.Add(song);
}
foreach (string directory in Directory.GetDirectories(SearchDirectory))
{
if (directory.Contains("System Volume Information"))
{
}
else
{
foreach (string song in Directory.GetFiles(directory, ext))
{
musicFound.Add(song);
}
foreach (string subDirectory in Directory.GetDirectories(directory))
{
foreach (string subSong in Directory.GetFiles(subDirectory, ext))
{
musicFound.Add(subSong);
}
}
}
}
}
}
Many thanks :)
By far the easiest way to be sure that you have access to a file system object is to attempt to access it. If it fails with an Access Denied error, then you don't have access. Just detect that error condition and proceed with the next item in the search.
In other words, delegate checking access to the system which is, after all, the ultimate arbiter of access rights.
You can check this question by replacing the Write with Read permissions. Also, wrap your code in a try catch block and if the exception is thrown, you can assume (or properly check the exception type to be sure) that the directory cannot be traversed.
I use VS 2008, .net 3.5, C# projects. I need do the same functionally like Microsoft.VisualBasic.FileIO.FileSystem.DeleteDirectory.
Anyone says referencing the Microsoft.VisualBasic is often undesirable from within C#. Any association with VB from within C# code strikes me as undesirable.
Using FileSystem class, this is a perfectly fine solution, but I prefer not references Microsoft.VisualBasic library. That one I would avoid.
private static void DeleteDirectory(string destino)
{
//UIOption Enumeration. Specifies whether to visually track the operation's progress. Default is UIOption.OnlyErrorDialogs. Required.
//RecycleOption Enumeration. Specifies whether or not the deleted file should be sent to the Recycle Bin. Default is RecycleOption.DeletePermanently.
//UICancelOption Enumeration. Specifies whether to throw an exception if the user clicks Cancel. Required.
Microsoft.VisualBasic.FileIO.FileSystem.DeleteDirectory(destino,
Microsoft.VisualBasic.FileIO.UIOption.OnlyErrorDialogs,
Microsoft.VisualBasic.FileIO.RecycleOption.DeletePermanently,
Microsoft.VisualBasic.FileIO.UICancelOption.ThrowException);
//Directory.Delete(destino, true);
}
Other samples:
How do you place a file in recycle bin instead of delete?
Microsoft.VisualBasic.FileIO.FileSystem.DeleteFile(file.FullName,
Microsoft.VisualBasic.FileIO.UIOption.OnlyErrorDialogs,
Microsoft.VisualBasic.FileIO.RecycleOption.SendToRecycleBin);
Possible duplicate of
System.IO Versus VisualBasic.FileIO
You can use FileIO from Microsoft.VisualBasic and AFAIK it will not behave unreasonably..
The same/similar functionality is available within the System.IO namespace:
System.IO.FileInfo fi = new System.IO.FileInfo("C:\\Test.txt");
fi.Delete();
System.IO.DirectoryInfo di = new System.IO.DirectoryInfo("C:\\Test");
di.Delete(true); //Recursive, pass false for no recursion.
I'm not aware of existing SendToRecycleBin equivalent, but you could try:
di.MoveTo("C:\\$Recycle.Bin\\S-..."); //You'd need to know the SID of the user logged in
To replicate the example
The following code will give you something similar to what you have provided as your example:
try
{
bool deletePermanently = true; //Set to false to move
System.IO.DirectoryInfo di = new System.IO.DirectoryInfo("C:\\Test");
if (deletePermanently)
{
if (di.Exists)
di.Delete(true);
}
else
{
if (di.Exists)
di.MoveTo("C:\\$Recycle.Bin\\S-0-0-00-00000000-000000000-0000000000-000"); //Replace with your SID
}
}
catch
{
Console.WriteLine("Error deleting directory"); //Add exception detail messages...
}
Again, the above example would need you to identify the SID of the user before being able to send to the recycle bin.
You could try the following.
System.IO.DirectoryInfo di = new System.IO.DirectoryInfo("C:\\MyDirectoryToDelete");
di.Delete(true);
Or even
System.IO.Directory.Delete("Path goes here");
Hope this helps.
I'm trying to create an application which scans a drive. The tricky part though, is that my drive contains a set of folders that have folders within folders and contain documents. I'm trying to scan the drive, take a "snapshot" of all documents & folders and dump into a .txt file.
The first time i run this app, the output will be a text file with all the folders & files.
The second time i run this application, it will take the 2 text files (the one produced from the 2nd time i run the app and the .txt file from the 1st time i have run the app) and compare them...reporting what has been moved/overridden/deleted.
Does anybody have any code for this? I'm a newbie at this C# stuff and any help would be greatly appreciated.
Thanks in advance.
One thing that we learned in the 80's was that if it's really tempting to use recursion for file system walking, but the moment you do that, someone will make a file system with nesting levels that will cause your stack to overflow. It's far better to use heap-based walking of the file system.
Here is a class I knocked together which does just that. It's not super pretty, but it does the job quite well:
using System;
using System.IO;
using System.Collections.Generic;
namespace DirectoryWalker
{
public class DirectoryWalker : IEnumerable<string>
{
private string _seedPath;
Func<string, bool> _directoryFilter, _fileFilter;
public DirectoryWalker(string seedPath) : this(seedPath, null, null)
{
}
public DirectoryWalker(string seedPath, Func<string, bool> directoryFilter, Func<string, bool> fileFilter)
{
if (seedPath == null)
throw new ArgumentNullException(seedPath);
_seedPath = seedPath;
_directoryFilter = directoryFilter;
_fileFilter = fileFilter;
}
public IEnumerator<string> GetEnumerator()
{
Queue<string> directories = new Queue<string>();
directories.Enqueue(_seedPath);
Queue<string> files = new Queue<string>();
while (files.Count > 0 || directories.Count > 0)
{
if (files.Count > 0)
{
yield return files.Dequeue();
}
if (directories.Count > 0)
{
string dir = directories.Dequeue();
string[] newDirectories = Directory.GetDirectories(dir);
string[] newFiles = Directory.GetFiles(dir);
foreach (string path in newDirectories)
{
if (_directoryFilter == null || _directoryFilter(path))
directories.Enqueue(path);
}
foreach (string path in newFiles)
{
if (_fileFilter == null || _fileFilter(path))
files.Enqueue(path);
}
}
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
Typical usage is this:
DirectoryWalker walker = new DirectoryWalker(#"C:\pathToSource\src", null, (x => x.EndsWith(".cs")));
foreach (string s in walker)
{
Console.WriteLine(s);
}
Which recursively lists all files that end in ".cs"
A better approach than your text file comparisons would be to use the FileSystemWatcher Class.
Listens to the file system change notifications and raises events when a directory, or file in a directory, changes.
You could log the changes and then generate your reports as needed from that log.
you can easily utilize the DirectoryInfo/FileInfo classes for this.
Basically instantiate an instance of the DirectoryInfo class, pointing towards the c:\ folder. Then using it's objects walk the folder structure.
http://msdn.microsoft.com/en-us/library/system.io.directoryinfo.aspx has code that could quite easily be translated.
Now, the other part of your question is insanity. You can find the differences between the two files relatively easily, but translating that into what has been moved/deleted/etc will take the some fairly advanced logic structures. After all, if I have two files, both named myfile.dat, and one is found at c:\foo and the other at c:\notfoo, how would the one at c:\notfoo be reported if I deleted the one at c:\foo? Another example, is if I have a file myfile2.dat and copy it from c:\bar to c:\notbar is that considered a move? What happens if I copy it on Tuesday, and then on Thursday I delete c:\bar\myfile2.dat--is that a move or a delete? And would the answer change if I ran the program on every Monday as opposed to daily?
There's a whole host of questions, and their corresponding logic structures which you'd need to think of amd code for in order to build that functionality, and even then, it would not be 100% correct, because it's not paging the file system as changes occur--there will always exist the possibility of a scenario that did not get reported correctly in your logic due to timing, logic structure, process time, when the app runs, or just due to the sheer perversity of computers.
Additionally, the processing time would grow exponentially with the size of your drive. After all, you'd need to check every file against every other file to determine it's state as opposed to its previous state. I'd hate to have to run this against my 600+GB drive at home, let alone the 40TB drives I have on servers at work.