i am trying to use the various file functions in C# like File.GetLastWriteTime, copy command on the file placed at the path greater than maximum allowed path on windows 7 i.e 260. Its giving me an error on long path name. On MSDN support i they have asked to use the \\?\ before the path. I did the same but still i got the same error, it seems it doesn't make any change. Below is my code. Please let me know if i am using it correct or i need to add any thing:
These all lib i am using as the code is having other things also:
the below is the respective code:
filesToBeCopied = Directory.GetFiles(path,"*",SearchOption.AllDirectories);
for (int j = 0; j < filesToBeCopied.Length; j++)
{
try
{
String filepath = #"\\?\" + filesToBeCopied[j];
File.GetLastWriteTime(filepath);
}
catch (Exception ex)
{
MessageBox.Show("Error Inside the single file iteration for the path:" +
filesToBeCopied[j] + " . The exception is :" + ex.Message);
}
}
where as path is the path to the folder at windows machine starting with drive letter. for ex.: d:\abc\bcd\cd\cdc\dc\..........
Here's a solution for at least the copying portion of your request (thank you pinvoke.net):
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
static extern bool CopyFile(string lpExistingFileName, string lpNewFileName, bool bFailIfExists);
And then to actually copy your file:
// Don't forget the '\\?\' for long paths
string reallyLongPath = #"\\?\d:\abc\bcd\cd\cdc\dc\..........";
string destination = #"C:\some\other\path\filename.txt";
CopyFile(reallyLongPath , destination, false);
As far as I know, you can't access a file directly if its path is too long (by directly, I mean using the methods of File, by creating a FileInfo via the constructor, or by using Directory.GetFiles(string fileName).
The only way I've found that will let you access such a file is to access a directory somewhere in the path before it gets too long, and then programatically walk down the tree until you get to your file, as seen here.
I've taken my code from there and modified it a little to return a FileInfo object for a file with a path that is "too long". Using this code, you can access the necessary properties on the returned FileInfo object (like LastWriteTime). It still has some limitations though, like the inability to use functions like CopyTo() or OpenText().
// Only call GetFileWithLongPath() if the path is too long
// ... otherwise, new FileInfo() is sufficient
private static FileInfo GetFile(string path)
{
if (path.Length >= MAX_FILE_PATH)
{
return GetFileWithLongPath(path);
}
else return new FileInfo(path);
}
static int MAX_FILE_PATH = 260;
static int MAX_DIR_PATH = 248;
private static FileInfo GetFileWithLongPath(string path)
{
string[] subpaths = path.Split('\\');
StringBuilder sbNewPath = new StringBuilder(subpaths[0]);
// Build longest sub-path that is less than MAX_PATH characters
for (int i = 1; i < subpaths.Length; i++)
{
if (sbNewPath.Length + subpaths[i].Length >= MAX_DIR_PATH)
{
subpaths = subpaths.Skip(i).ToArray();
break;
}
sbNewPath.Append("\\" + subpaths[i]);
}
DirectoryInfo dir = new DirectoryInfo(sbNewPath.ToString());
bool foundMatch = dir.Exists;
if (foundMatch)
{
// Make sure that all of the subdirectories in our path exist.
// Skip the last entry in subpaths, since it is our filename.
// If we try to specify the path in dir.GetDirectories(),
// We get a max path length error.
int i = 0;
while (i < subpaths.Length - 1 && foundMatch)
{
foundMatch = false;
foreach (DirectoryInfo subDir in dir.GetDirectories())
{
if (subDir.Name == subpaths[i])
{
// Move on to the next subDirectory
dir = subDir;
foundMatch = true;
break;
}
}
i++;
}
if (foundMatch)
{
// Now that we've gone through all of the subpaths, see if our file exists.
// Once again, If we try to specify the path in dir.GetFiles(),
// we get a max path length error.
foreach (FileInfo fi in dir.GetFiles())
{
if (fi.Name == subpaths[subpaths.Length - 1])
{
return fi;
}
}
}
}
// If we didn't find a match, return null;
return null;
}
Now that you've seen that, go rinse your eyes and shorten your paths.
try with this code
var path = Path.Combine(#"\\?\", filesToBeCopied[j]); //don't forget extension
"\?\" prefix to a path string tells the Windows APIs to disable all string parsing and to send the string that follows it straight to the file system.
Important : Not all file I/O APIs support "\?\", you should look at the reference topic for each API
http://www.codinghorror.com/blog/2006/11/filesystem-paths-how-long-is-too-long.html
I recently imported some source code for a customer that exceeded the maximum path limit of 256 characters.
The path you pasted was 285 characters long.
As you noted in your comment, MSDN's link here (http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx#maximum%5Fpath%5Flength) explains this length in greater detail:
In the Windows API (with some exceptions discussed in the following paragraphs), the maximum length for a path is MAX_PATH, which is defined as 260 characters. A local path is structured in the following order: drive letter, colon, backslash, name components separated by backslashes, and a terminating null character. For example, the maximum path on drive D is "D:\some 256-character path string" where "" represents the invisible terminating null character for the current system codepage. (The characters < > are used here for visual clarity and cannot be part of a valid path string.)
With respect to the \\?\ functionality:
Many but not all file I/O APIs support "\?\"; you should look at the reference topic for each API to be sure.
Related
I want to pass a list of directories to my C# console application as arguments which may be relative paths based on the current working directory, or contain drive letters and/or wildcards. E.g.:
myApp.exe someDir another\dir dirs\with\wild*cards* ..\..\a\relative\dir C:\an\absolute\dir\with\wild*cards*
So far I've come up with this:
static void Main(string[] args)
{
List<string> directories = new List<string>();
foreach (string arg in args)
{
if (Directory.Exists(arg))
{
// 'arg' by itself is a valid directory, and must not contain any wildcards
// just add it to 'directories'
directories.Add(arg);
}
else
{
// 'arg' must either be a non-existant directory or invalid directory name,
// or else it contains wildcard(s). Find all matching directory names, starting
// at the current directory (assuming 'arg' might be a relative path), and add
// all matching directory names to 'directories'.
string[] dirs = Directory.GetDirectories(Directory.GetCurrentDirectory(), arg, SearchOption.TopDirectoryOnly);
directories.AddRange(dirs);
}
}
Console.WriteLine("Full list of directories specified by the command line args:");
foreach (string dir in directories)
{
Console.WriteLine(" " + dir);
}
// Now go do what I want to do for each of these directories...
}
This works great for someDir, another\dir, and dirs\with\wild*cards*, but won't work for ..\..\a\relative\dir or C:\an\abolute\dir\with\wild*cards*. For the relative dir, Directory.GetDirectories() throws a System.ArgumentException saying "Search pattern cannot contain '..' to move up directories and can be contained only internally in file/directory names, as in 'a..b'." For the drive-letter-based directory, it throws a System.ArgumentException saying "Second path fragment must not be a drive or UNC name."
How can I handle the ".." and drive letters? This has to be a solved problem, but I can't find examples of such for C#.
"Bonus question": My above code also doesn't handle a\path\with\wild*cards*\in\anything\other\than\the\top\directory. Any easy way to handle that too?
Couple of observations:
1) To check if path absolute or relative - you can use Path.IsRooted()
2) To resolve path with ".." to absolute path (be it relative or absolute) you can use:
path = new Uri(Path.Combine(Directory.GetCurrentDirectory(), path)).AbsolutePath;
Routing it though Uri expands those dots. Path.GetFullPath will fail in case of wildcards, but Uri will not.
3) To check if path contains wildcards you can just do path.Contains("*") || path.Contains("?") since both of those characters are not valid path chars, so cannot be present in a context other than being wildcards.
4) To resolve wildcard in absolute path (and match your "bonus" requirement) you need to find out first directory which does not contain wildcard. So you basically need to split path into to parts - before first wildcard and after first wildcard. For example:
C:\an\abolute\dir\with\wild*cards*
Path before wildcard is C:\an\abolute\dir\with, after (and including) wildcard: wild*cards*.
C:\an\abolu*e\dir\with\wild*cards*
Path before first wildcard: C:\an, after: abolu*e\dir\with\wild*cards*. There are different ways to do that of course, easiest I think is regex:
#"[\\/](?=[^\\/]*[\*?])"
It basically matches directory separator, but only if it is followed by 0 or more charactres which are NOT directory separators, then followed by wildcard symbol.
Combining this all together we have:
static IEnumerable<string> ResolveDirectories(string path) {
if (path.Contains("*") || path.Contains("?")) {
// we have a wildcard,
// resolve it to absolute path if necessary
if (!Path.IsPathRooted(path))
path = Path.Combine(Directory.GetCurrentDirectory(), path);
// resolve .. stuff if any
path = new Uri(Path.Combine(Directory.GetCurrentDirectory(), path)).AbsolutePath;
// split with regex above, only on first match (2 parts)
var parts = new Regex(#"[\\/](?=[^\\/]*[\*?])").Split(path, 2);
var searchRoot = parts[0];
var searchPatterns = parts[1].Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
foreach (var dir in ResolveWildcards(searchRoot, searchPatterns))
yield return dir;
}
else {
// this should work with "../.." type of paths too, will resolve relative to current directory
// so no processing is necessary
if (Directory.Exists(path)) {
yield return path;
}
else {
// invalid directory?
}
}
}
static IEnumerable<string> ResolveWildcards(string searchRoot, string [] searchPatterns) {
// if we have path C:\an\abolu*e\dir\with\wild*cards*
// search root is C:\an
// and patterns are: [abolu*e, dir, with, wild*cards*]
if (Directory.Exists(searchRoot)) {
// use next pattern to search in a search root
var next = searchPatterns[0];
// leave the rest for recursion
var rest = searchPatterns.Skip(1).ToArray();
foreach (var dir in Directory.EnumerateDirectories(searchRoot, next, SearchOption.AllDirectories)) {
// if nothing left (last pattern) - return it
if (rest.Length == 0)
yield return dir;
else {
// otherwise search with rest patterns in freshly found directory
foreach (var sub in ResolveWildcards(dir, rest))
yield return sub;
}
}
}
}
This is not properly tested, so take care.
'Evk' posted his/her answer a few minutes before I was about to post this, so I have not evaluated Evk's answer.
After finding this question+answer which might have worked fine but I didn't test it, I stumbled across Michael Ganss's Glob.cs NuGet package, and the results are very nice:
// Requires Glob.cs, (c) 2013 Michael Ganss, downloaded via the NuGet package manager:
// https://www.nuget.org/packages/Glob.cs
// (In Visual Studio, go to Tools->NuGet Package Manager->Package Manager Console.)
// PM> Install-Package Glob.cs -Version 1.3.0
using Glob;
namespace StackOverflow_cs
{
class Program
{
static void Main(string[] args)
{
List<string> directories = new List<string>();
foreach (string arg in args)
{
var dirs = Glob.Glob.Expand(arg, true, true);
foreach (var dir in dirs)
{
directories.Add(dir.FullName);
}
}
Console.WriteLine("Full list of directories specified by the command line args:");
foreach (string dir in directories)
{
Console.WriteLine(" " + dir);
}
// Now go do what I want to do for each of these directories......
}
}
}
But I don't know why I have to say "Glob.Glob.Expand()" instead of simply "Glob.Expand()". Anyway, it works beautifully.
When i use OpenFileDialog to Open file, of course i need to get the file directory and its name to load the file.(to load xml, to access the file i need full path.)
opd is OpenFileDialog
if (opd.ShowDialog() == true)
{
var names = opd.FileNames;
foreach (string name in names)
{
LoadFile(Path.Combine(Path.GetDirectoryName(name), name));
}
}
my question is How Path.GetDirectoryName take the path of the File by Just taking the string ?
Path.GetDirectoryName(name)
name is Just string and this method takes its directory by just taking string? . there can be thousands of files with same name inside computer.
ShortQuestion:
where is opd refrenced?
Edit:
i thought opd.FileNames just takes name of the files.(because of methods name)
and Also i found something interesting.
LoadFile(Path.Combine(Path.GetDirectoryName(name), name));
this works fine Because Path.Combine will just skip the same part of string.
Ex:
string name = #"C:\Users\Default\xml.xml";
string getDirNameResault= Path.GetDirectoryName(name);// this will be C:\Users\Default
So Path.Combine will be
Path.Combine(#"C:\Users\Default", #"C:\Users\Default\xml.xml)
witch returns "C:\Users\Default\xml.xml" !
Path.GetDirectoryName splits the string you already have (from opd) by slash / or \ and then returns everything except last part.
Complete source code of the function in .NET Core foundational libraries (called CoreFX) you can find here: https://github.com/dotnet/corefx/blob/41e203011152581a6c65bb81ac44ec037140c1bb/src/System.Runtime.Extensions/src/System/IO/Path.cs#L151
Code of the implementation:
// Returns the directory path of a file path. This method effectively
// removes the last element of the given file path, i.e. it returns a
// string consisting of all characters up to but not including the last
// backslash ("\") in the file path. The returned value is null if the file
// path is null or if the file path denotes a root (such as "\", "C:", or
// "\\server\share").
//
public static String GetDirectoryName(String path)
{
if (path != null)
{
CheckInvalidPathChars(path);
#if FEATURE_LEGACYNETCF
if (!CompatibilitySwitches.IsAppEarlierThanWindowsPhone8)
{
#endif
string normalizedPath = NormalizePath(path, false);
// If there are no permissions for PathDiscovery to this path, we should NOT expand the short paths
// as this would leak information about paths to which the user would not have access to.
if (path.Length > 0)
{
try
{
// If we were passed in a path with \\?\ we need to remove it as FileIOPermission does not like it.
string tempPath = Path.RemoveLongPathPrefix(path);
// FileIOPermission cannot handle paths that contain ? or *
// So we only pass to FileIOPermission the text up to them.
int pos = 0;
while (pos < tempPath.Length && (tempPath[pos] != '?' && tempPath[pos] != '*'))
pos++;
// GetFullPath will Demand that we have the PathDiscovery FileIOPermission and thus throw
// SecurityException if we don't.
// While we don't use the result of this call we are using it as a consistent way of
// doing the security checks.
if (pos > 0)
Path.GetFullPath(tempPath.Substring(0, pos));
}
catch (SecurityException)
{
// If the user did not have permissions to the path, make sure that we don't leak expanded short paths
// Only re-normalize if the original path had a ~ in it.
if (path.IndexOf("~", StringComparison.Ordinal) != -1)
{
normalizedPath = NormalizePath(path, /*fullCheck*/ false, /*expandShortPaths*/ false);
}
}
catch (PathTooLongException) { }
catch (NotSupportedException) { } // Security can throw this on "c:\foo:"
catch (IOException) { }
catch (ArgumentException) { } // The normalizePath with fullCheck will throw this for file: and http:
}
path = normalizedPath;
#if FEATURE_LEGACYNETCF
}
#endif
int root = GetRootLength(path);
int i = path.Length;
if (i > root)
{
i = path.Length;
if (i == root) return null;
while (i > root && path[--i] != DirectorySeparatorChar && path[i] != AltDirectorySeparatorChar) ;
String dir = path.Substring(0, i);
#if FEATURE_LEGACYNETCF
if (CompatibilitySwitches.IsAppEarlierThanWindowsPhone8)
{
if (dir.Length >= MAX_PATH - 1)
throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
}
#endif
return dir;
}
}
return null;
}
For the complete source code of the function (in Mono) see here: https://github.com/mono/mono/blob/master/mcs/class/corlib/System.IO/Path.cs#L199
name is Just string and this method takes its directory by just taking string? . there can be thousands of files with same name inside computer.
name contains the full path, Path.GetDirectoryName() just strips everything after the last directory separator, Path.Combine(Path.GetDirectoryName(name), name) will not do anything useful:
If path2 includes a root, path2 is returned.
Just use name directly.
This question already has answers here:
See if file path is inside a directory
(2 answers)
Closed 9 years ago.
How can I check in C# if the specific path is to directory in "Program Files" ?
C:\Program Files\someDir... -> is in Program Files
D:\Apps\someDir... -> isn't in Program Files
Thanks!
You can check a path in ProgramFiles(x86) by using the code below:
string path = "yourpath";
var programfileX86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
if (path.IndexOf(programfileX86, StringComparison.OrdinalIgnoreCase) >= 0)
{
//Found path
}
There're some interseting and subtle issues with the problem:
You should compare paths case insenstive, e.g. "C:\PRogRAM FILES (x86)\Sample" is OK
Separators could be either / or \ so "C:/PRogRAM FILES (x86)/Sample" is OK as well
You should break on separatos only, e.g. "C:\Program Files (x86)MyData\Sample" is not OK
The Code:
public static Boolean PathIncludes(String path, String pathToInclude) {
if (String.IsNullOrEmpty(pathToInclude))
return false;
else if (String.IsNullOrEmpty(path))
return false;
String[] parts = Path.GetFullPath(path).Split(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar, Path.VolumeSeparatorChar);
String[] partsToInclude = Path.GetFullPath(pathToInclude).Split(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar, Path.VolumeSeparatorChar);
if (parts.Length < partsToInclude.Length)
return false;
for (int i = 0; i < partsToInclude.Length; ++i)
if (!String.Equals(parts[i], partsToInclude[i], StringComparison.OrdinalIgnoreCase))
return false;
return true;
}
public static Boolean InProgramFiles(String path) {
return PathIncludes(path, Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86)));
}
// Tests:
// Supposing that ProgramFilesX86 is "C:\Program Files (x86)"
InProgramFiles(#"C:\PRogRAM FILES (x86)\Sample"); // <- true
InProgramFiles(#"C:/PRogRAM FILES (x86)/Sample"); // <- true
InProgramFiles(#"D:/PRogRAM FILES (x86)/Sample"); // <- false
InProgramFiles(#"C:/PRogRAM FILES (x86)A/Sample"); // <- false
First you need to get the program files path. You can do that with System.Environment:
var programFilesPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ProgramFiles);
If you want the 32 bit program files path you would just change the special folder you are looking for (System.Environment.SpecialFolder.ProgramFilesX86). Then I would do a contains:
var isInProgramFiles = myPath.ToLower().Contains(programFilesPath.ToLower());
That should get you 90% of the way there at least! Best of luck!
EDIT / Sanitize Note
As a side note - there are situations where you can have a valid input and this still wouldn't match. For example - using "/" instead of "\". If you want to make sure you handle these boundary cases correctly, you can create a "DirectoryInfo" object from your input string, validate that it is actually a folder and also standardize the formatting for it. That code looks something like:
if (!System.IO.Directory.Exists(inputPath)) return false;
var checkPath = (new System.IO.DirectoryInfo(inputPath)).FullName;
In this example "inputPath" is the same as "myPath" was above. That should do a moderately good job of sanitizing the input. Best of luck!
If you have a path variable:
string path = "/* whatever path */";
You can check if it is in a folder subfolder this way:
path.IndexOf('\\' + subfolder + '\\') != -1
Note that in more complex cases .. may revert you out of a subdirectory, meaning that you are not in folder f2 if you have something like this:
"\\base_on_drive\\subfolder\\f1\\f2\\..\\a_file.txt"
The .. will bump you back into it's parent folder f1.
if (path.Contains(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)) || (path.Contains(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86)))
{
}
Assuming your program might be running inside of ProgramFiles, you will probably want to get the fullpath of any path you're checking (in case you get a relative path). In addition, C# has a handy SpecialFolder enumeration that you can use to get the ProgramFiles directory.
The following code will take in a path, convert it to a fullpath, and check if the ProgramFiles directory can be found inside of it. You may want to add some error handling (such as checking for null paths).
static string programfileX86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
public bool IsInsideProgramFiles(string path)
{
// Get the fullpath in case 'path' is a relative path
string fullPath = System.IO.Path.GetFullPath(path);
return (fullPath.IndexOf(programfileX86, StringComparison.OrdinalIgnoreCase) >= 0);
}
Note: Depending on the systems your code is running in, you may want to check for both SpecialFolder.ProgramFiles and SpecialFolder.ProgramFilesx86.
Credit goes to Toan Nguyen's for the code to get the ProgramFiles directory:
This question already has answers here:
How do I check if a given string is a legal/valid file name under Windows?
(27 answers)
Closed 7 years ago.
Is there a method in the System.IO namespace that checks the validity of a filename?
For example, C:\foo\bar would validate and :"~-* would not
Or a little trickier, X:\foo\bar would validate is there is an X: drive on the system, but wouldn't otherwise.
I suppose I could write such a method myself, but I'm more interested in a built-in one.
Just do;
System.IO.FileInfo fi = null;
try {
fi = new System.IO.FileInfo(fileName);
}
catch (ArgumentException) { }
catch (System.IO.PathTooLongException) { }
catch (NotSupportedException) { }
if (ReferenceEquals(fi, null)) {
// file name is not valid
} else {
// file name is valid... May check for existence by calling fi.Exists.
}
For creating a FileInfo instance the file does not need to exist.
You can get a list of invalid characters from Path.GetInvalidPathChars and GetInvalidFileNameChars as discussed in this question.
As noted by jberger, there some other characters which are not included in the response from this method. For much more details of the windows platform, take a look at Naming Files, Paths and Namespaces on MSDN.
As Micah points out, there is Directory.GetLogicalDrives to get a list of valid drives.
You could make use the System.Uri class. The Uri class isn't just useful for web URLs, it also handles file system paths as well. Use the Uri.TryCreate method to find if the path is rooted then use the IsLoopback property to determine if the Uri references the local machine.
Here is a simple method which determines if a string is a valid, local, and rooted file path.
public bool IsPathValidRootedLocal(String pathString) {
Uri pathUri;
Boolean isValidUri = Uri.TryCreate(pathString, UriKind.Absolute, out pathUri);
return isValidUri && pathUri != null && pathUri.IsLoopback;
}
I am confident this will work.
There are several methods you could use that exist in the System.IO namespace:
Directory.GetLogicalDrives() // Returns an array of strings like "c:\"
Path.GetInvalidFileNameChars() // Returns an array of characters that cannot be used in a file name
Path.GetInvalidPathChars() // Returns an array of characters that cannot be used in a path.
As suggested you could then do this:
bool IsValidFilename(string testName) {
string regexString = "[" + Regex.Escape(Path.GetInvalidPathChars()) + "]";
Regex containsABadCharacter = new Regex(regexString);
if (containsABadCharacter.IsMatch(testName)) {
return false;
}
// Check for drive
string pathRoot = Path.GetPathRoot(testName);
if (Directory.GetLogicalDrives().Contains(pathRoot)) {
// etc
}
// other checks for UNC, drive-path format, etc
return true;
}
Thought I would post a solution I cobbled together from bits of answers I found after searching for a robust solution to the same problem. Hopefully it helps someone else.
using System;
using System.IO;
//..
public static bool ValidateFilePath(string path, bool RequireDirectory, bool IncludeFileName, bool RequireFileName = false)
{
if (string.IsNullOrEmpty(path)) { return false; }
string root = null;
string directory = null;
string filename = null;
try
{
// throw ArgumentException - The path parameter contains invalid characters, is empty, or contains only white spaces.
root = Path.GetPathRoot(path);
// throw ArgumentException - path contains one or more of the invalid characters defined in GetInvalidPathChars.
// -or- String.Empty was passed to path.
directory = Path.GetDirectoryName(path);
// path contains one or more of the invalid characters defined in GetInvalidPathChars
if (IncludeFileName) { filename = Path.GetFileName(path); }
}
catch (ArgumentException)
{
return false;
}
// null if path is null, or an empty string if path does not contain root directory information
if (String.IsNullOrEmpty(root)) { return false; }
// null if path denotes a root directory or is null. Returns String.Empty if path does not contain directory information
if (String.IsNullOrEmpty(directory)) { return false; }
if (RequireFileName)
{
// if the last character of path is a directory or volume separator character, this method returns String.Empty
if (String.IsNullOrEmpty(filename)) { return false; }
// check for illegal chars in filename
if (filename.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) { return false; }
}
return true;
}
Even if the filename is valid, you may still want to touch it to be sure the user has permission to write.
If you won't be thrashing the disk with hundreds of files in a short period of time, I think creating an empty file is a reasonable approach.
If you really want something lighter, like just checking for invalid chars, then compare your filename against Path.GetInvalidFileNameChars().
Several of the System.IO.Path methods will throw exceptions if the path or filename is invalid:
Path.IsPathRooted()
Path.GetFileName()
http://msdn.microsoft.com/en-us/library/system.io.path_methods.aspx
I've had luck using regular expressions as others have shown.
One thing to keep in mind is that Windows at least prohibits some filenames that otherwise containlegal characters. A few come to mind: com, nul, prn.
I don't have it with me now, but I have a regex that takes these filename into consideration. If you want I can post it, otherwise I'm sure you can find it the same way I did: Google.
-Jay
Use the static GetInvalidFileNameChars method on the Path class in the System.IO namespace to determine what characters are illegal in a file name.
To do so in a path, call the static GetInvalidPathChars method on the same class.
To determine if the root of a path is valid, you would call the static GetPathRoot method on the Path class to get the root, then use the Directory class to determine if it is valid. Then you can validate the rest of the path normally.
Try out this method which would try to cover for all the possible Exceptions scenarios. It would work for almost all the Windows related Paths.
/// <summary>
/// Validate the Path. If path is relative append the path to the project directory by default.
/// </summary>
/// <param name="path">Path to validate</param>
/// <param name="RelativePath">Relative path</param>
/// <param name="Extension">If want to check for File Path</param>
/// <returns></returns>
private static bool ValidateDllPath(ref string path, string RelativePath = "", string Extension = "") {
// Check if it contains any Invalid Characters.
if (path.IndexOfAny(Path.GetInvalidPathChars()) == -1) {
try {
// If path is relative take %IGXLROOT% as the base directory
if (!Path.IsPathRooted(path)) {
if (string.IsNullOrEmpty(RelativePath)) {
// Exceptions handled by Path.GetFullPath
// ArgumentException path is a zero-length string, contains only white space, or contains one or more of the invalid characters defined in GetInvalidPathChars. -or- The system could not retrieve the absolute path.
//
// SecurityException The caller does not have the required permissions.
//
// ArgumentNullException path is null.
//
// NotSupportedException path contains a colon (":") that is not part of a volume identifier (for example, "c:\").
// PathTooLongException The specified path, file name, or both exceed the system-defined maximum length. For example, on Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260 characters.
// RelativePath is not passed so we would take the project path
path = Path.GetFullPath(RelativePath);
} else {
// Make sure the path is relative to the RelativePath and not our project directory
path = Path.Combine(RelativePath, path);
}
}
// Exceptions from FileInfo Constructor:
// System.ArgumentNullException:
// fileName is null.
//
// System.Security.SecurityException:
// The caller does not have the required permission.
//
// System.ArgumentException:
// The file name is empty, contains only white spaces, or contains invalid characters.
//
// System.IO.PathTooLongException:
// The specified path, file name, or both exceed the system-defined maximum
// length. For example, on Windows-based platforms, paths must be less than
// 248 characters, and file names must be less than 260 characters.
//
// System.NotSupportedException:
// fileName contains a colon (:) in the middle of the string.
FileInfo fileInfo = new FileInfo(path);
// Exceptions using FileInfo.Length:
// System.IO.IOException:
// System.IO.FileSystemInfo.Refresh() cannot update the state of the file or
// directory.
//
// System.IO.FileNotFoundException:
// The file does not exist.-or- The Length property is called for a directory.
bool throwEx = fileInfo.Length == -1;
// Exceptions using FileInfo.IsReadOnly:
// System.UnauthorizedAccessException:
// Access to fileName is denied.
// The file described by the current System.IO.FileInfo object is read-only.-or-
// This operation is not supported on the current platform.-or- The caller does
// not have the required permission.
throwEx = fileInfo.IsReadOnly;
if (!string.IsNullOrEmpty(Extension)) {
// Validate the Extension of the file.
if (Path.GetExtension(path).Equals(Extension, StringComparison.InvariantCultureIgnoreCase)) {
// Trim the Library Path
path = path.Trim();
return true;
} else {
return false;
}
} else {
return true;
}
} catch (ArgumentNullException) {
// System.ArgumentNullException:
// fileName is null.
} catch (System.Security.SecurityException) {
// System.Security.SecurityException:
// The caller does not have the required permission.
} catch (ArgumentException) {
// System.ArgumentException:
// The file name is empty, contains only white spaces, or contains invalid characters.
} catch (UnauthorizedAccessException) {
// System.UnauthorizedAccessException:
// Access to fileName is denied.
} catch (PathTooLongException) {
// System.IO.PathTooLongException:
// The specified path, file name, or both exceed the system-defined maximum
// length. For example, on Windows-based platforms, paths must be less than
// 248 characters, and file names must be less than 260 characters.
} catch (NotSupportedException) {
// System.NotSupportedException:
// fileName contains a colon (:) in the middle of the string.
} catch (FileNotFoundException) {
// System.FileNotFoundException
// The exception that is thrown when an attempt to access a file that does not
// exist on disk fails.
} catch (IOException) {
// System.IO.IOException:
// An I/O error occurred while opening the file.
} catch (Exception) {
// Unknown Exception. Might be due to wrong case or nulll checks.
}
} else {
// Path contains invalid characters
}
return false;
}
I don't know of anything out of the box that can just validate all of that for you, however the Path class in .NET can help you out tremendously.
For starters, it has:
char[] invalidChars = Path.GetInvalidFileNameChars(); //returns invalid charachters
or:
Path.GetPathRoot(string); // will return the root.
Probably the bast way is to build a custom method mixing a combination of regex and small look up on your file system (to see the drives, for example)
This will get you the drives on the machine:
System.IO.DriveInfo.GetDrives()
These two methods will get you the bad characters to check:
System.IO.Path.GetInvalidFileNameChars();
System.IO.Path.GetInvalidPathChars();
Think it's too late to answer but... :)
in case of path with volume name you could write something like this:
using System;
using System.Linq;
using System.IO;
// ...
var drives = Environment.GetLogicalDrives();
var invalidChars = Regex.Replace(new string(Path.GetInvalidFileNameChars()), "[\\\\/]", "");
var drive = drives.FirstOrDefault(d => filePath.StartsWith(d));
if (drive != null) {
var fileDirPath = filePath.Substring(drive.Length);
if (0 < fileDirPath.Length) {
if (fileDirPath.IndexOfAny(invalidChars.ToCharArray()) == -1) {
if (Path.Combine(drive, fileDirPath) != drive) {
// path correct and we can proceed
}
}
}
}
I am doing a file server migration and I'm writing a small C# app to help me map the user permissions so we can put them in user groups.
I'm currently using
Directory.GetAccessControl(path);
However it fails when it get to this 263 char file path.
Invalid name.
Parameter name: name
I get the same error when I use DirectoryInfo.GetAccessControl();
Is there a work around or alternative to this method?
Thanks!
One alternative is to use subst. From a command prompt, you can execute
subst X: "D:\really really really\long path\that you can shorten"
Then perform your operations on the X: drive and the whole beginning section won't count against your 260-char limit.
Prefix the path with "\?\" to specify an 'extended-length path". I haven't been able to test if Directory.GetAccessControl()` will work with extended-length paths, but it's worth a try:
from http://msdn.microsoft.com/en-us/library/aa365247.aspx:
Maximum Path Length Limitation
In the Windows API (with some exceptions discussed in the following paragraphs), the maximum length for a path is MAX_PATH, which is defined as 260 characters. A local path is structured in the following order: drive letter, colon, backslash, name components separated by backslashes, and a terminating null character. For example, the maximum path on drive D is "D:\<some 256-character path string><NUL>" where "<NUL>" represents the invisible terminating null character for the current system codepage. (The characters < > are used here for visual clarity and cannot be part of a valid path string.)
Note File I/O functions in the Windows API convert "/" to "\" as part of converting the name to an NT-style name, except when using the "\\?\" prefix as detailed in the following sections.
The Windows API has many functions that also have Unicode versions to permit an extended-length path for a maximum total path length of 32,767 characters. This type of path is composed of components separated by backslashes, each up to the value returned in the lpMaximumComponentLength parameter of the GetVolumeInformation function (this value is commonly 255 characters). To specify an extended-length path, use the "\\?\" prefix. For example, "\\?\D:\<very long path>". (The characters < > are used here for visual clarity and cannot be part of a valid path string.)
If it's an arbitrary limit in the library, then you could try using the 8 character names for the directories. To work out what these names are, run dir with the /X option:
C:\>dir /x
29/12/2009 23:33 PROGRA~1 Program Files
23/02/2010 21:26 PROGRA~2 Program Files (x86
05/12/2009 20:57 Users
02/02/2010 09:23 Windows
The short names are those with the tildes in. Try passing these to the function to reduce the string length. No guaranteees this will work, mind.
You should process your directory tree recursively using DirectoryInfo - doing so you will avoid passing the full path.
Using the library I mentioned above this does the trick nicely. I suppose I should have grabbed more mapped drive letters as needed but my max dir length was only 300 chars long.
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Security;
using System.Security.AccessControl;
using aejw.Network;
namespace SecurityScanner
{
class Program
{
static void Main(string[] args)
{
string path = #"\\mynetworkdir";
DirectoryInfo di = new DirectoryInfo(path);
List<DirSec> dirs = new List<DirSec>();
RecordSecurityData(di, dirs, path, path);
//Grouping up my users
List<List<DirSec>> groups = new List<List<DirSec>>();
foreach (DirSec d in dirs)
{
bool IsNew = true;
foreach (List<DirSec> group in groups)
{
if (d.IsSameUserList(group[0]))
{
group.Add(d);
IsNew = false;
break;
}
}
if (IsNew)
{
List<DirSec> newGroup = new List<DirSec>();
newGroup.Add(d);
groups.Add(newGroup);
}
}
//Outputting my potential user groups
StringBuilder sb = new StringBuilder();
foreach (List<DirSec> group in groups)
{
foreach (DirSec d in group)
{
sb.AppendLine(d.DirectoryName);
}
foreach (string s in group[0].UserList)
{
sb.AppendLine("\t" + s);
}
sb.AppendLine();
}
File.WriteAllText(#"c:\security.txt", sb.ToString());
}
public static void RecordSecurityData(DirectoryInfo di, List<DirSec> dirs, string path, string fullPath)
{
DirSec me = new DirSec(fullPath);
DirectorySecurity ds;
NetworkDrive nd = null;
if(path.Length <= 248)
ds = Directory.GetAccessControl(path);
else
{
nd = new NetworkDrive();
nd.LocalDrive = "X:";
nd.ShareName = path;
nd.MapDrive();
path = #"X:\";
di = new DirectoryInfo(path);
ds = Directory.GetAccessControl(path);
}
foreach (AuthorizationRule ar in ds.GetAccessRules(true, true, typeof(System.Security.Principal.NTAccount)))
{
me.AddUser(ar.IdentityReference.Value);
}
dirs.Add(me);
foreach (DirectoryInfo child in di.GetDirectories())
{
RecordSecurityData(child, dirs, path + #"\" + child.Name, fullPath + #"\" + child.Name);
}
if (nd != null)
nd.UnMapDrive();
}
public struct DirSec
{
public string DirectoryName;
public List<string> UserList;
public DirSec(string directoryName)
{
DirectoryName = directoryName;
UserList = new List<string>();
}
public void AddUser(string UserName)
{
UserList.Add(UserName);
}
public bool IsSameUserList(DirSec other)
{
bool isSame = false;
if (this.UserList.Count == other.UserList.Count)
{
isSame = true;
foreach (string myUser in this.UserList)
{
if (!other.UserList.Contains(myUser))
{
isSame = false;
break;
}
}
}
return isSame;
}
}
}
}