How do I get the security details for a long path? - c#

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;
}
}
}
}

Related

Directory.EnumerateDirectories : Only get back Directores with a special file in it [duplicate]

I wish to get list of all the folders/directories that has a particular file in it. How do I do this using C# code.
Eg: Consider I have 20 folders of which 7 of them have a file named "abc.txt". I wish to know all folders that has the file "abc.txt".
I know that we can do this by looking thru all the folders in the path and for each check if the File.Exists(filename); But I wish to know if there is any other way of doing the same rather than looping through all the folder (which may me little time consuming in the case when there are many folders).
Thanks
-Nayan
I would use the method EnumerateFiles of the Directory class with a search pattern and the SearchOption to include AllDirectories. This will return all files (full filename including directory) that match the pattern.
Using the Path class you get the directory of the file.
string rootDirectory = //your root directory;
var foundFiles = Directory.EnumerateFiles(rootDirectory , "abc.txt", SearchOption.AllDirectories);
foreach (var file in foundFiles){
Console.WriteLine(System.IO.Path.GetDirectoryName(file));
}
EnumerateFiles is only available since .NET Framework 4. If you are working with an older version of the .NET Framework then you could use GetFiles of the Directory class.
Update (see comment from PLB):
The code above will fail if the access to a directory in denied. In this case you will need to search each directory one after one to handle exceptions.
public static void SearchFilesRecursivAndPrintOut(string root, string pattern)
{
//Console.WriteLine(root);
try
{
var childDireactory = Directory.EnumerateDirectories(root);
var files = Directory.EnumerateFiles(root, pattern);
foreach (var file in files)
{
Console.WriteLine(System.IO.Path.GetDirectoryName(file));
}
foreach (var dir in childDireactory)
{
SearchRecursiv(dir, pattern);
}
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
}
The following shows how to narrow down your search by specific criteria (i.e. include only DLLs that contain "Microsoft", "IBM" or "nHibernate" in its name).
var filez = Directory.EnumerateFiles(#"c:\MLBWRT", "*.dll", SearchOption.AllDirectories)
.Where(
s => s.ToLower().Contains("microsoft")
&& s.ToLower().Contains("ibm")
&& s.ToLower().Contains("nhibernate"));
string[] allFiles = filez.ToArray<string>();
for (int i = 0; i < allFiles.Length; i++) {
FileInfo fInfo = new FileInfo(allFiles[i]);
Console.WriteLine(fInfo.Name);
}

How to get the Full Path of a File?

I try to get the Full Path of a File. ie. calc
Input: calc
Expected output: C:\WINDOWS\system32\calc.exe
I could find out how to do it with PowerShell:
(Get-Command calc).Source
Or with CommandLine:
where.exe calc
But unfortunately I can not get it done with C#.
The documentation for Get-Command says:
Get-Command * gets all types of commands, including all of the non-PowerShell files in the Path environment variable ($env:Path), which it lists in the Application command type.
So we will need to get the Path environment variable and iterate over the directories it lists, looking for files with extensions that indicate the file is a program, for example "*.com" and "*.exe".
The problem with the Path environment variable is that it can become polluted with non-existent directories, so we will have to check for those.
The case of the filename and extension don't matter, so case-insensitive comparisons need to be made.
static void ShowPath(string progName)
{
var extensions = new List<string> { ".com", ".exe" };
string envPath = Environment.GetEnvironmentVariable("Path");
var dirs = envPath.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string d in dirs.Where(f => Directory.Exists(f)))
{
foreach (var f in (Directory.EnumerateFiles(d).
Where(thisFile => extensions.Any(h => Path.GetExtension(thisFile).Equals(h, StringComparison.InvariantCultureIgnoreCase)))))
{
if (Path.GetFileNameWithoutExtension(f).Equals(progName, StringComparison.InvariantCultureIgnoreCase))
{
Console.WriteLine(f);
return;
}
}
}
Console.WriteLine("Not found.");
}
static void Main(string[] args)
{
ShowPath("calc");
Console.ReadLine();
}
Output:
C:\WINDOWS\system32\calc.exe
There is always the possibility that the current user does not have permission to list the files from somewhere in the path, so checks should be added for that. Also, you might want to use StringComparison.CurrentCultureIgnoreCase for the comparison.
You can get the Pathenvironment variable, split it with ; as delimiter and loop over that result. Then, check if the file path + #"\" + name + ".exe" exists.
var findMe = "calc";
var pathes = Environment.GetEnvironmentVariable("Path").Split(';');
foreach (var path in pathes)
{
var testMe = $#"{path}\{findMe}.exe";
if (File.Exists(testMe))
{
Console.WriteLine(testMe);
}
}
This outputs :
C:\WINDOWS\system32\calc.exe
I do not know about any way of doing that exact thing from C# either. However the paths are usually well known and can be retreived via the SpecialFolders Enumeration:
using System;
using System.Diagnostics;
using System.IO;
namespace RunAsAdmin
{
class Program
{
static void Main(string[] args)
{
/*Note: Running a batch file (.bat) or similar script file as admin
Requires starting the interpreter as admin and handing it the file as Parameter
See documentation of Interpreting Programm for details */
//Just getting the Absolute Path for Notepad
string windir = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
string FullPath = Path.Combine(windir, #"system32\notepad.exe");
//The real work part
//This is the programm to run
ProcessStartInfo startInfo = new ProcessStartInfo(FullPath);
//This tells it should run Elevated
startInfo.Verb = "runas";
//And that gives the order
//From here on it should be 100% identical to the Run Dialog (Windows+R), except for the part with the Elevation
System.Diagnostics.Process.Start(startInfo);
}
}
}
I did not just use System (37) back then, as I wrote it when x32/x86 Systems were still a thing. You would need to check how it resolves nowadays.
Note that most of those paths are duplicated in the PATH System Variable, so you could look it up: https://www.architectryan.com/2018/03/17/add-to-the-path-on-windows-10/
Path Variables in turn go back to the old DOS days. Basically if you gave the Commandline a command/filename it would try the build-in commands, then Executables in the current working Directory (.bat, .com, .exe), and then go look over the path directories to again look for executeables. And only if all that failed, would it complain.
I finally tried to combine all three answers and came up with this:
I post it here in case someone has the same problem.
public static string[] GetPathOf(string cmd)
{
var list = new List<string>();
list.AddRange(Environment.GetEnvironmentVariable("path", EnvironmentVariableTarget.Machine).Split(';'));
list.AddRange(Environment.GetEnvironmentVariable("path", EnvironmentVariableTarget.Process).Split(';'));
list.AddRange(Environment.GetEnvironmentVariable("path", EnvironmentVariableTarget.User).Split(';'));
list = list.Distinct().Where(e=>Directory.Exists(e)).SelectMany(e=> new DirectoryInfo(e).GetFiles()).Where(e=>Regex.IsMatch(e.Name,"(?i)^"+cmd+"\\.(?:exe|cmd|com)")).Select(e=>e.FullName).ToList();
return list.ToArray();
}

How to build a list of directories from args that may contain drive letters and/or wildcards

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.

Error: File Path is Too Long

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.

how to read all the subdirectories in a given destination which contain Master File in it

I am having a problem .
My Problem is to read all the subdirectories in a given destination which contain Master file .
I can read subdirectories but i am creating a project which only read given directory which should contain Master file in the directory .
In the given directory a file called Master file should be there.
I want to write the code like if the given directory doesnot contain any Master file in it it should Jump to another Directory.
My source Directory is #"C:\test.
In #"C:\test" there are many folders and subfolders .
the test directory contains "C:\test\test1\test2\test3 . In this path test3 folder contains Master file test1 and test2 doesn't .
I want to write this code something like this,
MLMReader Reader = new MLMReader();
Reader.OpenDirectory(#"C:\test");
if (!File.Exists(test + "\\Master"))
{
//i want to loop the "C"\\" and if test1 does not contain
// Master File then jump to another directory test2, if
//test2 directory contain Master File then the work should
// continue after finishing go to test3
}
Is there any way to do .
Any suggestions for my problem.
I haven't tested, but I'm pretty sure the following will work:
string[] paths = Directory.GetFiles(dirPath, "MasterFile", SearchOption.AllDirectories);
Then you can just foreach over the resulting array, if you want to go through all MasterFiles. Or if you just care about the first result, then it's just paths[0] -- of course, means it does a lil bit extra work finding all matching paths. And you probably don't need a check for an empty array as an index out of bounds would indicate there's no MasterFile (unless you want to catch that and then rethrow as file not found exception or whatever).
Linq-to-FileSystem allows you to perform structured queries on your file system. See the following example:
var dir = new DirectoryInfo(#"C:\test");
// find all subdirectories of test that contains a file / folder called 'Master'
var dirs = dir.Elements()
.Where(d => d.Elements().Any(i => i.Name == "Master"))
this is my code where I program with directorys, hope it will help you.
using System;
using System.IO;
static void Print(string path, int? rec, int? tree, bool color, int? level = 0)
{
if ((rec != null && level > rec) || path == null) return;
if (rec == null) rec = 0;
string[] dir;
string[] fil;
try
{
dir = Directory.GetDirectories(path);
fil = Directory.GetFiles(path);
}
catch(Exception e)
{
Console.WriteLine(e.Message);
return;
}
foreach (string s in dir)
{
WriteSpace(level,tree);
Console.WriteLine(s);
Print(s, rec, tree, color, level + 1);
}
if (color)
{
ConsoleColor def = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Blue;
foreach (string s in fil) // vypis file
{
WriteSpace(level, tree);
Console.WriteLine(s);
}
Console.ForegroundColor = def;
}
else
{
foreach (string s in fil)
{
WriteSpace(level, tree);
Console.WriteLine(s);
}
}
}
private static void WriteSpace(int? level, int? tree)
{
for (int i = 0; i < level*tree; i++)
Console.Write(" ");
}
}
Here's a Recursive Example on how you can traverse a directory structure and look for a certain file, once it is found you can call the corresponding method.
static void Main()
{
TraverseDirectory(#"D:\TestDirectory");
}
static void DoSomethingWithMaster(string path)
{
Console.WriteLine("Found master at {0}", path);
}
static void TraverseDirectory(string directory)
{
var currentDirectory = new DirectoryInfo(directory);
foreach(var dir in currentDirectory.GetDirectories())
{
var currentPath = dir.FullName;
TraverseDirectory(currentPath);
var pathToMasterFile = Path.Combine(currentPath, "Master");
if (File.Exists(pathToMasterFile))
DoSomethingWithMaster(pathToMasterFile);
}
}
I have the following Folder Structure:
D:\TestDirectory\1
D:\TestDirectory\2
D:\TestDirectory\3
D:\TestDirectory\4
D:\TestDirectory\4\Master
D:\TestDirectory\5
And when running the above it prints: Found master at D:\TestDirectory\4
All you need for this is:
using System;
using System.IO;
You can also move TraverseDirectory(currentPath); to the end of the foreach-loop, where you put it depends on when you want to detect the file, do you want to go deep and then climb your way back and check for the Master-file on the way up, or do you want to check for the master file before you enter the next directory?
According to your question, you might want to swap them in my answer and if I understand you correctly, you might not even want to go to the next directory after finding one master-file?
foreach(var dir in currentDirectory.GetDirectories())
{
var currentPath = dir.FullName;
var pathToMasterFile = Path.Combine(currentPath, "Master");
if (File.Exists(pathToMasterFile))
DoSomethingWithMaster(pathToMasterFile);
TraverseDirectory(currentPath);
}
In this example, it does exactly what you are saying in your commented code inside your if. It will first check TestDirectory\1 for a Master file and then go further down the line. If you don't care about any other Master-files in one sub-directory once it is found, you can just nest TraverseDirectory(pathToMasterFile) inside an else-block.
Edit
You might want to use EnumerateDirectories, see MSDN for details.

Categories

Resources