How to iterate through files and skip the ones to which I don't have access - vb.net

I want to get all files in a directory, so I have used For Each loop.
but I got this error and stopped the loop.
System.UnauthorizedAccessException: 'Access to the path
'G:\$RECYCLE.BIN\S-1-5-18' is denied.'
The loop cannot access some of the files because of file permissions. I want to by-pass those files and move to the next file.
how can I do this,
Try
For Each filename As String In Directory.GetFiles(dir_path, pattern, sub_directory)
Console.WriteLine(filename.ToString)
Next filename
Catch ex As Exception
End Try

The loop is irrelevant. It's the call to GetFiles that is the problem. There is no way to call GetFiles with the recursive option and simply ignore inaccessible folders. You can only use that method when you know you can access every subfolder. Otherwise, you need to write your own recursive file search and explicitly catch those exceptions and ignore them. There are lots of examples of that on the web.
It should be noted that GetFiles does the entire search first, creates an array containing all the file paths and returns it, so your loop can't even begin until that's done. Even if this could work, it would still generally be preferable to call EnumerateFiles if you want to loop through the file paths on the spot. That's because EnumerateFiles is an iterator, basically returning the file paths one by one. That means that, for instance, you can break out of the loop if a particular condition is met without completing the entire search. EnumerateFiles will still throw an exception if it finds an inaccessible folder though, so it won't help here. If you do write your own method though, it would be nice to write an iterator if you plan to search big folders or you might not need to use all results.
EDIT:
Here's my home-spun versions. The first one does all the work first while the second is an iterator. Note that you may be able to make the first one more efficient by using a single external list to store all the file paths in rather than creating a new one in every recursive call.
Public Function GetFilesRecursively(path As String, searchPattern As String) As String()
Dim filePaths As New List(Of String)(Directory.GetFiles(path, searchPattern))
For Each folderPath In Directory.GetDirectories(path)
Try
filePaths.AddRange(GetFilesRecursively(folderPath, searchPattern))
Catch ex As UnauthorizedAccessException
'Ignore inaccessible folders
End Try
Next
Return filePaths.ToArray()
End Function
Public Iterator Function EnumerateFilesRecursively(path As String, searchPattern As String) As IEnumerable(Of String)
For Each filePath In Directory.EnumerateFiles(path, searchPattern)
Yield filePath
Next
For Each folderPath In Directory.EnumerateDirectories(path)
Try
For Each filePath In EnumerateFilesRecursively(folderPath, searchPattern)
Yield filePath
Next
Catch ex As UnauthorizedAccessException
'Ignore inaccessible folders
End Try
Next
End Function

Related

