Get file information from recursive folders - c#

What I'd like to do is get 6 pieces of file information from every file in a folder, and all folders beneath it. These pieces are:
Type (you can do this in Access, it will return "Microsoft Excel Worksheet" or "Winzip File" or something, just like you see in the Explorer window, but I didn't see a property for this in C#)
FullName
Length
CreationTime
LastAccessTime
LastWriteTime
I have the following code, which will spit out a list of files complete with path, but I'm trying to figure out how to get that additional info in my output. If it can be separated by a semicolon or something, that would be good as the eventual desire is to write this info to a table. Any help is appreciated.
private void GetMyFiles () {
string rootDirectory = #"" + txtPathToScan.Text + "";
string extension = "*";
List<string> files = GetFilesRecursively (rootDirectory, extension);
Console.WriteLine ("Got {0} files of extension {1} in directory {2}", files.Count, extension, rootDirectory);
foreach (string file in files) {
Console.WriteLine (file);
}
}
/// <summary>
/// This function calls itself recursively for each of the subdirectories that it finds
/// in the root directory passed to it. It returns files of the extension as specified
/// by the caller.
/// </summary>
/// <param name="rootDirectory">The directory from which the file list is sought.</param>
/// <param name="extension">The particular extension for which the file list is sought.</param>
/// <returns>A list of all the files with extension as specified by the caller. This list
/// includes the files in the current directory as well its sub-directories.</returns>
static List<string> GetFilesRecursively (string rootDirectory, string extension) {
// Uncomment this line only if you want to trace the control as it goes from
// sub-directory to sub-directory. Be ready for long scrolls of text output:
//Console.WriteLine ("Currently in directory {0}", rootDirectory);
// Create an output list:
List<string> opList = new List<string> ();
// Get all files in the current directory:
string[] allFiles = Directory.GetFiles (rootDirectory, "*." + extension);
// Add these files to the output list:
opList.AddRange (allFiles);
// Get all sub-directories in current directory:
string[] subDirectories = Directory.GetDirectories (rootDirectory);
// And iterate through them:
foreach (string subDir in subDirectories) {
// Get all the files from the sub-directory:
List<string> subDirFileList = GetFilesRecursively (subDir, extension);
// And add it to this list:
opList.AddRange (subDirFileList);
}
// Finally return the output list:
return opList;
}

You can get most of these using the FileInfo class....
var info = new System.IO.FileInfo(#"c:\folder\file.ext");
It has properties for Length, CreationTime, LastAccessTime and LastWriteTime, to output them (for example) to the console, you could do...
Console.WriteLine("For file {0}", info.Name);
Console.WriteLine("Length is {0}", info.Length);
Console.WriteLine("Creation time is {0}", info.CreationTime);
Console.WriteLine("Last access time is {0}", info.LastAccessTime);
Console.WriteLine("Last write time is {0}", info.LastWriteTime);
To get the full filename, you can use GetFileNameWithoutExtension...
var fileName = System.IO.Path.GetFileNameWithoutExtension(#"c:\folder\file.ext")
filename will equal "file" in the above example.
Getting the description of a file extension is a bit more work, but can be done using SHGetFIleInfo, See this answer for details.
To integrate this with your current code (if you're not so worried about the file descriptions), you could change your GetFilesRecursively method to return a List<FileInfo>...
static List<FileInfo> GetFilesRecursively (string rootDirectory, string extension)
Update your opList variable accordingly...
List<FileInfo> opList = new List<FileInfo> ();
And then tweak your AddRange call to add FileInfo objects...
opList.AddRange (allFiles.Select(f => new FileInfo(f)));
Lastly, the type of subDirFileList will need to be changed as well, so a List<FileInfo>.
(I think that's everything!)

For posterity, the completed answer is as below. Please only vote for Richard's answer if you're going to upvote anything. Thanks!
private void GetMyFiles () {
string rootDirectory = #"" + txtPathToScan.Text + "";
string extension = "*";
List<FileInfo> files = GetFilesRecursively (rootDirectory, extension);
Console.WriteLine ("Got {0} files of extension {1} in directory {2}", files.Count, extension, rootDirectory);
foreach (FileInfo file in files)
{
Console.WriteLine(file);
var info = new System.IO.FileInfo(#"" + file + "");
Console.WriteLine("For file {0}", info.Name);
Console.WriteLine("Length is {0} KB", info.Length/1024);
Console.WriteLine("Creation time is {0}", info.CreationTime);
Console.WriteLine("Last access time is {0}", info.LastAccessTime);
Console.WriteLine("Last write time is {0}", info.LastWriteTime);
}
}
/// <summary>
/// This function calls itself recursively for each of the subdirectories that it finds
/// in the root directory passed to it. It returns files of the extension as specified
/// by the caller.
/// </summary>
/// <param name="rootDirectory">The directory from which the file list is sought.</param>
/// <param name="extension">The particular extension for which the file list is sought.</param>
/// <returns>A list of all the files with extension as specified by the caller. This list
/// includes the files in the current directory as well its sub-directories.</returns>
static List<FileInfo> GetFilesRecursively(string rootDirectory, string extension)
{
// Uncomment this line only if you want to trace the control as it goes from
// sub-directory to sub-directory. Be ready for long scrolls of text output:
//Console.WriteLine ("Currently in directory {0}", rootDirectory);
// Create an output list:
List<FileInfo> opList = new List<FileInfo>();
// Get all files in the current directory:
string[] allFiles = Directory.GetFiles (rootDirectory, "*." + extension);
// Add these files to the output list:
opList.AddRange(allFiles.Select(f => new FileInfo(f)));
// Get all sub-directories in current directory:
string[] subDirectories = Directory.GetDirectories (rootDirectory);
// And iterate through them:
foreach (string subDir in subDirectories) {
// Get all the files from the sub-directory:
List<FileInfo> subDirFileList = GetFilesRecursively (subDir, extension);
// And add it to this list:
opList.AddRange (subDirFileList);
}
// Finally return the output list:
return opList;
}

Related

How to check if there are folders in a path and open them in c#

I have been trying to make a console app that asks you for the path and extension and prints all the names of files that have the extension inputted in the path, but I want to make it so that if there are any more folders in the path then it will open the folder and search for a file with the extension inputted by the user.
Here is my code:
Console.WriteLine("What is the path?");
string path = Console.ReadLine();
Console.WriteLine("What is the extension?");
string extension = Console.ReadLine();
string[] files = System.IO.Directory.GetFiles(path, $"*{extension}");
foreach(string file in System.IO.Directory.GetFiles(path,"*.txt"))
{
Console.WriteLine(Path.GetFileName(file));
}
my code just looks for the file only in the path provided, but it doesn't look into a folder and search for a file
Thanks in advance
This method should accomplish what you want. You'll have to supply the folder and the extension as "\folder\*.ext" or similar as the parameter.
/// <summary>
/// Returns a list of files matching the wildcard.
/// The directory specified in the wildcard and all its child directories are searched.
/// If no directory part is specified, the current directory and its children are searched.
/// </summary>
/// <param name="wildcard">Wild card to match files (required).</param>
/// <returns>A list of files matching the wildcard.</returns>
public static List<string> getFilesMatchingWildcardRecursive(string wildcard)
{
if (string.IsNullOrEmpty(wildcard))
{
throw new InvalidDataException($"Required parameter {nameof(wildcard)} is null or empty.");
}
else
{
string directory = Path.GetDirectoryName(wildcard);
if (string.IsNullOrEmpty(directory)) { directory = "."; }
string filepart = Path.GetFileName(wildcard);
return new List<string>(Directory.GetFiles(directory, filepart, SearchOption.AllDirectories));
}
}

Access to the path 'd:\$RECYCLE.BIN\S-1-5-21-494745725-312220573-749543506-41600' is denied

I am new to C# . I have a text box where i enter the file to search and a 'search' button. on clock of search i want it to populate the files in the folder but i get the above error. Below is my code:
string[] directories = Directory.GetDirectories(#"d:\",
"*",
SearchOption.AllDirectories);
string file = textBox1.Text;
DataGrid dg = new DataGrid();
{
var files = new List<string>();
foreach (DriveInfo d in DriveInfo.GetDrives().Where(x => x.IsReady))
{
try
{
files.AddRange(Directory.GetFiles(d.RootDirectory.FullName, file , SearchOption.AllDirectories));
}
catch(Exception ex)
{
MessageBox.Show("the exception is " + ex.ToString());
//Logger.Log(e.Message); // Log it and move on
}
}
Please help me resolve it . Thanks
The most important rule when searching on a folder which potentially contains inaccessible subfolder is:
Do NOT use SearchOption.AllDirectories!
Use SearchOption.TopDirectoryOnly instead, combined with recursive search for all the accessible directories.
Using SearchOption.AllDirectories, one access violation will break your entire loop even before any file/directory is processed. But if you use SearchOption.TopDirectoryOnly, you only skip what is inaccessible.
There is more difficult way to use Directory.GetAccessControl() per child directory check to see if you have an access to a Directory before hand (this option is rather hard though - I don't really recommend this unless you know exactly how the access system works).
For recursive search, I have this code implemented for my own use:
public static List<string> GetAllAccessibleDirectories(string path, string searchPattern) {
List<string> dirPathList = new List<string>();
try {
List<string> childDirPathList = Directory.GetDirectories(path, searchPattern, SearchOption.TopDirectoryOnly).ToList(); //use TopDirectoryOnly
if (childDirPathList == null || childDirPathList.Count <= 0) //this directory has no child
return null;
foreach (string childDirPath in childDirPathList) { //foreach child directory, do recursive search
dirPathList.Add(childDirPath); //add the path
List<string> grandChildDirPath = GetAllAccessibleDirectories(childDirPath, searchPattern);
if (grandChildDirPath != null && grandChildDirPath.Count > 0) //this child directory has children and nothing has gone wrong
dirPathList.AddRange(grandChildDirPath.ToArray()); //add the grandchildren to the list
}
return dirPathList; //return the whole list found at this level
} catch {
return null; //something has gone wrong, return null
}
}
This is how you call it
List<string> accessibleDirs = GetAllAccessibleDirectories(myrootpath, "*");
Then, you only need to search/add the files among all accessible directories.
Note: this question is quite classical though. I believe there are some other better solutions out there too.
And in case there are some directories which you particularly want to avoid after you get all your accessible directories, you could also filter the List result by LINQ using part of the directory's name as keyword (i.e. Recycle.Bins).
As Ian has specified in his post, do not use recursive file listing (Directory.GetFiles(path, searchPattern, SearchOption.AllDirectories)) in case like yours, since the first exception will stop further processing.
Also, to somewhat alleviate such issues and for better results in general, you should run this program as an Administrator. This can be done by right-clicking your application in windows explorer, and then checking Run this program as an administrator option on Compatibility tab.
Also, you should use code like below to do your search, so the intermediate exceptions do not stop further searching.
static void Main(string[] args) {
string fileToFind = "*.jpg";
var files = new List<string>();
foreach (DriveInfo d in DriveInfo.GetDrives().Where(x => x.IsReady))
files.AddRange(FindDirectory(fileToFind, d.RootDirectory.FullName));
}
/// <summary>
/// This function returns the full file path of the matches it finds.
/// 1. It does not do any parameter validation
/// 2. It searches recursively
/// 3. It eats up any error that occurs when requesting files and directories within the specified path
/// 4. Supports specifying wildcards in the fileToFind parameter.
/// </summary>
/// <param name="fileToFind">Name of the file to search, without the path</param>
/// <param name="path">The path under which the file needs to be searched</param>
/// <returns>Enumeration of all valid full file paths matching the file</returns>
public static IEnumerable<string> FindDirectory(string fileToFind, string path) {
// Check if "path" directly contains "fileToFind"
string[] files = null;
try {
files = Directory.GetFiles(path, fileToFind);
} catch { }
if (files != null) {
foreach (var file in files)
yield return file;
}
// Check all sub-directories of "path" to see if they contain "fileToFInd"
string[] subDirs = null;
try {
subDirs = Directory.GetDirectories(path);
} catch { }
if (subDirs == null)
yield break;
foreach (var subDir in subDirs)
foreach (var foundFile in FindDirectory(fileToFind, subDir))
yield return foundFile;
}

Trouble specifying destination filename for use in FileInfo.Copy example from MSDN

I'm using two DateTimePickers to specify a date range, then I'm using a CheckedListBox to specify some strings for filenames with wildcards to enumerate in each day's subdirectory contained within a system environment variable path. I want to copy from that source to a destination using FileInfo.Copy.
I have my code already creating the necessary directories. But I'm having trouble specifying the destination filenames -- they are not being specified at all with how I have this written.
I was thinking of using regular expressions, but after some digging I found this MSDN article that seems to do what I want already. I think I need to alter my code in order to use it. I could use some assistance fitting what I already have into what MSDN shows in its example.
I have been on this part of my program for a month now, which has led me to learn quite a bit about c#, parallel programming, async, lambda expressions, background workers, etc. What seems should be simple has become a big rabbit hole for me. For this question I just need a nudge in the right direction, and I will greatly appreciate it!
Here is my code as it stands:
private async void ProcessFiles()
{
// create a list of topics
var topics = topicsBox.CheckedItems.Cast<string>().ToList();
// create a list of source directories based on date range
var directories = new List<string>();
var folders = new List<string>();
for (DateTime date = dateTimePicker1.Value.Date;
date.Date <= dateTimePicker2.Value.Date;
date = date.AddDays(1))
{
directories.Add(_tracePath + #"\" + date.ToString("yyyy-MM-dd") + #"\");
folders.Add(#"\" + date.ToString("yyyy-MM-dd") + #"\");
}
// create a list of source files to copy and destination
// revise based on https://msdn.microsoft.com/en-us/library/kztecsys.aspx?f=255&MSPPError=-2147217396
foreach (var path in directories)
{
var path1 = path;
try
{
foreach (var files2 in folders)
{
// create the target directory
var destPath = textBox1.Text + #"\" + textBox4.Text + files2;
Console.WriteLine("Target directory is {0}", destPath);
Console.WriteLine("Destination filename is {0}", files2);
foreach (var files in topics)
{
foreach (string sourcePath in Directory.EnumerateFiles(path1, files + "*.*", SearchOption.AllDirectories))
{
// copy the files to the temp folder asynchronously
Console.WriteLine("Copy {0} to {1}", sourcePath, destPath);
Directory.CreateDirectory(sourcePath.Replace(sourcePath, destPath));
}
}
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
So sourcePath contains the source path and filename. You can easily construct the destination path from that, like so:
// Get just the filename of the source file.
var filename = Path.GetFileName(sourcePath);
// Construct a full path to the destination by combining the destination path and the filename.
var fullDestPath = Path.Combine(destPath, filename);
// Ensure the destination directories exist. Don't pass in the filename to CreateDirectory!
Directory.CreateDirectory(destPath);
Then you can copy the file (synchronously) like this:
File.Copy(sourcePath, fullDestPath);

FindFirstFile with IO.Directory.GetFiles

I have a situation where I have to find a path to the first file named my.exe starting from startingdirectory & \mydir\ and go deep as needed.
Actually, IO.Directory.GetFiles is suitable but I need it stop searching after the first file is found like it is possible with FindFirstFile from WinAPI.
VB.NET
Dim findedDirectories() As String = IO.Directory.GetFiles( _
startingdirectory & "\mydir\", "my.exe", IO.SearchOption.AllDirectories)
C#
string[] findedDirectories = IO.Directory.GetFiles( _
startingdirectory + "\\mydir\\", "my.exe", IO.SearchOption.AllDirectories);
Is it possible to stop searching after the first file is found in a way that the result of the function will be a string or an empty string, not a string array? Or is here better way to search for a first file in subdirectories?
A solution like the following one could help:
/// <summary>
/// Searches for the first file matching to searchPattern in the sepcified path.
/// </summary>
/// <param name="path">The path from where to start the search.</param>
/// <param name="searchPattern">The pattern for which files to search for.</param>
/// <returns>Either the complete path including filename of the first file found
/// or string.Empty if no matching file could be found.</returns>
public static string FindFirstFile(string path, string searchPattern)
{
string[] files;
try
{
// Exception could occur due to insufficient permission.
files = Directory.GetFiles(path, searchPattern, SearchOption.TopDirectoryOnly);
}
catch (Exception)
{
return string.Empty;
}
// If matching files have been found, return the first one.
if (files.Length > 0)
{
return files[0];
}
else
{
// Otherwise find all directories.
string[] directories;
try
{
// Exception could occur due to insufficient permission.
directories = Directory.GetDirectories(path);
}
catch (Exception)
{
return string.Empty;
}
// Iterate through each directory and call the method recursivly.
foreach (string directory in directories)
{
string file = FindFirstFile(directory, searchPattern);
// If we found a file, return it (and break the recursion).
if (file != string.Empty)
{
return file;
}
}
}
// If no file was found (neither in this directory nor in the child directories)
// simply return string.Empty.
return string.Empty;
}
I guess the simplest approach would be to organise the recursion into sub-directories yourself with recursive calls to Directory.GetDirectories passing SearchOption.TopDirectoryOnly. In each directory check for the file's existence with File.Exists.
This actually mirrors the way it would be done in Win32 with FindFirstFile. When using FindFirstFile you always need to implement the sub-directory recursion yourself because FindFirstFile has nothing analagous to SearchOption.AllDirectories.

c# - Function to replicate the folder structure in the file path

I need a simple function which will take a FileInfo and a destination_directory_name as input, get the file path from the fileinfo and replicate it in the destination_directory_name passed as the second parameter.
for ex. filepath is "d:\recordings\location1\client1\job1\file1.ext
the function should create the directories in the destination_directory_name if they dont exist and copy the file after creating the directories.
System.IO.Directory.CreateDirectory can be used to create the final directory, it will also automatically create all folders in the path if they do not exist.
//Will create all three directories (if they do not already exist).
System.IO.Directory.CreateDirectory("C:\\First\\Second\\Third")
I'm using the following method for that purpose:
public static void CreateDirectory(DirectoryInfo directory)
{
if (!directory.Parent.Exists)
CreateDirectory(directory.Parent);
directory.Create();
}
Use it in this way:
// path is your file path
string directory = Path.GetDirectoryName(path);
CreateDirectory(new DirectoryInfo(directory));
Based on #NTDLS's answer here's a method that will replicate source to destination. It handles case where source is a file or a folder. Function name kind of stinks... lemme know if you think of a better one.
/// <summary>
/// Copies the source to the dest. Creating any neccessary folders in the destination path as neccessary.
///
/// For example:
/// Directory Example:
/// pSource = C:\somedir\conf and pDest=C:\somedir\backups\USER_TIMESTAMP\somedir\conf
/// all files\folders under source will be replicated to destination and any paths in between will be created.
/// </summary>
/// <param name="pSource">path to file or directory that you want to copy from</param>
/// <param name="pDest">path to file or directory that you want to copy to</param>
/// <param name="pOverwriteDest">if true, files/directories in pDest will be overwritten.</param>
public static void FileCopyWithReplicate(string pSource, string pDest, bool pOverwriteDest)
{
string destDirectory = Path.GetDirectoryName(pDest);
System.IO.Directory.CreateDirectory(destDirectory);
if (Directory.Exists(pSource))
{
DirectoryCopy(pSource, pDest,pOverwriteDest);
}
else
{
File.Copy(pSource, pDest, pOverwriteDest);
}
}
// From MSDN Aricle "How to: Copy Directories"
// Link: http://msdn.microsoft.com/en-us/library/bb762914.aspx
private static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs)
{
DirectoryInfo dir = new DirectoryInfo(sourceDirName);
DirectoryInfo[] dirs = dir.GetDirectories();
if (!dir.Exists)
{
throw new DirectoryNotFoundException(
"Source directory does not exist or could not be found: "
+ sourceDirName);
}
if (!Directory.Exists(destDirName))
{
Directory.CreateDirectory(destDirName);
}
FileInfo[] files = dir.GetFiles();
foreach (FileInfo file in files)
{
string temppath = Path.Combine(destDirName, file.Name);
file.CopyTo(temppath, true);
}
if (copySubDirs)
{
foreach (DirectoryInfo subdir in dirs)
{
string temppath = Path.Combine(destDirName, subdir.Name);
DirectoryCopy(subdir.FullName, temppath, copySubDirs);
}
}
}
Similar to the question, I am copying a folder structure from one destination and duplicating it to another. Sorry for posting to an old thread, but it may be useful for someone that ends up here.
Let's assume that we have an application that is standalone, and we need to copy the folder structure from an Input folder to an Output folder. The Input folder and Output folder is in the root directory of our application.
We can make a DirectoryInfo for the Input folder (structure we want to copy) like this:
DirectoryInfo dirInput = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory + "\\Input\\");
Our output folder location can be stored in a string.
string dirOutput = AppDomain.CurrentDomain.BaseDirectory + "\\Output\\";
This recursive method should handle the rest for us.
public static void ProcessDirectories(DirectoryInfo dirInput, string dirOutput)
{
string dirOutputfix = String.Empty;
foreach (DirectoryInfo di in dirInput.GetDirectories())
{
dirOutputfix = dirOutput + "\\" + di.Name);
if (!Directory.Exists(dirOutputfix))
{
try
{
Directory.CreateDirectory(dirOutputfix);
}
catch(Exception e)
{
throw (e);
}
}
ProcessDirectories(di, dirOutputfix);
}
}
This method can be easily modified to handle files also, but I designed it with only folders in mind for my project.
Now we just call the method with our dirInput and dirOutput.
ProcessDirectories(dirInput, dirOutput);

Categories