I'm a newbie to programming, and have only been working with standard console programs written with C#.
I am currently on an internship, and I've been asked to design a little Tool for them.
To be fair, the assignment is way over my ahead, and nothing like what I have previously made with C#.
The Tool basicly has to do the following:
User choses a folder to be searched.
Program checks all files in the folder, and all sub folders, and
checks the Write-protection if not already checked.
Program sets read-only attribute on all files, if there is not currently.
If this is not the place to search for help, please disregard my question.
Thanks for reading.
This is pretty much a copy paste from this thread:
The complete code should look something like:
public void SetAllFilesAsReadOnly(string rootPath)
{
//this will go over all files in the directory and sub directories
foreach (string file in Directory.EnumerateFiles(rootPath, "*.*", SearchOption.AllDirectories))
{
//Getting an object that holds some information about the current file
FileAttributes attr = File.GetAttributes(file);
// set the file as read-only
attr = attr | FileAttributes.ReadOnly;
File.SetAttributes(file,attr);
}
}
Following your comments, Just for better understanding, let's break it into bits and pieces:
once you have the file path, create the file attribute object:
var attr = File.GetAttributes(path);
For the following, you might want to read a bit about enum flags and bitwise
this is how you set as Read only:
// set read-only
attr = attr | FileAttributes.ReadOnly;
File.SetAttributes(path, attr);
this is how you un-set as Read only:
// unset read-only
attr = attr & ~FileAttributes.ReadOnly;
File.SetAttributes(path, attr);
And for getting all files you can use:
foreach (string file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories))
{
Console.WriteLine(file);
}
You can check it via
FileAttributes attr = File.GetAttributes(path);
if(attr.HasFlag( FileAttributes.ReadOnly ))
{
//it is readonly
}
This MSDN thread introduces the following code sample for acquiring folder permissions:
DirectorySecurity dSecurity = Directory.GetAccessControl(#"d:\myfolder");
foreach (FileSystemAccessRule rule in dSecurity.GetAccessRules(true, true, typeof(NTAccount)))
{
if (rule.FileSystemRights == FileSystemRights.Read)
{
Console.WriteLine("Account:{0}", rule.IdentityReference.Value);
}
}
Check out DirectoryInfo. In particular the Attributes property.
Related
I want to continue my code when error comes up , but i dont know how ...
here's my code :
foreach(string path in Directory.GetDirectories(#"C:\", "*.*", SearchOption.AllDirectories)
{
Console.WriteLine(path);
}
And the error comes on foreach(string path in Directory.GetDirectories(#"C:\", "*.*", SearchOption.AllDirectories) and i don't know how to continue this loop
and the error :
Unauthorized access
And even i run my code as Administrator this error comes up again
Thanks,
The best is to use recursive search and not using SearchOption.AllDirectories, but rather SearchOption.TopDirectoryOnly
If you use 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.
Thus, to do it, you can create a method which receives a directory path as input. And in that method, if the input directory have child directory(ies) (see Directory.GetDirectories(string path) method, you call the method again for each child directory (recursive call) before you process all the files in the directory. Else, get the files (see Directory.GetFiles) in the directory and process them immediately.
Then for the method above, one way is to prevent the code crash when you cannot access certain file/directory is by using try-catch block for each child directory reading and file reading. This way, if one file/folder cannot be accessed, your code will still be running, finding the processing the next file/directory.
Alternatively, you can 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).
Edit (code added):
Something like this will do:
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
}
}
And to call it, you can do something like this
string rootpath = #"C:\DummyRootFolder";
List<string> dirList = GetAllAccessibleDirectories(rootpath, "*.*"); //you get all accessible directories here
In the dirList you will get all the directories that you search for, and if there is access violation along the way, it will only affects sub-directories search due to the try-catch block.
Note that the rootpath is excluded in the method. But if you want to add it to the list too, you could simply do
dirList.Insert(0, path); //do this after you get dirList
There are also more complicated ways of doing this by using Directory.GetAccessControl and PermissionSet
Hope it may clarify.
According to the documentation, you should look at EnumerateDirectories for performance reasons:
https://msdn.microsoft.com/en-us/library/c1sez4sc(v=vs.110).aspx
Also, it appears that this question has already been answered before:
Directory.EnumerateFiles => UnauthorizedAccessException
Hope this helps!
How about this:
foreach (string path in Directory.GetDirectories(#"C:\", "*.*", SearchOption.AllDirectories)) {
try {
Console.WriteLine(path);
} catch (Exception ex) {
Console.WriteLine("Unable to access directories in path: " + path);
}
}
So I have a directory where a bunch of configuration files are stored, but now I also have several sub directories which also contain config files. I had a function for collecting all configuration files per station, but now it's only collecting files in the directory, not the sub folders. old code:
string MasterList = "ConfigsForStation1.txt";
string dirC = "..\\config\\Station1\\";
foreach (FileInfo fileC in dirC.GetFiles())
{
if(!System.IO.Path.GetExtension(fileC.FullName).EndsWith(".confg"))
{ WriteToList(fileC,MasterList);}
}
and now with this sub directory stuff implemented its more along these lines:
string MasterList = ...;
string dirC = ...;
//collect files in sub directories substations 1 & 2
foreach(DirectoryInfo Dx in dirC.GetDirectories())
{ foreach(FileInfo fileC in Dx.GetFiles())
{...}
}
//collect files in parent directory station 1
foreach(FileInfo fileC in dirC.GetFiles())
{...}
my question is: is there a cleaner way to collect files from all sub folders rather then nest a foreach inside of a foreach? and then do a third pass for the stuff in parent. It feels abit sloppy and I feel like theres some command like dirC.getAllsub() that will do so for me? just looking for hints and ideas to clean up the code.
There's Directory.EnumerateFiles() to which you can pass a parameter of SearchOption.AllDirectories which you can use to tell it to recurse the files for you automatically.
You should be able to do something like:
foreach (string filename in Directory.EnumerateFiles(dirC, "*.confg", SearchOption.AllDirectories))
... Do something with filename
Or if you need to process every file:
foreach (string filename in Directory.EnumerateFiles(dirC, "*", SearchOption.AllDirectories))
... Do something with filename
The GetFiles() method will return them all in one shot:
String dirC = "..\\config\\Station1\\";
DirectoryInfo di = new DirectoryInfo(dirC);
FileInfo[] fia = di.GetFiles("*.*", SearchOption.AllDirectories);
foreach (FileInfo fi in fia)
{
//do something with the file
}
You can replace the search pattern *.* with whatever fits the files you are looking for, such as *.cfg.
Use Directory.GetFiles() with SearchOption.AllDirectories. That will automatically recurse for you.
I wanted to write this as a comment but stackoverflow wasn't letting me.
Check out this question which provides many ways to do what you're asking.
use Directory.GetFiles:
var files = Directory.GetFiles("C:\\", "*", SearchOption.AllDirectories);
Imagine I wish to create (or overwrite) the following file :- C:\Temp\Bar\Foo\Test.txt
Using the File.Create(..) method, this can do it.
BUT, if I don't have either one of the following folders (from that example path, above)
Temp
Bar
Foo
then I get an DirectoryNotFoundException thrown.
So .. given a path, how can we recursively create all the folders necessary to create the file .. for that path? If Temp or Bar folders exists, but Foo doesn't... then that is created also.
For simplicity, lets assume there's no Security concerns -- all permissions are fine, etc.
To summarize what has been commented in other answers:
//path = #"C:\Temp\Bar\Foo\Test.txt";
Directory.CreateDirectory(Path.GetDirectoryName(path));
Directory.CreateDirectory will create the directories recursively and if the directory already exist it will return without an error.
If there happened to be a file Foo at C:\Temp\Bar\Foo an exception will be thrown.
DirectoryInfo di = Directory.CreateDirectory(path);
Console.WriteLine("The directory was created successfully at {0}.",
Directory.GetCreationTime(path));
See this MSDN page.
Use Directory.CreateDirectory before you create the file. It creates the folder recursively for you.
. given a path, how can we recursively create all the folders necessary to create the file .. for that path
Creates all directories and subdirectories as specified by path.
Directory.CreateDirectory(path);
then you may create a file.
You will need to check both parts of the path (directory and filename) and create each if it does not exist.
Use File.Exists and Directory.Exists to find out whether they exist. Directory.CreateDirectory will create the whole path for you, so you only ever need to call that once if the directory does not exist, then simply create the file.
You should use Directory.CreateDirectory.
http://msdn.microsoft.com/en-us/library/54a0at6s.aspx
Assuming that your assembly/exe has FileIO permission is itself, well is not right. Your application may not run with admin rights. Its important to consider Code Access Security and requesting permissions
Sample code:
FileIOPermission f2 = new FileIOPermission(FileIOPermissionAccess.Read, "C:\\test_r");
f2.AddPathList(FileIOPermissionAccess.Write | FileIOPermissionAccess.Read, "C:\\example\\out.txt");
try
{
f2.Demand();
}
catch (SecurityException s)
{
Console.WriteLine(s.Message);
}
Understanding .NET Code Access Security
Is “Code Access Security” of any real world use?
You want Directory.CreateDirectory()
Here is a class I use (converted to C#) that if you pass it a source directory and a destination it will copy all of the files and sub-folders of that directory to your destination:
using System.IO;
public class copyTemplateFiles
{
public static bool Copy(string Source, string destination)
{
try {
string[] Files = null;
if (destination[destination.Length - 1] != Path.DirectorySeparatorChar) {
destination += Path.DirectorySeparatorChar;
}
if (!Directory.Exists(destination)) {
Directory.CreateDirectory(destination);
}
Files = Directory.GetFileSystemEntries(Source);
foreach (string Element in Files) {
// Sub directories
if (Directory.Exists(Element)) {
copyDirectory(Element, destination + Path.GetFileName(Element));
} else {
// Files in directory
File.Copy(Element, destination + Path.GetFileName(Element), true);
}
}
} catch (Exception ex) {
return false;
}
return true;
}
private static void copyDirectory(string Source, string destination)
{
string[] Files = null;
if (destination[destination.Length - 1] != Path.DirectorySeparatorChar) {
destination += Path.DirectorySeparatorChar;
}
if (!Directory.Exists(destination)) {
Directory.CreateDirectory(destination);
}
Files = Directory.GetFileSystemEntries(Source);
foreach (string Element in Files) {
// Sub directories
if (Directory.Exists(Element)) {
copyDirectory(Element, destination + Path.GetFileName(Element));
} else {
// Files in directory
File.Copy(Element, destination + Path.GetFileName(Element), true);
}
}
}
}
Following code will create directories (if not exists) & then copy files.
// using System.IO;
// for ex. if you want to copy files from D:\A\ to D:\B\
foreach (var f in Directory.GetFiles(#"D:\A\", "*.*", SearchOption.AllDirectories))
{
var fi = new FileInfo(f);
var di = new DirectoryInfo(fi.DirectoryName);
// you can filter files here
if (fi.Name.Contains("FILTER")
{
if (!Directory.Exists(di.FullName.Replace("A", "B"))
{
Directory.CreateDirectory(di.FullName.Replace("A", "B"));
File.Copy(fi.FullName, fi.FullName.Replace("A", "B"));
}
}
}
Is there a way to tell using C# if a file is real or a symbolic link?
I've dug through the MSDN W32 docs, and can't find anything for checking this. I'm using CreateSymbolicLink from here, and it's working fine.
private bool IsSymbolic(string path)
{
FileInfo pathInfo = new FileInfo(path);
return pathInfo.Attributes.HasFlag(FileAttributes.ReparsePoint);
}
I have some source code for symlinks posted on my blog that will allow you to:
create symlinks
check whether a path is a symlink
retrieve the target of a symlink
It also contains NUnit test cases, that you may wish to extend.
The meaty bit is:
private static SafeFileHandle getFileHandle(string path)
{
return CreateFile(path, genericReadAccess, shareModeAll, IntPtr.Zero, openExisting,
fileFlagsForOpenReparsePointAndBackupSemantics, IntPtr.Zero);
}
public static string GetTarget(string path)
{
SymbolicLinkReparseData reparseDataBuffer;
using (SafeFileHandle fileHandle = getFileHandle(path))
{
if (fileHandle.IsInvalid)
{
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
int outBufferSize = Marshal.SizeOf(typeof(SymbolicLinkReparseData));
IntPtr outBuffer = IntPtr.Zero;
try
{
outBuffer = Marshal.AllocHGlobal(outBufferSize);
int bytesReturned;
bool success = DeviceIoControl(
fileHandle.DangerousGetHandle(), ioctlCommandGetReparsePoint, IntPtr.Zero, 0,
outBuffer, outBufferSize, out bytesReturned, IntPtr.Zero);
fileHandle.Close();
if (!success)
{
if (((uint)Marshal.GetHRForLastWin32Error()) == pathNotAReparsePointError)
{
return null;
}
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
reparseDataBuffer = (SymbolicLinkReparseData)Marshal.PtrToStructure(
outBuffer, typeof(SymbolicLinkReparseData));
}
finally
{
Marshal.FreeHGlobal(outBuffer);
}
}
if (reparseDataBuffer.ReparseTag != symLinkTag)
{
return null;
}
string target = Encoding.Unicode.GetString(reparseDataBuffer.PathBuffer,
reparseDataBuffer.PrintNameOffset, reparseDataBuffer.PrintNameLength);
return target;
}
That is:
Open the file with CreateFile()
Call DeviceIoControl() to get the reparse point data (NOTE: it could be a junction point!)
Check out the returned data structure to inspect. The reparse tag will tell you if it is a junction point or symbolic link. This may be all you want to do.
Here is an example of differentiating files and directories from links to files and links to directories.
Links to either files or directories maintain their own attributes (creation date, permissions) separate from their targets.
File links can be deleted (e.g. using "del") without affecting the target file.
Directory links can be removed (e.g. "rmdir") without affecting the target directory. Take care when using "rd /s". This WILL remove the directory link target.
The key FileAttributes flag to check in both FileInfo and DirectoryInfo is FileAttributes.ReparsePoint.
static void Main( string[] args ) {
FileInfo file_info = new FileInfo(args[0]);
DirectoryInfo directory_info = new DirectoryInfo(args[0]);
bool is_file = file_info.Exists;
bool is_directory = directory_info.Exists;
if (is_file) {
Console.WriteLine(file_info.ToString() + " is a file");
if ( file_info.Attributes.HasFlag(FileAttributes.ReparsePoint) )
Console.WriteLine(args[0] + " is a Windows file link");
}
else if (is_directory) {
Console.WriteLine(directory_info.ToString() + " is a directory");
if ( directory_info.Attributes.HasFlag(FileAttributes.ReparsePoint) )
Console.WriteLine(args[0] + " is a Windows directory link");
}
It proves the above answers are not reliable.
Finally I got the right solution from MSDN:
To determine if a specified directory is a mounted folder, first call the GetFileAttributes function and inspect the FILE_ATTRIBUTE_REPARSE_POINT flag in the return value to see if the directory has an associated reparse point. If it does, use the FindFirstFile and FindNextFile functions to obtain the reparse tag in the dwReserved0 member of the WIN32_FIND_DATA structure. To determine if the reparse point is a mounted folder (and not some other form of reparse point), test whether the tag value equals the value IO_REPARSE_TAG_MOUNT_POINT. For more information, see Reparse Points.
Starting with .NET 6 you can use:
FileSystemInfo.LinkTarget Property
Property description:
Gets the target path of the link located in FullName, or null if this FileSystemInfo instance doesn't represent a link.
For example:
static bool IsSymbolicLink(string path)
{
FileInfo file = new FileInfo(path);
return file.LinkTarget != null;
}
GetFileInformationByHandle fills a BY_HANDLE_FILE_INFORMATION structure which has a field dwFileAttributes where bits are set with info about the file's attributes (details here). In particular, look at the bit at mask...:
FILE_ATTRIBUTE_REPARSE_POINT 1024 0x0400
A file or directory that has an
associated reparse point, or a file
that is a symbolic link.
According to this answer to Stack Overflow question Find out whether a file is a symbolic link in PowerShell, getting the System.IO.FileAttributes for the file (via File.GetAttributes), and testing for the ReparsePoint bit, works. If the bit is set, it is a symlink or a junction point. If not, it is a regular file (or hardlink).
The library MonoPosix provides API to check if a file is a symbolic link:
public bool IsSymlink(string filePath)
=> UnixFileSystemInfo.GetFileSystemEntry(filePath).IsSymbolicLink;
I know I am late to the party but found this discussion when researching same question
I found the below worked for me so thought I would post in case of use to anyone else
It works like this:-
var provider = ReparsePointFactory.Provider;
var link = provider.GetLink(#"c:\program files (x86)\common files\microsoft shared\vgx\vgx.dll");
MsgBox("Link Type: " + link.Type.ToString + " Link Target: " + link.Target + " Link Attributes: " + link.Attributes.ToString);
https://github.com/NCodeGroup/NCode.ReparsePoints https://www.nuget.org/packages/NCode.ReparsePoints/
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.