views:

779

answers:

4

On a Vista machine with the valid path C:\Users\David, calling Directory.GetFiles(@"C:\Users\David") throws the following ArgumentException when run as the David user, who can view the contents of the directory just fine in Windows Explorer:

System.ArgumentException message: Illegal characters in path.
Argument: ""
Stack trace:
   at System.IO.Path.CheckInvalidPathChars(String path)
   at System.IO.Path.InternalCombine(String path1, String path2)
   at System.IO.Directory.InternalGetFileDirectoryNames(String path, String userPathOriginal, String searchPattern, Boolean includeFiles, Boolean includeDirs, SearchOption searchOption)
   at System.IO.Directory.GetFiles(String path, String searchPattern, SearchOption searchOption)
   at System.IO.Directory.GetFiles(String path)
   at Microsoft.Samples.XFileExplorer.ContentView.CreateContentDataTable(String CurrentFolder) in C:\Users\david\Downloads\MEF Preview 5\MEF Preview 5\Samples\XFileExplorer\XFileExplorer\ContentView.xaml.cs:line 108

The Vista machine happens to have been accessed by a Mac running MacFuse, so the directory contains a file that looks like it is named "._Icon" but must really contain some illegal characters. I believe this is the source of the error. I am left with the problem of what to do when Directory.GetFiles() throws an exception when it runs across a file name it does not like? Are there any alternate ways of listing a files contents that do not through such exception?

As for this particular file, I suspect the file name must contain some characters not displayed by Windows Explorer or the command-prompt:

   C:\Users\david>dir ._Icon
   Volume in drive C is Bootcamp
   Volume Serial Number is XXXX-XXX

   Directory of C:\Users\david

   File Not Found

And finally:

   C:\Users\david>dir ._Icon*
   Volume in drive C is Bootcamp
   Volume Serial Number is XXXX-XXX
   Directory of C:\Users\david

   05/25/2008  07:40 AM            43,296 ._Icon
           1 File(s)         43,296 bytes
           0 Dir(s)  58,950,623,232 bytes free

Looking at the file across SMB, it looks like the file is actually named "._Icon?". Each time I try to remove the file from the Mac, the file seems to immediately reappear.

A: 

How about just deleting the file, or renaming it?

Seriously, does this happen often enough that it's worth you writing special-case code to "fix"?

John Saunders
The general case is that "somehow" an "illegal" file name gets created. I am looking for a general solution as to how to query a directory's contents when it contains illegal file names.Deleting or renaming the file from Windows is not possible due to the illegal file name:C:\Users\david>del ._Icon?C:\Users\david\._IconThe filename, directory name, or volume label syntax is incorrect.
flipdoubt
I heard you. I'm asking you whether it's worthwhile to solve the general case, as opposed to fixing the specific case by deleting the file. Is it going to take you more time to solve the general case (and maintain the code), than to delete or rename all of the files that will cause this problem over the lifetime of the code? If Macs are creating such files daily, then solve the general case. If this happens once a year, you likely have better things to do with your time.
John Saunders
+1  A: 

Can you try listing the file using PInvoke FindFirstFile- see here. Does is cause simmillar issues?

The file information will be returned in the WIN32 FIND DATA struct. Then you call FindNextFile for each file until it doesn't return 0. In the struct you can use cFileName member get the file name. Check to see what's invalid in there.

My point is that this may return the fileinformation that Directory.GetFiles spews on.

here is a sample of its use:

public const int MAX_PATH = 260;
 public const int MAX_ALTERNATE = 14;

[StructLayout(LayoutKind.Sequential)]
    public struct FILETIME {
    public uint dwLowDateTime;
    public uint dwHighDateTime;
 }; 

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] 
public struct WIN32_FIND_DATA {
    public FileAttributes dwFileAttributes;
    public FILETIME ftCreationTime; 
    public FILETIME ftLastAccessTime; 
    public FILETIME ftLastWriteTime; 
    public int nFileSizeHigh;
    public int nFileSizeLow;
    public int dwReserved0;
    public int dwReserved1;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=MAX_PATH)] 
    public string cFileName; 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=MAX_ALTERNATE)] 
    public string cAlternate; 
}

[DllImport("kernel32", CharSet=CharSet.Unicode)] 
public static extern IntPtr FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData);

[DllImport("kernel32", CharSet=CharSet.Unicode)] 
public static extern bool FindNextFile(IntPtr hFindFile, out WIN32_FIND_DATA lpFindFileData);

private long RecurseDirectory(string directory, int level, out int files, out int folders) {
    IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
    long size = 0;
    files = 0;
    folders = 0;
    Kernel32.WIN32_FIND_DATA findData;

    IntPtr findHandle;

    // please note that the following line won't work if you try this on a network folder, like \\Machine\C$
    // simply remove the \\?\ part in this case or use \\?\UNC\ prefix
    findHandle = Kernel32.FindFirstFile(@"\\?\" + directory + @"\*", out findData);
    if (findHandle != INVALID_HANDLE_VALUE) {

        do {
            if ((findData.dwFileAttributes & FileAttributes.Directory) != 0) {

                if (findData.cFileName != "." && findData.cFileName != "..") {
                    folders++;

                    int subfiles, subfolders;
                    string subdirectory = directory + (directory.EndsWith(@"\") ? "" : @"\") + 
                        findData.cFileName;
                    if (level != 0)  // allows -1 to do complete search.
                        {
                    size += RecurseDirectory(subdirectory, level - 1, out subfiles, out subfolders);

                    folders += subfolders;
                    files += subfiles;
                    }
                }
            }
            else {
                // File
                files++;

                size += (long)findData.nFileSizeLow + (long)findData.nFileSizeHigh * 4294967296;
            }
        } 
        while (Kernel32.FindNextFile(findHandle, out findData));
        Kernel32.FindClose(findHandle);

    }

    return size;
}

// [Sample by Kåre Smith] // [Minor edits by Mike Liddell]
Preet Sangha
I'm liking this example: http://www.dotnet247.com/247reference/msgs/22/112844.aspx
flipdoubt
So ... I can call the method without a problem but how do I get at the results when it returns an int? How can I use that int to list all the actual file names?
flipdoubt
+1  A: 

Just an FYI... what you are seeing is a Resource Fork. These are far different from windows resources and are roughly analogus to NTFS alternate data streams.

http://en.wikipedia.org/wiki/Resource_fork

The resource fork is implemented in all of the filesystems used for system drives on the Macintosh (MFS, HFS and HFS Plus). The presence of a resource fork makes it easy to store a variety of additional information, such as allowing the system to display the correct icon for a file and open it without the need for a file extension in the file name. While access to the data fork works like file access on any other operating system — pick a file, pick a byte offset, read some data — access to the resource fork works more like extracting structured records from a database.

From the Microsoft sysinternals site:

The NTFS file system provides applications the ability to create alternate data streams of information. By default, all data is stored in a file's main unnamed data stream, but by using the syntax 'file:stream', you are able to read and write to alternates. Not all applications are written to access alternate streams, but you can demonstrate streams very simply. First, change to a directory on a NTFS drive from within a command prompt. Next, type 'echo hello > test:stream'. You've just created a stream named 'stream' that is associated with the file 'test'. Note that when you look at the size of test it is reported as 0, and the file looks empty when opened in any text editor. To see your stream enter 'more < test:stream' (the type command doesn't accept stream syntax so you have to use more).

mpjda