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
Related
I use Directory.Exist to test a directory, but I can not distinguish types of errors.
I would like to distinguish if the directory doesn't exist or access is not allowed. I saw this link C# Test if user has write access to a folder but it's quite long and complicated. There is not an easier way?
You can use the Exist method to check whether the directory exists or not. Like this
if(Directory.Exists(directory)) {
// directory exists
try
{
// and is accessible by user...
File.GetAccessControl(filePath);
return true;
}
catch (UnauthorizedAccessException)
{
// but is unable to be accessed...
return false;
}
} else {
// directory doesn't exist, so no file accessible.
}
This is a bit easy and understand code for you. Each of the method is having its own commentary for you.
I'm trying to detect if the directory exists, but in this particular situation my directory is a network location.
I used VB.NET's My.Computer.FileSystem.DirectoryExists(PATH) and the more general System.IO.Directory.Exists(PATH), and in both cases, the system response is false.
I checked and the PATH exists, I can view it in MyComputer Folder.
If I debug the program, and watch the My.Computer.FileSystem.Drives variable, the network location does not appear in that list.
UPDATE: I checked and in Windows XP the Response is True, but not in Windows 7.
UPDATE2: I tested both proposed solutions but I still have the same problem, on the image below you will see that I can access using Explorer but my program cannot.
The GetUNCPath function returns a valid path (no errors), but Directory.Exists stil returns false.
I also tried with the UNC path "\\Server\Images"; same result.
UPDATE3: If I cannot link with a network drive, how can I link to UNC path directly?. I discovered that if i run VS in normal mode, it works, but my software must run in administrator mode. So, there is any way to check the existence of a network directory as administrator?
If UAC is turned on, mapped network drives only exist "by default" in the session they are mapped: Normal or elevated. If you map a network drive from explorer, then run VS as admin, the drive will not be there.
You need to enable what MS calls "linked connections":
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System: EnableLinkedConnections (REG_DWORD) = 0x1
Background Information about "two logon sessions" with UAC: http://support.microsoft.com/kb/937624/en-us
When you use System.IO.Directory.Exists, it only lets you know that it couldn't find the directory, but this could be because the directory doesn't actually exist or because the user doesn't have sufficient access rights to the directory.
In order to resolve this, we add a secondary test after Directory.Exists fails to obtain the real reason for the directory's absence and we have wrapped this into a global method that is used in place of the standard Directory.Exists method:
''' <summary>
''' This method tests to ensure that a directory actually does exist. If it does not, the reason for its
''' absence will attempt to be determined and returned. The standard Directory.Exists does not raise
''' any exceptions, which makes it impossible to determine why the request fails.
''' </summary>
''' <param name="sDirectory"></param>
''' <param name="sError"></param>
''' <param name="fActuallyDoesntExist">This is set to true when an error is not encountered while trying to verify the directory's existence. This means that
''' we have access to the location the directory is supposed to be, but it simply doesn't exist. If this is false and the directory doesn't exist, then
''' this means that an error, such as a security error, was encountered while trying to verify the directory's existence.</param>
Public Function DirectoryExists(ByVal sDirectory As String, ByRef sError As String, Optional ByRef fActuallyDoesntExist As Boolean = False) As Boolean
' Exceptions are partially handled by the caller
If Not IO.Directory.Exists(sDirectory) Then
Try
Dim dtCreated As Date
' Attempt to retrieve the creation time for the directory.
' This will usually throw an exception with the complaint (such as user logon failure)
dtCreated = Directory.GetCreationTime(sDirectory)
' Indicate that the directory really doesn't exist
fActuallyDoesntExist = True
' If an exception does not get thrown, the time that is returned is actually for the parent directory,
' so there is no issue accessing the folder, it just doesn't exist.
sError = "The directory does not exist"
Catch theException As Exception
' Let the caller know the error that was encountered
sError = theException.Message
End Try
Return False
Else
Return True
End If
End Function
public static class MappedDriveResolver
{
[DllImport("mpr.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int WNetGetConnection([MarshalAs(UnmanagedType.LPTStr)] string localName, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder remoteName, ref int length);
public static string GetUNCPath(string originalPath)
{
StringBuilder sb = new StringBuilder(512);
int size = sb.Capacity;
// look for the {LETTER}: combination ...
if (originalPath.Length > 2 && originalPath[1] == ':')
{
// don't use char.IsLetter here - as that can be misleading
// the only valid drive letters are a-z && A-Z.
char c = originalPath[0];
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
{
int error = WNetGetConnection(originalPath.Substring(0, 2), sb, ref size);
if (error == 0)
{
DirectoryInfo dir = new DirectoryInfo(originalPath);
string path = Path.GetFullPath(originalPath).Substring(Path.GetPathRoot(originalPath).Length);
return Path.Combine(sb.ToString().TrimEnd(), path);
}
}
}
return originalPath;
}
}
To use it, pass in a Network folder path, convert to UNC folder path and see if the folder exists:
File.Exists(MappedDriveResolver.GetUNCPath(filePath));
Edit:
I saw your second edit and the only difference (in my Windows7) when I view a network drive I see Computer > Images (\\xyzServer). Is your PC's name Equipo? is that team in spanish? is that your PC? I tried to reproduce your problem but it works for me:
Adding to this, I needed to do an 'Exists' check on network shares that could listed, but the account did not have permission to access, so Directory.Exists would return False.
Various solutions posted did not work for me, so here is my own:
public static bool DirectoryVisible(string path)
{
try
{
Directory.GetAccessControl(path);
return true;
}
catch (UnauthorizedAccessException)
{
return true;
}
catch
{
return false;
}
}
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.
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.
In my C# .NET 2.0 application I'm accessing network paths, and I'd like to be able to tell the difference between paths that don't exist, and paths which do exist but for which I don't have access rights. I tried doing the following:
try
{
string[] contents = Directory.GetFileSystemEntries( path );
}
catch( Exception e )
{
if( e is DirectoryNotFoundException )
MessageBox.Show( "Path not found" );
else
MessageBox.Show( "Access denied" );
}
This works fine for local paths, but for network paths, the exception is always System.IO.IOException, regardless of the reason for the error. The exception Message field shows a different message depending on whether the path exists or not, so clearly the information is available at some point, but I can't get to it. Is there a way to differentiate between "path not found" and "access denied" for network paths?
Edit: So, in case anyone else wants the quick solution to this, as suggested by henrik, and incorporating peSHIr's advice, here is what you can do:
try
{
// Issue this call just to find out whether the path exists
// We don't care about the result
string[] contents = Directory.GetFileSystemEntries( path );
// If we get this far then the path exists.
}
catch( IOException e )
{
uint error = (uint)Marshal.GetHRForException( e );
if( error == (uint)0x80070041 ) // ERROR_NETWORK_ACCESS_DENIED
{
// The poor deluded user doesn't have access rights
this.SuperProprietaryTechniqueForGettingAccessRights();
}
else
{
// Hah! The idiot user specified a path that doesn't exist!
// Chastise them severely, like all good GUI should.
MessageBox.Show( "NO! BAD USER!" );
}
}
catch
{
// Swallow all other types of exception - we only made the call
// to find out whether the path exists.
}
First, I would not catch (Exception), but do something like this:
try {
string[] contents = Directory.GetFileSystemEntries(path);
}
catch(DirectoryNotFoundException)
{
MessageBox.Show("Path not found");
}
catch(IOException)
{
MessageBox.Show("Could not access path");
}
But the real question is: why would you actually need to know the difference, if all you do is show a message box to the user with a generic error message?
Also, unfortunately, the path may exist and you don't have access, and the result is that the path appears non-existent to your account. Many companies modify the special privs on a folder to remove "intermediary" folders from visibility, but they are still present.
For example, consider the path \srv\test1\test2\test3\
If you modify the advanced security for the folder named "test2" above, you can prevent users from navigating into it. Attempts to open the folder will result in a not found exception, and viewing the parent folder "test1" will not show the folder as present. However, if you provide the full path above and have sufficient privileges on the folder named "test3" then the folder will open.
Just something to consider, for all intents and purposes the folder doesn't exist for the user, however, if you offer the ability for a user to specify a full path then you should keep the above in mind (e.g. allow users to reach paths by full name, because it's possible they lack privs to view/list/read/see an intermediary, and the error returned doesn't indicate that it's a security restriction, so preventing access to the full path because of an intermediary appeared non-existent coudl be considered bugged app logic.)
Hope that helps.
You can call Marshal.GetHRForException to get more detailed information as in:
if (Marshal.GetHRForException(e) == unchecked((int)0x800704cf)) // ERROR_NETWORK_UNREACHABLE
See WinError.h for error codes.