Search code examples
c#.netsystem.io.directory

Directory.CreateDirectory fails with invalid character


I am facing issue that my path string passes check for Path.GetInvalidPathChars() but fails when trying to create directory.

static void Main(string[] args)
{

    string str2 = @"C:\Temp\hjk&(*&ghj\config\";

    foreach (var character in System.IO.Path.GetInvalidPathChars())
    {
         if (str2.IndexOf(character) > -1)
         {

             Console.WriteLine("String contains invalid path character '{0}'", character);
             return;
         }
    }


    Directory.CreateDirectory(str2); //<-- Throws exception saying Invalid character.

    Console.WriteLine("Press any key..");
    Console.ReadKey();
}

Any idea what could be the issue?


Solution

  • This is one of those times where slight issues in the wording of the documentation can make all the difference on how we look at or use the API. In our case, that part of the API doesn't do us much good.


    You haven't completely read the documentation on Path.GetInvalidPathChars():

    The array returned from this method is not guaranteed to contain the complete set of characters that are invalid in file and directory names. The full set of invalid characters can vary by file system. For example, on Windows-based desktop platforms, invalid path characters might include ASCII/Unicode characters 1 through 31, as well as quote ("), less than (<), greater than (>), pipe (|), backspace (\b), null (\0) and tab (\t).

    And don't think that Path.GetInvalidFileNameChars() will do you any better immediately (we'll prove how this is the better choice below):

    The array returned from this method is not guaranteed to contain the complete set of characters that are invalid in file and directory names. The full set of invalid characters can vary by file system. For example, on Windows-based desktop platforms, invalid path characters might include ASCII/Unicode characters 1 through 31, as well as quote ("), less than (<), greater than (>), pipe (|), backspace (\b), null (\0) and tab (\t).


    In this situation, it's best to try { Directory.CreateDirectory(str2); } catch (ArgumentException e) { /* Most likely the path was invalid */ } instead of manually validating the path*. This will work independent of file-system.


    When I tried to create your directory on my Windows system:

    Invalid Path Characters

    Now if we go through all the characters in that array:

    foreach (char c in Path.GetInvalidPathChars())
    {
        Console.WriteLine($"0x{(int)c:X4} : {c}");
    }
    

    We get:

    0x0022 : "
    0x003C : <
    0x003E : >
    0x007C : |
    0x0000 : 
    0x0001 : 
    0x0002 : 
    0x0003 : 
    0x0004 : 
    0x0005 : 
    0x0006 : 
    0x0007 : 
    0x0008 : 
    0x0009 :   
    0x000A : 
    
    0x000B : 
    0x000C : 
    0x000D : 
    0x000E : 
    0x000F : 
    0x0010 : 
    0x0011 : 
    0x0012 : 
    0x0013 : 
    0x0014 : 
    0x0015 : 
    0x0016 : 
    0x0017 : 
    0x0018 : 
    0x0019 : 
    0x001A : 
    0x001B : 
    0x001C : 
    0x001D : 
    0x001E : 
    0x001F : 
    

    As you can see, that list is incomplete.

    However: if we do the same for GetInvalidFileNameChars()

    foreach (char c in Path.GetInvalidFileNameChars())
    {
        Console.WriteLine($"0x{(int)c:X4} : {c}");
    }
    

    We end up with a different list, which includes all of the above, as well as:

    0x003A : :
    0x002A : *
    0x003F : ?
    0x005C : \
    0x002F : /
    

    Which is exactly what our error-message indicates. In this situation, you may decide you want to use that instead. Just remember our warning above, Microsoft makes no guarantees as to the accuracy of either of these methods.

    Of course, this isn't perfect, because using Path.GetInvalidFileNameChars() on a path will throw a false invalidation (\ is invalid in a filename, but it's perfectly valid in a path!), so you'll need to correct for that. You can do so by ignoring (at the very least) the following characters:

    0x003A : :
    0x005C : \
    

    You may also want to ignore the following character (as sometimes people use the web/*nix style paths):

    0x002F : /
    

    The last thing to do here is demonstrate a slightly easier way of writing this code. (I'm a regular on Code Review so it's second nature.)

    We can do this whole thing in one expresion:

    System.IO.Path.GetInvalidFileNameChars().Except(new char[] { '/', '\\', ':' }).Count(c => str2.Contains(c)) > 0
    

    Example of usage:

    var invalidPath = @"C:\Temp\hjk&(*&ghj\config\";
    var validPath = @"C:\Temp\hjk&(&ghj\config\"; // No asterisk (*)
    
    var invalidPathChars = System.IO.Path.GetInvalidFileNameChars().Except(new char[] { '/', '\\', ':' });
    
    if (invalidPathChars.Count(c => invalidPath.Contains(c)) > 0)
    {
        Console.WriteLine("Invalid character found.");
    }
    else
    {
        Console.WriteLine("Free and clear.");
    }
    
    if (invalidPathChars.Count(c => validPath.Contains(c)) > 0)
    {
        Console.WriteLine("Invalid character found.");
    }
    else
    {
        Console.WriteLine("Free and clear.");
    }
    

    *: This is arguable, you may want to manually validate the path if you are certain your validation code will not invalidate valid paths. As MikeT said: "you should always try to validate before getting an exception". Your validation code should be equal or less restrictive than the next level of validation.