How to double check if folder is empty`?

I can't find a solution for my problem. My Code is deleting empty folders and in general working fine, but there is one exception. It goes through every path one time, but if there was a Folder (A) that only has empty Folder (B) in it, then it would only delete Folder(B), since Folder (A) was not at empty at the time. How can I make it, so that it understands that Folder (A) is gonna be empty, once Folder (B) is deleted?
I thought DeleteEmptyFolder(folder.FullName) would solve the problem, but it is not working, since it doesn't repeat the same path it already went through. Removing directory.GetDirectories.Count = 0 doesn't work either, since it would delete any folder that doesn't have a file in it (even if there is another folder with files in it)
Private Sub DeleteEmptyFolder(ByVal sDirectoryPath As String)
If IO.Directory.Exists(sDirectoryPath) Then
Dim directory As New IO.DirectoryInfo(sDirectoryPath)
If directory.GetDirectories.Count = 0 AndAlso directory.GetFiles.Count = 0 Then
directory.Delete(True)
Return
End If
For Each folder As IO.DirectoryInfo In directory.GetDirectories()
DeleteEmptyFolder(folder.FullName)
Next
End If
End Sub
I am fairly new to VB.Net, so pardon if it is an obvious answer that I don't see.
Here's how the code ought to look:
Private Sub DeleteEmptyFolder(folderPath As String)
If Directory.Exists(folderPath) Then
For Each subFolderPath In Directory.EnumerateDirectories(folderPath)
DeleteEmptyFolder(subFolderPath)
Next
If Directory.EnumerateFiles(folderPath).Any() OrElse
Directory.EnumerateDirectories(folderPath).Any() Then
Return
End If
Directory.Delete(folderPath)
End If
End Sub
There's no point using DirectoryInfo if you need no other information about files and folders other than path. You should use EnumerateFiles and EnumerateDirectories over GetFiles and GetDirectories unless you specifically need to get an array of entries up front. In this case, you definitely don't. Let's say that you had a folder with 1000 files in it. This:
directory.GetFiles.Count = 0
would create an array containing an element for all 1000 files first, then check the number of elements in it. On the other hand, this:
Directory.EnumerateFiles(folderPath).Any()
would return True as soon as it encountered the first file, ignoring the other 999. You only care whether there's any files in the folder, not how many there are.
Please try this:
Private Sub deleteEmptyFolders(ByRef folder As String)
'Does exist such a path?
If IO.Directory.Exists(folder) Then 'yes
'Loop over all directories
For Each subFolder In IO.Directory.GetDirectories(folder)
'Delete all empty folders
deleteEmptyFolders(subFolder)
Next
'Delete folder if nothing remained in it
Try
My.Computer.FileSystem.DeleteDirectory(folder, FileIO.DeleteDirectoryOption.ThrowIfDirectoryNonEmpty)
Catch ex As Exception
End Try
End If
End Sub
I think this does what you want in a simple way.
I solved the problem by moving some of the code around.
Private Sub DeleteEmptyFolder(ByVal sDirectoryPath As String)
If IO.Directory.Exists(sDirectoryPath) Then
Dim directory As New IO.DirectoryInfo(sDirectoryPath)
For Each folder As IO.DirectoryInfo In directory.GetDirectories()
DeleteEmptyFolder(folder.FullName)
Next
If directory.GetDirectories.Count = 0 AndAlso directory.GetFiles.Count = 0 Then
directory.Delete(True)
Return
End If
End If
End If

Limiting the amount of files grabbed from system.io.directory.getfiles

I've got a folder browser dialogue populating the directory location (path) of a system.io.directory.getfiles. The issue is if you accidentally select a folder with hundereds or thousands of files (which there's no reason you would ever need this for this app) it will lock up the app while it grabs all the files. All I'm grabbing are the directory locations as strings and want to put a limit on the amount of files that can be grabbed. Here's my current code that isn't working.
If JigFolderBrowse.ShowDialog = DialogResult.OK Then
Dim dirs(50) As String
dirs = System.IO.Directory.GetFiles(JigFolderBrowse.SelectedPath.ToString, "*", System.IO.SearchOption.AllDirectories)
If dirs.Length> 50 Then
MsgBox("Too Many Files Selected" + vbNewLine + "Select A Smaller Folder To Be Organized")
Exit Sub
End If
'Seperate Each File By Type
For i = 0 To dirs.Length - 1
If Not dirs(i).Contains("~$") Then
If dirs(i).Contains(".SLDPRT") Or dirs(i).Contains(".sldprt") Then
PartsListBx.Items.Add(dirs(i))
ElseIf dirs(i).Contains(".SLDASM") Or dirs(i).Contains(".sldasm") Then
AssemListBx.Items.Add(dirs(i))
ElseIf dirs(i).Contains(".SLDDRW") Or dirs(i).Contains(".slddrw") Then
DrawingListBx.Items.Add(dirs(i))
ElseIf dirs(i).Contains(".pdf") Or dirs(i).Contains(".PDF") Then
PDFsListBx.Items.Add(dirs(i))
ElseIf dirs(i).Contains(".DXF") Or dirs(i).Contains(".dxf") Then
DXFsListBx.Items.Add(dirs(i))
ElseIf Not dirs(i).Contains(".db") Then
OtherFilesListBx.Items.Add(dirs(i))
End If
End If
The Directory.GetFiles method always retrieves the full list of matching files before returning. There is no way to limit it (outside of specifying a more narrow search pattern, that is). There is, however, the Directory.EnumerateFiles method which does what you need. From the MSDN article:
The EnumerateFiles and GetFiles methods differ as follows: When you use EnumerateFiles, you can start enumerating the collection of names before the whole collection is returned; when you use GetFiles, you must wait for the whole array of names to be returned before you can access the array. Therefore, when you are working with many files and directories, EnumerateFiles can be more efficient.
So, for instance, you could do something like this:
dirs = Directory.
EnumerateFiles(
JigFolderBrowse.SelectedPath.ToString(),
"*",
SearchOption.AllDirectories).
Take(50).
ToArray()
Take is a LINQ extension method which returns only the first x-number of items from any IEnumerable(Of T) list. So, in order for that line to work, you'll need to import the System.Linq namespace. If you can't, or don't want to, use LINQ, you can just implement your own method that does the same sort of thing (iterates an IEnumerable list in a for loop and returns after reading only the first 50 items).
Side Note 1: Unused Array
Also, it's worth mentioning, in your code, you initialize your dirs variable to point to a 50-element string array. You then, in the very next line, set it to point to a whole new array (the one returned by the Directory.GetFiles method). While it's not breaking functionality, it is unnecessarily inefficient. You're creating that extra array, just giving the garbage collector extra work to do, for no reason. You never use that first array. It just gets dereferenced and discarded in the very next line. It would be better to create the array variable as null:
Dim dirs() As String
Or
Dim dirs() As String = Nothing
Or, better yet:
Dim dirs() As String = Directory.
EnumerateFiles(
JigFolderBrowse.SelectedPath.ToString(),
"*",
SearchOption.AllDirectories).
Take(50).
ToArray()
Side Note 2: File Extension Comparisons
Also, it looks like you are trying to compare the file extensions in a case-insensitive way. There are two problems with the way you are doing it. First, you only comparing it against two values: all lowercase (e.g. ".pdf") and all uppercase (e.g. ".PDF). That won't work with mixed-case (e.g. ".Pdf").
It is admittedly annoying that the String.Contains method does not have a case-sensitivity option. So, while it's a little hokey, the best option would be to make use of the String.IndexOf method, which does have a case-insensitive option:
If dirs(i).IndexOf(".pdf", StringComparison.CurrentCultureIgnoreCase) <> -1 Then
However, the second problem, which invalidates my last point of advice, is that you are checking to see if the string contains the particular file extension rather than checking to see if it ends with it. So, for instance, a file name like "My.pdf.zip" will still match, even though it's extension is ".zip" rather than ".pdf". Perhaps this was your intent, but, if not, I would recommend using the Path.GetExtension method to get the actual extension of the file name and then compare that. For instance:
Dim ext As String = Path.GetExtension(dirs(i))
If ext.Equals("pdf", StringComparison.CurrentCultureIgnoreCase) Then
' ...

VB How do I add items to a class list using iteration?

This is a very simple question.
I am trying to add items to the Inventory class list from a .txt file. However i can't use the word 'New' in this line otherwise it give a syntax error:
New Inventory("Rod", 1)
Function GetInventory() As IEnumerable(Of Inventory)
If System.IO.File.Exists(loc) = True Then
If System.IO.File.ReadAllText(loc).Count > 0 Then
Dim file As System.IO.StreamReader
file = My.Computer.FileSystem.OpenTextFileReader(loc)
Do While file.Peek() >= 0
New Inventory("Rod", 1)
Loop
Return New Inventory
file.Close()
End If
End If
End Function
How do I go about this???
Thank you.
With what you have, you're reading the entire file into memory twice. That's crazy wasteful.
Also, you don't need the .Exists() check here. The file system is volatile, meaning it's possible for the file to cease to be available between when you check .Exists() and when you try to access the file. A good program will still have a good exception handler for when the file does not exist, and now that you have an exception handler, the .Exists() check is just redundant. It's not saving you the performance hit you likely think it is. Also, this method is probably the wrong place to handle the exception. That's usually better to do in the calling code somewhere, meaning you can skip error checking here completely.
You can get this whole method down to a single statement that will cut your execution time in half:
Function GetInventory(ByVal loc As String) As IEnumerable(Of Inventory)
Return IO.File.ReadLines(loc).Select(Function(i) New Inventory(i, 1))
End Function

How to use Directory.EnumerateFiles

msdn (https://msdn.microsoft.com/en-us/library/dd383458(v=vs.110).aspx) says:
The EnumerateFiles and GetFiles methods differ as follows: When you use EnumerateFiles, you can start enumerating the collection of names before the whole collection is returned; when you use GetFiles, you must wait for the whole array of names to be returned before you can access the array. Therefore, when you are working with many files and directories, EnumerateFiles can be more efficient.
How can I start using the collection before the whole collection is returned?
The following code gives an elapsed time of more than 3 minutes for a directory with around 45000 files
Dim TIme1, TIme2 As String
TIme1 = TimeString
Dim DirFiles As Generic.List(Of String) = New Generic.List(Of String)(Directory.EnumerateFiles(SourceDirectory))
Dim NumberOfFiles As Integer
NumberOfFiles = DirFiles.Count()
TIme2 = TimeString
MsgBox("Begin time " & TIme1 & "There are " & NumberOfFiles & " Photos in the Directory ." & SourceDirectory & "End Time " & TIme2)
Can I already use entries in Dirfiles before the collection is entirely read? How?
I used to be a professional programmer before Microsoft launched Windows. My experience with windows programming is minimal.
While you can't make good use of count of files returned by EnumerateFiles, you can start working with individual files in the collection without any delay with For Each loop etc. which don't need the count of elements for its working.
So for example you can do:
Dim FileCount As Integer
Dim files = Directory.EnumerateFiles(srcDir)
For Each file in files
'Do something with this file
' e.g.
TextBox1.AppendText(file & vbCrLf)
FileCount += 1
Next
MsgBox ( FileCount.ToString & " files processed.")
So you see how it can be used?
[NB: freehand typed code..might contain typos. It is only meant to explain the concept.]
EnumerateFiles allows you to start processing files before all the files have been found. It appears that you want to know the number of files. You can't know that until all the files have been found, so EnumerateFiles doesn't help you in this case.
The signature for GetFiles is Directory.GetFiles(path As String) As String(). For it to return you results it must hit the hard drive and build the entire array first. If there are 45,000 files then it must build an array of 45,000 elements before it can give you a result.
The signature for EnumerateFiles is Directory.EnumerateFiles(path As String) As IEnumerable(Of String). In this case it doesn't need to hit the hard drive at all to give you a response. So you should be able to get a result almost instantly regardless of the number of files.
Take this test code:
Dim sw = Stopwatch.StartNew()
Dim files = Directory.GetFiles("C:\Windows\System32")
sw.Stop()
Console.WriteLine(sw.Elapsed.TotalMilliseconds)
I get a result of about 6.5 milliseconds to return the files.
But if I change GetFiles to EnumerateFiles I get a result back in 0.07 milliseconds. It's nearly 100 times slower to call GetFiles for this folder!
This is because EnumerateFiles return an IEnumerable<string>. The interface for IEnumerable(Of T) is:
Public Interface IEnumerable(Of Out T)
Inherits IEnumerable
Function GetEnumerator() As IEnumerator(Of T)
End Interface
Whenever we call foreach or .Count() or .ToArray() on an enumerable under the hood we are calling GetEnumerator() which in turn returns another object of type IEnumerator(Of T) with this signature:
Public Interface IEnumerator(Of Out T)
Inherits IDisposable
Inherits IEnumerator
ReadOnly Property Current As T
Function MoveNext() As Boolean
Sub Reset()
End Interface
It's this enumerator that actually does the hard work of returning all of the files. As soon as the first call to MoveNext is made the first file name is immediately available in Current. Then MoveNext is called in a loop until it returns a false and you then know the loop is over. Meanwhile you can collect all of the files from the Current property.
So, in your code, if you were performing some action over each and every file returned then EnumerateFiles would be the way to go.
But since you are doing New Generic.List(Of String)(Directory.EnumerateFiles(SourceDirectory)) you are forcing the iteration of the entire enumerable immediately. Any advantage of using EnumerateFiles is immediately lost.
GetFiles method will materialize the entire list of files that are in a directory. The preferred method to call now is Directory.EnumerateFiles as it will stream the files back (through a yield-like mechanism) as the underlying call to the OS yields the results back.
Solutions using the GetFiles/GetDirectories are kind of slow since the objects need to be created. Using the enumeration on the other hand doesnt do this, it doesn't create any temporary objects.
Either way in the end theres still iteration happening...
Example file count...
Directory.EnumerateFiles(directory, filetype, SearchOption.AllDirectories).Count()
I now use the following before enumeratefiles is started
Public Function FileCount(PathName As String) As Long
Dim fso As Scripting.FileSystemObject
Dim fld As Scripting.Folder
fso = CreateObject("Scripting.FileSystemObject")
If fso.FolderExists(PathName) Then
fld = fso.GetFolder(PathName)
FileCount = fld.Files.Count
End If
End Function
This needs Microsoft Scripting Runtime (set a reference to the VB script run-time library in your Project)

Unable to delete all files from temporary folders in vb.net

I am using the following code to delete all files from a particular folder:
Sub DeleteFiles(Folder As String)
If Directory.Exists(Folder) Then
For Each _file As String In Directory.GetFiles(Folder)
File.Delete(_file)
Next
For Each _folder As String In Directory.GetDirectories(Folder)
DeleteFiles(_folder)
Next
End If
End Sub
Whenever I use the above code to delete all files from "C:\Temp" by calling it using DeleteFiles("C:\Temp"), It deletes all the files successfully, but whenever I try to use the same code for deleting files in "C:\Windows\TEMP\", it breaks the operation saying that the file is in use. I want that the code should not raise an exception and stop deleting the files right-away. If the file cannot be deleted, the code should move on to the next file and try deleting it. This way, it should be able to delete maximum possible files from that directory.
You need to handle exceptions raised by File.Delete() using a Try....Catch... statement. I'm not a VB coder (I'm surprised to find myself answering this question), but something like this should work:
Sub DeleteFiles(Folder As String)
If Directory.Exists(Folder) Then
For Each _file As String In Directory.GetFiles(Folder)
Try
File.Delete(_file)
Catch e As System.IO.IOException
Console.WriteLine(e.Message)
End Try
Next
For Each _folder As String In Directory.GetDirectories(Folder)
DeleteFiles(_folder)
Next
End If
End Sub
This will catch an System.IO.IOException exception, log that it was received, and then ignore it. Note that this will catch a number of other File.Delete() related exceptions such as System.IO.DirectoryNotFoundException, System.IO.PathTooLongException, etc. If you want to catch these, you must add a Catch clause for each before the more general System.IO.IOException. The possible exceptions are listed here, along with an example of using File.Delete() - you just need to read the docs.
You might like to also look at Directory.Delete to recursively delete a directory, its files, and its subdirectories.
You can't stop an exception being thrown. What you need to do is catch the exception, process it as appropriate (which may mean just ignoring it) and move on. That means putting a Try...Catch block inside your first loop. That way, when File.Delete throws an exception, you can catch it, ignore it and the loop will continue.
Be sure to catch only the type of exception that you expect to be thrown though. Otherwise, something completely unexpected may be causing an issue and you're just ignoring it, which is bad. Only ignore exceptions that you reasonably expect and know can be safely ignored.
Dim temp As String = Environment.GetEnvironmentVariable("TEMP")
Dim k As String() = System.IO.Directory.GetFiles(temp)
Dim i As Integer
For i = 0 To k.Length
On Error Resume Next
Kill(k(i))
System.IO.File.Delete(k(i))
Next