Is there any way to search for the particular parent directory?
I know I can get the parent of the directory using this
Directory.GetParent(Directory.GetCurrentDirectory()).FullName
But this returns immediate parent, is there any way I can kind of search for particular parent in the directory hierarchy of the path?
EDIT
What I am trying to achieve is, say if I have current directory like this
C:/Project/Source/Dev/Database
So I want to reach to the directory Source
I know I can reach it by calling GetParent method twice but this I don't think is the right way to do it because what if in future my file current directory changes and it goes further down.
So I want some full proof way where I can directly find the path of the directory Source no matter how deep I am in the current directory because that is for sure that I will be inside directory Source
So something like
FindParent('Source')
You can try something like this (for loop):
private static IEnumerable<String> ParentDirectories(string directory = null) {
for (string dir = null == directory ? Directory.GetCurrentDirectory() : directory;
dir != null;
dir = Directory.GetParent(dir)?.FullName)
yield return dir;
}
Demo:
var demo = string.Join(Environment.NewLine,
ParentDirectories(#"C:/Project/Source/Dev/Database"));
Console.Write(demo);
Outcome:
C:/Project/Source/Dev/Database // initial directory
C:\Project\Source\Dev // and its all parents
C:\Project\Source
C:\Project
C:\
If you don't want to include directory itself, add .Skip(1):
var demo = string.Join(Environment.NewLine,
ParentDirectories(#"C:/Project/Source/Dev/Database").Skip(1));
Finally, if you want to find out a parent directory which ends by Source:
string dirName = "Source";
string myParent = ParentDirectories(#"C:/Project/Source/Dev/Database")
.FirstOrDefault(dir => string.Equals(
dirName,
new DirectoryInfo(dir).Name,
StringComparison.OrdinalIgnoreCase));
Console.Write(myParent);
Outcome:
C:\Project\Source
Directory.GetCurrentDirectory() returns a string that represents the full absolute path of the current directory.
If you want to get the path of a specific parent directory, you can simply use substring:
var path = Directory.GetCurrentDirectory(); // Suppose C:/Project/Source/Dev/Database
var sourceDir = new string[] {Path.DirectorySeparatorChar + "Source" + Path.DirectorySeparatorChar,
Path.AltDirectorySeparatorChar + "Source" + Path.AltDirectorySeparatorChar};
var sourcePath = path.IndexOf(sourceDir[0], StringComparison.OrdinalIgnoreCase) > -1 ?
path.Substring(0, path.IndexOf(sourceDir[0]) + sourceDir[0].Length) :
path.IndexOf(sourceDir[1], StringComparison.OrdinalIgnoreCase) > -1 ?
path.Substring(0, path.IndexOf(sourceDir[1]) + sourceDir[1].Length) :
null;
I've used Path.DirectorySeparatorChar and Path.AltDirectorySeparatorChar as separators so that the code will work the same on each platform.
Two possibles without anymore additional information about your situation:
DirectoryInfo.GetDirectories would get you all the child directories of the Current directory so you would need to switch your current to the root + 1
System.IO.Path.Combine(myPath, "..") which would act more like cd ..
Actually I thought there is an already existing way or function but if I have to write it myself then this solution I wrote worked for me
private static string GetParent(string directoryPath, string parent)
{
DirectoryInfo directory = new DirectoryInfo(directoryPath);
while (directory.Parent!=null)
{
if (directory.Parent.Name.ToLower() == parent.ToLower())
{
return directory.Parent.FullName;
}
directory = directory.Parent;
}
return null;
}
When traversing any kind of sequence (a sequence of numbers, a sequence of directories, a sequence of nodes in a graph), you have the option of employing recursion.
Directory structures (when ignoring links of any kind) are a subset of the data structure known as a Directed Acyclic Graph (or DAG) - this subset typically doesn't rejoin and fans out forever (so long as the operating system allows for the given depth of your nested directories). We typically refer to this as a Tree structure.
When put in this light, its probably no surprise to most that the concept of recursion surfaces because its quite common for programmers to apply recursion to traverse down a tree. However, whether you traverse up or down a tree is simply an implementation detail.
There is no reason you cannot use recursion to also traverse up a tree.
Here is a simple console app (written using .net6 conventions & C#10 syntax) that you can study, and then perhaps apply to solve future problems.
using System.Runtime.InteropServices; // For the [Optional] attribute
var dir = Directory.GetCurrentDirectory();
var file = Directory.GetFiles(dir).First();
var projFile = DirectoryRecursor.RecurseUpwardsUntilFileIsFoundWith(".csproj", file, 5);
Console.WriteLine(projFile);
public static class DirectoryRecursor
{
public static FileInfo? RecurseUpwardsUntilFileIsFoundWith(
string stringToMatch,
string sourceFile,
[Optional] int? maxParentDirLevel)
{
if (maxParentDirLevel is not null && maxParentDirLevel == 0) return null; // try and stave off disaster
var dirName = Path.GetDirectoryName(sourceFile);
if (dirName is null) return null;
var csprojFile = Directory.GetFiles(dirName).Where(x => x.EndsWith(stringToMatch)).SingleOrDefault();
if (csprojFile is null)
{
if (ThereIsAParentDirectory(new DirectoryInfo(sourceFile), out var parentDir))
{
return RecurseUpwardsUntilFileIsFoundWith(stringToMatch, parentDir.FullName, maxParentDirLevel - 1);
}
else
{
return null;
}
}
return new FileInfo(csprojFile);
}
public static bool ThereIsAParentDirectory(DirectoryInfo dir, out DirectoryInfo parentDir)
{
if (dir.Parent is not null)
{
parentDir = dir.Parent;
return dir.Parent.Exists;
}
parentDir = dir;
return false;
}
}
A quick side note:
An example of applied recursion both up and down a tree (which I appologize is not currently open source), is the conversation builder over at www.palavyr.com. This conversation tree is implemented as a double linked list to facilitate recursion both up and down the conversation tree. I'm planning on producing a technical blog post explaining this implementation - I'll update this response with that once I've assembled it.
Related
My business logic is accepting a folder path string to read a folder/file. However as security dictates, the user can only access that folder. For example:
Their folder is: C:\foo\user\bar, they can access C:\foo\user\bar\data\.a.b..txt by https://www.example.com/download?path=data/.a.b..txt
However, I want to prevent user from entering something that could make them go up a folder and see data of other. Here is my current code:
var result = this.ResultFolder; // The user folder (\user\bar as in the example)
if (!string.IsNullOrEmpty(path))
{
path = path.Replace("/", #"\");
if (path.StartsWith(#"\"))
{
path = path.Substring(1);
}
if (path.StartsWith('\\') || path.Contains("..\\"))
{
throw new InvalidDataException("Forbidden Path.");
}
result = Path.Combine(result, path);
}
Basically, what I do:
Replace all / into \ so I only have to worry about one separation character.
The request allows path to start with \ , it counts as nothing.
Now if user try to be malicious (by using \\ to go to root directory), or trying to go up a level by using ..\ (note before I used .. only, but get false case because it is valid file/folder name)
Is it correct and safe yet? Is there any framework method that helps with this?
Here's a solution that uses Path.GetFullPath(string path):
Create this function:
private static bool VerifyPathUnderRoot(string pathToVerify, string rootPath = ".")
{
var fullRoot = Path.GetFullPath(rootPath);
var fullPathToVerify = Path.GetFullPath(pathToVerify);
return fullPathToVerify.StartsWith(fullRoot);
}
Then you can test it with code like this:
var paths = new[]
{
"somepath/somefile.xxx",
"..\\somepath/somefile.xxx",
#"C:\this\that\the.other",
};
foreach (var path in paths)
{
var isOk = VerifyPathUnderRoot(path);
var okString = isOk ? "OK" : "No";
Debug.WriteLine($"{okString}: {path}");
}
which results in this in the Output pane of the debugger:
OK: somepath/somefile.xxx
No: ..\somepath/somefile.xx
No: C:\this\that\the.other
I use GetFullPath twice to canonicalize the paths (making sure that all slashes end up the same, etc.).
I've a part of a string path: "\MVVM\MyFirstTest2016\MyFirstTest\bin\Debug\MyFirstTest.exe"
I want to search the above path in C: and need to get the complete full path of the directory.
I tried with Path.GetFullPath("") and other in-built methods but didnt get complete full path.
Here's the code:
The sourceDir will be the full path.
string defaultFolder = System.AppDomain.CurrentDomain.BaseDirectory;
string navigateToFolder = "\\MVVM\\MyFirstTest2016\\MyFirstTest\\bin\\Debug\\MyFirstTest.exe";
string sourceDir = Path.Combine(defaultFolder, navigateToFolder);
It sounds like your problem is similar to this question. You've got a partial path, but no idea where it is on this drive. The naive approach would be as follows.
You're first step would be to start at the root of the drive, and get the list of directories:
string[] dirs = Directory.GetDirectories(#"c:\", "*");
You'd then check if any of these strings matched the first directory of your root path (MVVM). If it does, you'd go into that folder and check if it contained the next directory. If it does, check the next and next, etc. until you've exhausted the path.
If not, you'd iterate over the directories, and run the same logic: get the directories, check if any match your first folder, etc.
So a bit of pseudo code would look like:
string directoryPath = "\MVVM\MyFirstTest2016\MyFirstTest\bin\Debug\MyFirstTest.exe"
string[] splitPath = directoryPath.split("\")
check("c:\")
public void check(string directory)
string[] directories = Directory.GetDirectories(#directory, "*")
if(checkDirectories(directories, splitPath))
// Success!
else
for(string subDirectory : directories)
string newDirectory = Path.combine(directory, subDirectory)
check(newDirectory)
public boolean checkDirectories(string[] directories, string[] splitPath)
// Horrible, but just for example - finding the file at the end
if(splitPath.size == 1)
// Get file list in current directory and check the last part of splitPath
if(directories.contains(splitPath[0])
// Recursively call checkDirectories with the sub directories of this folder, an splitPath missing the first item. This can be done using Array.Copy(splitPath, 1, newArray, 0)
Obviously that's nowhere near runnable, but it should give you the basic idea. The other question I linked earlier also has an accepted answer which will help more.
You could iterate over all directories and check if the sub directory is available.
The normal Directory.EnumerateDirectories may throw a UnauthorizedAccessException which stops the process of finding the directory.
So you could write your own EnumerateDirectories as shown below.
The example provided will return the folders found.
void Main()
{
string path = #"temp\A\B";
var parts = path.Split(new [] { Path.DirectorySeparatorChar });
var rootPath = "c:\\";
foreach(var result in DirectoryEx.EnumerateDirectories(rootPath, parts.First()))
{
var checkPath = Path.Combine(result, String.Join(""+Path.DirectorySeparatorChar, parts.Skip(1).ToArray()));
if (Directory.Exists(checkPath))
Console.WriteLine("Found : " + checkPath);
}
}
public class DirectoryEx
{
public static IEnumerable<string> EnumerateDirectories(string dir, string name)
{
IEnumerable<string> dirs;
// yield return may not be used in try with catch.
try { dirs = Directory.GetDirectories(dir); }
catch(UnauthorizedAccessException) { yield break; }
foreach (var subdir in dirs)
{
if(Path.GetFileName(subdir).Equals(name, StringComparison.OrdinalIgnoreCase))
yield return subdir;
foreach(var heir in EnumerateDirectories(subdir, name))
yield return heir;
}
}
}
In my case results in :
Found : c:\dev\temp\A\B
Found : c:\Temp\A\B
One option would be to do System.IO.Directory.GetParent() a few times. Is there a more graceful way of travelling a few folders up from where the executing assembly resides?
What I am trying to do is find a text file that resides one folder above the application folder. But the assembly itself is inside the bin, which is a few folders deep in the application folder.
Other simple way is to do this:
string path = #"C:\Folder1\Folder2\Folder3\Folder4";
string newPath = Path.GetFullPath(Path.Combine(path, #"..\..\"));
Note This goes two levels up. The result would be:
newPath = #"C:\Folder1\Folder2\";
Additional Note
Path.GetFullPath normalizes the final result based on what environment your code is running on windows/mac/mobile/...
if c:\folder1\folder2\folder3\bin is the path then the following code will return the path base folder of bin folder
//string directory=System.IO.Directory.GetParent(Environment.CurrentDirectory).ToString());
string directory=System.IO.Directory.GetParent(Environment.CurrentDirectory).ToString();
ie,c:\folder1\folder2\folder3
if you want folder2 path then you can get the directory by
string directory = System.IO.Directory.GetParent(System.IO.Directory.GetParent(Environment.CurrentDirectory).ToString()).ToString();
then you will get path as c:\folder1\folder2\
You can use ..\path to go one level up, ..\..\path to go two levels up from path.
You can use Path class too.
C# Path class
This is what worked best for me:
string parentOfStartupPath = Path.GetFullPath(Path.Combine(Application.StartupPath, #"../"));
Getting the 'right' path wasn't the problem, adding '../' obviously does that, but after that, the given string isn't usable, because it will just add the '../' at the end.
Surrounding it with Path.GetFullPath() will give you the absolute path, making it usable.
public static string AppRootDirectory()
{
string _BaseDirectory = AppDomain.CurrentDomain.BaseDirectory;
return Path.GetFullPath(Path.Combine(_BaseDirectory, #"..\..\"));
}
Maybe you could use a function if you want to declare the number of levels and put it into a function?
private String GetParents(Int32 noOfLevels, String currentpath)
{
String path = "";
for(int i=0; i< noOfLevels; i++)
{
path += #"..\";
}
path += currentpath;
return path;
}
And you could call it like this:
String path = this.GetParents(4, currentpath);
C#
string upTwoDir = Path.GetFullPath(Path.Combine(System.AppContext.BaseDirectory, #"..\..\"));
The following method searches a file beginning with the application startup path (*.exe folder). If the file is not found there, the parent folders are searched until either the file is found or the root folder has been reached. null is returned if the file was not found.
public static FileInfo FindApplicationFile(string fileName)
{
string startPath = Path.Combine(Application.StartupPath, fileName);
FileInfo file = new FileInfo(startPath);
while (!file.Exists) {
if (file.Directory.Parent == null) {
return null;
}
DirectoryInfo parentDir = file.Directory.Parent;
file = new FileInfo(Path.Combine(parentDir.FullName, file.Name));
}
return file;
}
Note: Application.StartupPath is usually used in WinForms applications, but it works in console applications as well; however, you will have to set a reference to the System.Windows.Forms assembly. You can replace Application.StartupPath by
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) if you prefer.
I use this strategy to find configuration and resource files. This allows me to share them for multiple applications or for Debug and Release versions of an application by placing them in a common parent folder.
Hiding a looped call to Directory.GetParent(path) inside an static method is the way to go.
Messing around with ".." and Path.Combine will ultimately lead to bugs related to the operation system or simply fail due to mix up between relative paths and absolute paths.
public static class PathUtils
{
public static string MoveUp(string path, int noOfLevels)
{
string parentPath = path.TrimEnd(new[] { '/', '\\' });
for (int i=0; i< noOfLevels; i++)
{
parentPath = Directory.GetParent(parentPath ).ToString();
}
return parentPath;
}
}
this may help
string parentOfStartupPath = Path.GetFullPath(Path.Combine(Application.StartupPath, #"../../")) + "Orders.xml";
if (File.Exists(parentOfStartupPath))
{
// file found
}
If you know the folder you want to navigate to, find the index of it then substring.
var ind = Directory.GetCurrentDirectory().ToString().IndexOf("Folderame");
string productFolder = Directory.GetCurrentDirectory().ToString().Substring(0, ind);
I have some virtual directories and I cannot use Directory methods. So, I made a simple split/join function for those interested. Not as safe though.
var splitResult = filePath.Split(new[] {'/', '\\'}, StringSplitOptions.RemoveEmptyEntries);
var newFilePath = Path.Combine(filePath.Take(splitResult.Length - 1).ToArray());
So, if you want to move 4 up, you just need to change the 1 to 4 and add some checks to avoid exceptions.
Path parsing via System.IO.Directory.GetParent is possible, but would require to run same function multiple times.
Slightly simpler approach is to threat path as a normal string, split it by path separator, take out what is not necessary and then recombine string back.
var upperDir = String.Join(Path.DirectorySeparatorChar, dir.Split(Path.DirectorySeparatorChar).SkipLast(2));
Of course you can replace 2 with amount of levels you need to jump up.
Notice also that this function call to Path.GetFullPath (other answers in here) will query whether path exists using file system. Using basic string operation does not require any file system operations.
Given a command-line style path to a command such as bin/server.exe or ping, how can I get the full path to this executable (as cmd or Process.Start would resolve it)?
I tried Path.GetFullPath, but it always expands relative to the working directory. It expands bin/server.exe correctly, however given ping it returns c:\users\matt\ping (non-existent). I want c:\Windows\system32\ping.exe.
Edit: I would like the same behaviour as cmd. Some considerations:
When there is a local executable with the same name as one in the path, cmd prefers the local one
cmd can expand the command server to server.bat or server.exe (adding the file extension)
I also tried Windows' command-line tool called where . It does almost I want:
Displays the location of files that match the search pattern. By default, the search is done along the current directory and in the paths specified by the PATH environment variable.
>where ping
C:\Windows\System32\PING.EXE
>where bin\server
INFO: Could not find files for the given pattern(s).
(This question is hard to search around because of the two different meanings of the word 'path')
Considering PATHEXT too, stealing from Serj-Tm's answer (sorry! +1 to him):
public static string WhereSearch(string filename)
{
var paths = new[]{ Environment.CurrentDirectory }
.Concat(Environment.GetEnvironmentVariable("PATH").Split(';'));
var extensions = new[]{ String.Empty }
.Concat(Environment.GetEnvironmentVariable("PATHEXT").Split(';')
.Where(e => e.StartsWith(".")));
var combinations = paths.SelectMany(x => extensions,
(path, extension) => Path.Combine(path, filename + extension));
return combinations.FirstOrDefault(File.Exists);
}
Sorry the indentation's a bit all-over-the-place - I was trying to make it not scroll. I don't know if the StartsWith check is really necessary - I'm not sure how CMD copes with pathext entries without a leading dot.
public static string GetFullPath(string filename)
{
return new[]{Environment.CurrentDirectory}
.Concat(Environment.GetEnvironmentVariable("PATH").Split(';'))
.Select(dir => Path.Combine(dir, filename))
.FirstOrDefault(path => File.Exists(path));
}
If you're only interested in searching the current directory and the paths specified in the PATH environment variable, you can use this snippet:
public static string GetFullPath(string fileName)
{
if (File.Exists(fileName))
return Path.GetFullPath(fileName);
var values = Environment.GetEnvironmentVariable("PATH");
foreach (var path in values.Split(';'))
{
var fullPath = Path.Combine(path, fileName);
if (File.Exists(fullPath))
return fullPath;
}
return null;
}
You have to search the entire disk.
Windows can respond to things like, iexplore, ping, cmd, etc, because they are in the registry under this key:
HKEY_LOCAL_MACHINE
SOFTWARE
Microsoft
Windows
CurrentVersion
App Paths
The only other way is to search the entire disk for the application.
EDIT: My understanding was, that you want to search for any random executable name, not the ones that are already known to Windows..
internal class Program
{
static void Main(string[] args)
{
string fullPath = GetExactPathFromEnvironmentVar("ping.exe");
if (!string.IsNullOrWhiteSpace(fullPath))
Console.WriteLine(fullPath);
else
Console.WriteLine("Not found");
}
static string GetExactPathFromEnvironmentVar(string program)
{
var pathVar = System.Environment.GetEnvironmentVariable("PATH");
string[] folders = pathVar.Split(';');
foreach (var folder in folders)
{
string path = Path.Combine(folder, program);
if (File.Exists(path))
{
return path;
}
}
return null;
}
}
HTH
I am just learning C# (have been fiddling with it for about 2 days now) and I've decided that, for leaning purposes, I will rebuild an old app I made in VB6 for syncing files (generally across a network).
When I wrote the code in VB 6, it worked approximately like this:
Create a Scripting.FileSystemObject
Create directory objects for the source and destination
Create file listing objects for the source and destination
Iterate through the source object, and check to see if it exists in the destination
if not, create it
if so, check to see if the source version is newer/larger, and if so, overwrite the other
So far, this is what I have:
private bool syncFiles(string sourcePath, string destPath) {
DirectoryInfo source = new DirectoryInfo(sourcePath);
DirectoryInfo dest = new DirectoryInfo(destPath);
if (!source.Exists) {
LogLine("Source Folder Not Found!");
return false;
}
if (!dest.Exists) {
LogLine("Destination Folder Not Found!");
return false;
}
FileInfo[] sourceFiles = source.GetFiles();
FileInfo[] destFiles = dest.GetFiles();
foreach (FileInfo file in sourceFiles) {
// check exists on file
}
if (optRecursive.Checked) {
foreach (DirectoryInfo subDir in source.GetDirectories()) {
// create-if-not-exists destination subdirectory
syncFiles(sourcePath + subDir.Name, destPath + subDir.Name);
}
}
return true;
}
I have read examples that seem to advocate using the FileInfo or DirectoryInfo objects to do checks with the "Exists" property, but I am specifically looking for a way to search an existing collection/list of files, and not live checks to the file system for each file, since I will be doing so across the network and constantly going back to a multi-thousand-file directory is slow slow slow.
Thanks in Advance.
The GetFiles() method will only get you files that does exist. It doesn't make up random files that doesn't exist. So all you have to do is to check if it exists in the other list.
Something in the lines of this could work:
var sourceFiles = source.GetFiles();
var destFiles = dest.GetFiles();
foreach (var file in sourceFiles)
{
if(!destFiles.Any(x => x.Name == file.Name))
{
// Do whatever
}
}
Note: You have of course no guarantee that something hasn't changed after you have done the calls to GetFiles(). For example, a file could have been deleted or renamed if you try to copy it later.
Could perhaps be done nicer somehow by using the Except method or something similar. For example something like this:
var sourceFiles = source.GetFiles();
var destFiles = dest.GetFiles();
var sourceFilesMissingInDestination = sourceFiles.Except(destFiles, new FileNameComparer());
foreach (var file in sourceFilesMissingInDestination)
{
// Do whatever
}
Where the FileNameComparer is implemented like so:
public class FileNameComparer : IEqualityComparer<FileInfo>
{
public bool Equals(FileInfo x, FileInfo y)
{
return Equals(x.Name, y.Name);
}
public int GetHashCode(FileInfo obj)
{
return obj.Name.GetHashCode();
}
}
Untested though :p
One little detail, instead of
sourcePath + subDir.Name
I would use
System.IO.Path.Combine(sourcePath, subDir.Name)
Path does reliable, OS independent operations on file- and foldernames.
Also I notice optRecursive.Checked popping out of nowhere. As a matter of good design, make that a parameter:
bool syncFiles(string sourcePath, string destPath, bool checkRecursive)
And since you mention it may be used for large numbers of files, keep an eye out for .NET 4, it has an IEnumerable replacement for GetFiles() that will let you process this in a streaming fashion.