Is there any way to get Environment.SpecialFolder from file path? - c#

I have a requirement where I want to get the Environment.SpecialFolder value from a file path.
Eg -
string filePath = #"C:\Program Files (x86)\text.txt"
//SpecialFolder sf = GetSpecialFolderAssociatedWithPath(filePath); // sf will be ProgramFilesX86
//Need something similar
I want to further use the sf to generate another path, so if we get the path corresponding to that particular SpecialFolder, that will work as well.

You could do it something like this (assuming you want to get the actual enum value for the special folder):
public static Environment.SpecialFolder? FindSpecialFolder(string filePath)
{
filePath = Path.GetFullPath(filePath);
foreach (var folder in Enum.GetValues<Environment.SpecialFolder>())
{
string directory = Environment.GetFolderPath(folder);
if (directory.Length > 0 && filePath.StartsWith(directory, StringComparison.OrdinalIgnoreCase))
return folder;
}
return null;
}
Note that I had to return a nullable because Microsoft failed to follow their own guidelines and didn't include a special "None" zero value in the Environment.SpecialFolder enum that I could return to indicate "not found".
The usage would be something like this:
string filePath = #"C:\Program Files (x86)\text.txt";
var folder = FindSpecialFolder(filePath);
if (folder == null)
Console.WriteLine("No folder found");
else
Console.WriteLine(folder.Value);
If you want both the path and the enum value, you could return them both in a tuple:
public static (Environment.SpecialFolder? specialFolder, string? directory) FindSpecialFolder(string filePath)
{
filePath = Path.GetFullPath(filePath);
foreach (var folder in Enum.GetValues<Environment.SpecialFolder>())
{
string directory = Environment.GetFolderPath(folder);
if (directory.Length > 0 && filePath.StartsWith(directory, StringComparison.OrdinalIgnoreCase))
return (folder, directory);
}
return default;
}
Which you could use like:
var folder = FindSpecialFolder(filePath);
if (folder.specialFolder == null)
Console.WriteLine("No folder found");
else
Console.WriteLine($"{folder.specialFolder.Value}: {folder.directory}");
Actually, it's possible that some of the special folders may be nested underneath other special folders, for example you might have:
C:\Path1\Path2
C:\Path1\Path2\Path3
In that event, the code above will return the first path that it matches, rather than the longest; i.e. looking for "C:\Path1\Path2\Path3\SomeFile.txt" might return the special folder for "C:\Path1\Path2" rather than the one for "C:\Path1\Path2\Path3".
If you want to handle that possibility, you'll have to find the longest matching path, for example:
public static (Environment.SpecialFolder? specialFolder, string? directory) FindSpecialFolder(string filePath)
{
filePath = Path.GetFullPath(filePath);
int longest = 0;
Environment.SpecialFolder? longestFolder = null;
string? longestDir = null;
foreach (var folder in Enum.GetValues<Environment.SpecialFolder>())
{
string directory = Environment.GetFolderPath(folder);
if (directory.Length > longest && filePath.StartsWith(directory, StringComparison.OrdinalIgnoreCase))
{
longestDir = directory;
longestFolder = folder;
longest = directory.Length;
}
}
return (longestFolder, longestDir);
}
And use it like:
var folder = FindSpecialFolder(filePath);
if (folder.specialFolder == null)
Console.WriteLine("No folder found");
else
Console.WriteLine($"{folder.specialFolder.Value}: {folder.directory}");
Another thing to be aware of is that multiple special folders might have the same path. In this case it's not possible to differentiate them, so the code will just return the first match it finds.
Also note the use of filePath = Path.GetFullPath(filePath); to ensure that relative paths are converted to absolute paths, otherwise the matching likely wouldn't work.

I'm not aware of any existing function to do this, but rolling your own isn't too difficult. For example:
private static bool TryGetSpecialFolderAssociatedWithPath(string filePath, out Environment.SpecialFolder sf)
{
foreach (Environment.SpecialFolder value in Enum.GetValues(typeof(Environment.SpecialFolder)))
{
string path = Environment.GetFolderPath(value);
if (!string.IsNullOrEmpty(path) && filePath.StartsWith(path + "\\"))
{
sf = value;
return true;
}
}
sf = default; // Actually the same as Desktop
return false;
}
Usage:
string filePath = #"C:\Program Files (x86)\text.txt";
if (TryGetSpecialFolderAssociatedWithPath(filePath, out Environment.SpecialFolder sf))
{
Console.WriteLine("Special folder is " + sf);
}
This produces the following output:
Special folder is ProgramFilesX86
One fine point to note is that I append a backslash to the returned path. If I didn't, the code snippet would've hit ProgramFiles before ProgramFilesX86.

Related

Check file exists and has the right naming standard in C#

We have a process from third-party vendor to drop sales and invetory data everyday and could have any of the following scenarios
Drop the right file. (Naming standard: test.xls)
Drop the right file but not follow the right naming standard. (Other
names could be test_mmddyyyy or testmmddyyyy)
No file dropped.
I am trying to build my logic around these scenarios and stuck at how to build my logic when the file exists but does not have the right naming standard and check for this condition and change the name of the file to the appropriate naming standard.
public void Main()
{
try
{
string filefullpathname = #"C:\Temp\test.xls";
if (File.Exists(filefullpathname) == false)
{
Console.WriteLine("File does not exist in the path");
}
// file exists but right naming standard not followed (Other names could be test_mmddyyyy or testmmddyyyy)
// how to check for this condition and change the name of the file to the naming standard
else
{
string dirname = #"C:\Temp\";
DirectoryInfo directory = new DirectoryInfo(dirname);
string filepartialname = "test";
FileInfo[] fileindirectory = directory.GetFiles(filepartialname + "*");
foreach (FileInfo filename in fileindirectory)
{
string fullname = filename.FullName;
bool ind = Path.HasExtension(fullname);
if (ind == false)
{
File.Move(fullname, directory + filepartialname + ".xls");
}
else
{
File.Move(fullname, directory + filepartialname + ".xls");
}
}
}
Dts.TaskResult = (int)ScriptResults.Success;
}
catch (Exception error)
{
Console.WriteLine(error);
}
}
It is not really clear as to if it is only the file name or a missing extension. So I put in both.
public void Main()
{
try
{
string dirname = #"C:\Temp\";
DirectoryInfo directory = new DirectoryInfo(dirname);
string filepartialname = "test";
FileInfo[] fileindirectory = directory.GetFiles(filepartialname + "*");
foreach (FileInfo filename in fileindirectory)
{
if (filename.Extension == "")
{
//doesn't have an extension
}
else if (!Regex.IsMatch(filename.Name.Replace(filename.Extension, ""), #"^[A-Z|a-z]$"))
{
//contains more then just test
}
else
{
//file is good
}
}
}
catch (Exception error)
{
Console.WriteLine(error);
}
}
Your explanation of what your inputs could be, and how you want to move those inputs isn't super clear, but this should get you started:
var expectedFilename = Path.Combine(someOtherDirectory, "test.xls");
// Matches test* and *.xls
var relevantFiles = Directory
.EnumerateFiles(searchDirectory, "*", SearchOption.TopDirectoryOnly)
.Where(f => Path.GetFileName(f).StartsWith("test") || Path.GetExtension(f).Equals(".xls"))
foreach (var file in relevantFiles)
{
// If there's more than one file matching the pattern, last one in wins
File.Move(file, expectedFilename);
}

Iterate through directories recursively and handle relative paths

I have a text files with paths to different texts files with paths and so on. Like this:
C:\folder1\dirs1.txt:
folder2\dirs1.txt
folder2\dirs2.txt
folder3\dirs1.txt
C:\folder1\folder2\dirs1.txt:
folder4\dirs1.txt
folder4\dirs2.txt
I need to iterate through all files and print it, it's easy:
IEnumerable<string> ExtractAllPathsFromFile(string path) { ... }
void PrintAllPaths(string root)
{
var paths = ExtractAllPathsFromFile(root);
foreach (path in paths)
{
Console.WriteLine(path);
if (File.Exists(path))
PrintAllPaths(path);
}
}
PrintAllPaths(root:C:\folder1\dirs1.txt);
But as you can see in the example all paths are relative (not absolute), and I need to come up with the idea how to handle it in my algorithm.
Use the Path.Combine() methods to safely concatenate multiple paths. You can get the directory path from C:\folder1\dirs1.txt with the Path.GetDirectoryName() method. Wouldn't something like this work?
using System.IO;
public static string[] ExtractPathsFromFile(string originalPath)
{
string[] newPaths = File.ReadAllLines(originalPath);
string[] modified = new string[newPaths.Length];
for (int i = 0; i < newPaths.Length; i++)
modified[i] = Path.Combine(Path.GetDirectoryName(originalPath), newPaths[i].Trim());
return modified;
}
Eh, something like that?
String path = null;
var files = File
.ReadLines(#"C:\MyFiles.txt")
.Select(line => {
if (Path.IsPathRooted(line)) {
path = Path.GetDirectoryName(line);
return line;
}
else
return Path.Combine(path, line.Trim());
});
...
Console.WriteLine(String.Join(Environment.NewLine, files));
Probably you mean this:
IEnumerable<string> ExtractAllPathsFromFile(string path)
{
var basePath = Path.GetDirectoryName( path );
return File.ReadAllLines(path).Select (f => Path.Combine(basePath, f.Trim()));
}

Directory.GetFiles or DirectoryInfo.GetFiles returns empty list

I've got some strange behavior during execution in a ASP.NET application.
It doesn't matter if I set workingFolder (see code below) to System.IO.Path.GetTempPath or any other public folder (current case).
I receive a ZIP file, unpack that file (using SharpZipLib) and try to digest the files in that folder, but the System.IO GetFiles returns an empty list.
I've tried to use DirectoryInfo.GetFiles and Directory.GetFiles : both an empty list.
If I breakpoint on the Directory.Delete and look at the folder I can see the files, they're not locked and I can do anything with the files - even if I set the "run from cursor" point at the beginning of the foreach no luck - the GetFiles still return an empty list (although I can see the files in explorer).
private const string EXTENSION_LOG_FILE = ".txt";
private const string VALID_EXTENSION_MASK = "*." + EXTENSION_LOG_FILE;
var zipFolder = unpackZip(filePath, workingFolder);
foreach (var zipFileInfo in new DirectoryInfo(zipFolder).GetFiles(VALID_EXTENSION_MASK, SearchOption.TopDirectoryOnly))
{
// never get's here
value.AddRange(getLogItems(zipFileInfo.FullName));
File.Delete(zipFileInfo.FullName);
}
// this fails: folder is not empty
Directory.Delete(zipFolder);
and the unpackZip method:
private static string unpackZip(string zipFile, string workingFolder)
{
// doesn't matter what name I use, GUID or no GUID the GetFiles still returns an empty lists
var tempFolder = Path.Combine(workingFolder, Guid.NewGuid().ToString());
Directory.CreateDirectory(tempFolder);
using (var unzipStream = new ZipInputStream(File.OpenRead(zipFile)))
{
ZipEntry entry;
while ((entry = unzipStream.GetNextEntry()) != null)
{
var fileName = Path.GetFileName(entry.Name);
if (fileName == string.Empty) continue;
using (var streamWriter = File.Create(Path.Combine(tempFolder, Path.GetFileName(entry.Name))))
{
var size = 2048;
var data = new byte[2048];
while (size > 0)
{
size = unzipStream.Read(data, 0, data.Length);
streamWriter.Write(data, 0, size);
}
}
}
}
return tempFolder;
}
any suggestions?
Problem which I guessed in
private const string VALID_EXTENSION_MASK = "*." + EXTENSION_LOG_FILE;
retruns *..txt because EXTENSION_LOG_FILE = ".txt"

C# structure for If.. And If... And If

I am using Visual Studio 2013 Express and I am new to Visual C#. I am sure there is a better way to do what I am trying and I would appreciate any suggestions.
The code I'm trying to write evaluates a series of tests and sets a flag only if all tests = TRUE. I'm currently using six nested if structures to get this done and, while it works, I'm looking for a cleaner, more professional solution. Here's the sample (shortened to three levels for this post):
private const string sDrive = "D:\\";
private const string sFolder = "FTP\\";
private const string sDivFolder = "ABC";
private static bool bSanity = false;
private static void SanityCheck()
{
if (Directory.Exists(sDrive))
{
if (Directory.Exists(sDrive + sFolder))
{
if (Directory.Exists(sDrive + sFolder + sDivFolder))
{
bSanity = true;
}
else
{
Console.WriteLine("FATAL: Div folder doesn't exist.");
}
}
else
{
Console.WriteLine("FATAL: Root folder doesn't exist.");
}
}
else
{
Console.WriteLine("FATAL: Disk drive doesn't exist.");
}
}
The main issue is whether you need to keep the same error reporting you have now. In general, if that is required, I think this is simpler to handle by inverting the cases. That will allow you to remove the nesting by using if/else if:
private static void SanityCheck()
{
if (!Directory.Exists(sDrive))
{
Console.WriteLine("FATAL: Disk drive doesn't exist.");
}
else if (!Directory.Exists(Path.Combine(sDrive, sFolder))
{
Console.WriteLine("FATAL: Root folder doesn't exist.");
}
else if (!Directory.Exists(Path.Combine(sDrive, sFolder, sDivFolder))
{
Console.WriteLine("FATAL: Div folder doesn't exist.");
}
else
{
bSanity = true;
}
}
If the detailed error reporting is not required, you can just check for the lowest level folder directly:
private static void SanityCheck()
{
if (Directory.Exists(Path.Combine(sDrive, sFolder, sDivFolder))
bSanity = true;
else
Console.WriteLine("FATAL: Drive or folder doesn't exist.");
}
if (Directory.Exists(sDrive) && Directory.Exists(sDrive + sFolder) && Directory.Exists(sDrive + sFolder + sDivFolder))
{
bSanity = true;
}
else
{
Console.WriteLine("FATAL: Disk drive doesn't exist.");
}
&& is an early exit operator.
How about using a loop and an array?
// set path array
var paths = new[] { sDrive, sFolder, sDivFolder };
// use StringBuilder for faster string concatenation
var sb = new StringBuilder();
foreach (var p in paths)
{
// append next part of the path
sb.Append(p);
// check if it exists
if (!Directory.Exists(sb.ToString()))
{
// print info message and return from method, because path is incorrect
Console.WriteLine("FATAL: \"{0}\" path doesn't exist.", sb.ToString());
return;
}
}
// we are here, so the whole path works and we can set bSanity to true
bSanity = true;
You can easily manipulate how deap the check is by changing array length. And it will print you exactly what part of the path is not correct.
So one possible cleaner solution might be this:
private static List<Tuple<string, string>> _dir = new List<Tuple<string, string>>
{
Tuple.Create(#"D:\", "FATAL: Disk drive doesn't exist."),
Tuple.Create("FTP", "FATAL: Root folder doesn't exist."),
Tuple.Create("ABC", "FATAL: Div folder doesn't exist."),
}
private static void SanityCheck()
{
var path = string.Empty;
foreach (var t in _dir)
{
path = Path.Combine(path, t.Item1);
if (!Directory.Exists(path))
{
Console.WriteLine(t.Item2);
break;
}
}
}
This isn't exactly the same behaviour as the original, but it is much simpler.
if (Directory.Exists(Path.Combine(sDrive, sFolder, sDivFolder))
bSanity = true;
else
Console.WriteLine("FATAL: Div folder doesn't exist.");

Getting the folder name from a full filename path

string path = "C:\folder1\folder2\file.txt";
What objects or methods could I use that would give me the result folder2?
I would probably use something like:
string path = "C:/folder1/folder2/file.txt";
string lastFolderName = Path.GetFileName( Path.GetDirectoryName( path ) );
The inner call to GetDirectoryName will return the full path, while the outer call to GetFileName() will return the last path component - which will be the folder name.
This approach works whether or not the path actually exists. This approach, does however, rely on the path initially ending in a filename. If it's unknown whether the path ends in a filename or folder name - then it requires that you check the actual path to see if a file/folder exists at the location first. In that case, Dan Dimitru's answer may be more appropriate.
Try this:
string filename = #"C:/folder1/folder2/file.txt";
string FolderName = new DirectoryInfo(System.IO.Path.GetDirectoryName(filename)).Name;
Simple & clean. Only uses System.IO.FileSystem - works like a charm:
string path = "C:/folder1/folder2/file.txt";
string folder = new DirectoryInfo(path).Name;
DirectoryInfo does the job to strip directory name
string my_path = #"C:\Windows\System32";
DirectoryInfo dir_info = new DirectoryInfo(my_path);
string directory = dir_info.Name; // System32
I used this code snippet to get the directory for a path when no filename is in the path:
for example "c:\tmp\test\visual";
string dir = #"c:\tmp\test\visual";
Console.WriteLine(dir.Replace(Path.GetDirectoryName(dir) + Path.DirectorySeparatorChar, ""));
Output:
visual
string Folder = Directory.GetParent(path).Name;
var fullPath = #"C:\folder1\folder2\file.txt";
var lastDirectory = Path.GetDirectoryName(fullPath).Split('\\').LastOrDefault();
It's also important to note that while getting a list of directory names in a loop, the DirectoryInfo class gets initialized once thus allowing only first-time call. In order to bypass this limitation, ensure you use variables within your loop to store any individual directory's name.
For example, this sample code loops through a list of directories within any parent directory while adding each found directory-name inside a List of string type:
[C#]
string[] parentDirectory = Directory.GetDirectories("/yourpath");
List<string> directories = new List<string>();
foreach (var directory in parentDirectory)
{
// Notice I've created a DirectoryInfo variable.
DirectoryInfo dirInfo = new DirectoryInfo(directory);
// And likewise a name variable for storing the name.
// If this is not added, only the first directory will
// be captured in the loop; the rest won't.
string name = dirInfo.Name;
// Finally we add the directory name to our defined List.
directories.Add(name);
}
[VB.NET]
Dim parentDirectory() As String = Directory.GetDirectories("/yourpath")
Dim directories As New List(Of String)()
For Each directory In parentDirectory
' Notice I've created a DirectoryInfo variable.
Dim dirInfo As New DirectoryInfo(directory)
' And likewise a name variable for storing the name.
' If this is not added, only the first directory will
' be captured in the loop; the rest won't.
Dim name As String = dirInfo.Name
' Finally we add the directory name to our defined List.
directories.Add(name)
Next directory
Below code helps to get folder name only
public ObservableCollection items = new ObservableCollection();
try
{
string[] folderPaths = Directory.GetDirectories(stemp);
items.Clear();
foreach (string s in folderPaths)
{
items.Add(new gridItems { foldername = s.Remove(0, s.LastIndexOf('\\') + 1), folderpath = s });
}
}
catch (Exception a)
{
}
public class gridItems
{
public string foldername { get; set; }
public string folderpath { get; set; }
}
An alternative can be to split the path and get the 2nd last element from the path using Index Struct introduced in C# 8.0.
var path = #"C:\folder1\folder2\file.txt";
var folder = path.Split(#"\")[^2]; // 2nd element from the end
Console.WriteLine(folder); // folder2
I don't know why anyone, did not highlight this solution:
string path = "C:/folder1/folder2/file.txt";
string folder = new DirectoryInfo(path).Parent.Name;
Output: folder2
This is ugly but avoids allocations:
private static string GetFolderName(string path)
{
var end = -1;
for (var i = path.Length; --i >= 0;)
{
var ch = path[i];
if (ch == System.IO.Path.DirectorySeparatorChar ||
ch == System.IO.Path.AltDirectorySeparatorChar ||
ch == System.IO.Path.VolumeSeparatorChar)
{
if (end > 0)
{
return path.Substring(i + 1, end - i - 1);
}
end = i;
}
}
if (end > 0)
{
return path.Substring(0, end);
}
return path;
}

Categories

Resources