How to search a folder with its subfolders and save the results to an array VB.NET - vb.net

I am trying to execute a search on a folder and get an array of every result back. I found this code but it doesn't go into subfolders:
Dim Results As New List(Of String)
For Each strFileName As String In IO.Directory.GetFiles("pathToSearch")
If strFileName.Contains("searchTerm") Then
Results.Add(strFileName)
End If
Next
How can I do exactly this, but also look into the subfolders?
I'm not very knowledgeable about the search options in VB.NET yet, so I apologize in advance if this seems stupid. I have tried searching online but haven't found anything. I can't have a single string, it needs to be an array (this needs to be interpreted by the machine later in the program)
Thanks for any help

No recursion required. There is already an overload for this. You just need to call it with appropriate search option.
e.g. To list all txt files in the directory as well as the subdirectories you can do:
Dim foundFiles() As String = System.IO.Directory.GetFiles("path/to/dir", "*.txt", System.IO.SearchOption.AllDirectories)

In order to get the subfolders you might try some recursive function

Unless there's a file system search API with which I'm unfamiliar, this is going to involve a recursive method to perform the searching into sub-directories.
Helpfully, Microsoft even has a complete example of something very similar available. In VB it might look something like this (my VB is very rusty and this is free-hand code, by the way...):
Function FindFiles(ByVal dir As String, ByVal searchTerm As String) As List(Of String)
Dim Results As New List(Of String)
' search files in this directory
For Each strFileName As String In IO.Directory.GetFiles(dir)
If strFileName.Contains(searchTerm) Then
Results.Add(strFileName)
End If
Next
' recurse into child directories
For Each strDirectoryName As String In IO.Directory.GetDirectories(dir)
Results = Results.AddRange(FindFiles(strDirectoryName, searchTerm)
Next
Return Results
End Function

Make a recursive function that keeps calling itself until all subdirectories are checked:
Private Function GetAllFileNamesFromDirectory(ByVal strPath As String, ByVal strSearchTerm As String) As List(Of String)
Dim lstFileNames As New List(Of String)
Dim lstSubDirectories As List(Of String) = IO.Directory.GetDirectories(strPath).ToList()
Dim lstFilesToAdd As List(Of String) = IO.Directory.GetFiles(strPath).ToList()
For Each strFileToAdd As String In lstFilesToAdd
If strFileToAdd.Contains(strSearchTerm) Then
'Additional logic would be required to filter out the directory name.
lstFileNames.Add(strFileToAdd)
End If
Next
If lstSubDirectories.Count > 0 Then
lstSubDirectories.ForEach(Sub(strDirectoryPath As String)
Dim lstSubDirectoryFilesToAdd As List(Of String) = GetAllFileNamesFromDirectory(strDirectoryPath, strSearchTerm)
If lstSubDirectoryFilesToAdd.Count > 0 Then
lstFileNames.AddRange(lstSubDirectoryFilesToAdd)
End If
End Sub)
End If
Return lstFileNames
End Function

Related

VB.net Apply function to items in a list

I have a list of string containing full file paths and I'd like to apply a function to each path in that list and get the result in the same or a new list.
Dim Remove As New List(Of String)
Remove.Add("C:\_Vault\Designs\Jobs\Customer\Job23\Assemblies\045-0201.iam")
Remove.Add("C:\_Vault\Designs\Jobs\Customer\Job23\Parts\212-D017.ipt")
Remove.Add("C:\_Vault\Designs\Jobs\Customer\Job23\Parts\211-W01.iam")
Function FileName(spth As String) As String
'Returns filename with extension from full path
Return System.IO.Path.GetFileName(spth)
End Function
The end result I'd like is for the list Remove to contain the following. I know I could use a loop to do this but I've been learning about lambda expressions lately and feel there should be a simple solution to this.
{"045-0201.iam", "212-D017.ipt", "211-W01.iam"}
Try this
Dim Remove As New List(Of String)
Remove.Add("C:\_Vault\Designs\Jobs\Customer\Job23\Assemblies\045-0201.iam")
Remove.Add("C:\_Vault\Designs\Jobs\Customer\Job23\Parts\212-D017.ipt")
Remove.Add("C:\_Vault\Designs\Jobs\Customer\Job23\Parts\211-W01.iam")
Remove = Remove.Select(Function(s)
Return IO.Path.GetFileName(s)
End Function).ToList
Calling Select and ToList on the existing List is most likely fine and what most people would do. It's worth being aware, though, that that will not modify the existing collection but rather return a new one. If you only have the one reference to that list then that's not a big deal but other references to the existing list will not see the change, e.g.
Dim fileNames As New List(Of String) From {"C:\Folder\File1.ext",
"C:\Folder\File2.ext",
"C:\Folder\File3.ext"}
Dim temp = fileNames
fileNames = fileNames.Select(Function(s) Path.GetFileName(s)).ToList()
For Each fileName In fileNames
Console.WriteLine(fileName)
Next
For Each fileName In temp
Console.WriteLine(fileName)
Next
If you run that then you'll see that the first loop displays just the files names but the second loop displays the full paths, because it still refers to the original list.
If that's a problem, there is another way to do this without an explicit loop:
Dim fileNames As New List(Of String) From {"C:\Folder\File1.ext",
"C:\Folder\File2.ext",
"C:\Folder\File3.ext"}
Dim temp = fileNames
Array.ForEach(Enumerable.Range(0, fileNames.Count).ToArray(),
Sub(i) fileNames(i) = Path.GetFileName(fileNames(i)))
For Each fileName In fileNames
Console.WriteLine(fileName)
Next
For Each fileName In temp
Console.WriteLine(fileName)
Next
If you run that then you'll see that both loops display just the file names because there's only one list.
That said, if the first code posed a problem because of multiple references to the list, I'd just use a loop.
I know you stated that you'd want something other than a loop, but there really is no needfor anything fancy here. By the way, writing Remove.Add sounds like a riddle.
Sub Main()
Dim Remove As New List(Of String)
Remove.Add("C:\_Vault\Designs\Jobs\Customer\Job23\Assemblies\045-0201.iam")
Remove.Add("C:\_Vault\Designs\Jobs\Customer\Job23\Parts\212-D017.ipt")
Remove.Add("C:\_Vault\Designs\Jobs\Customer\Job23\Parts\211-W01.iam")
Console.WriteLine("Before execution")
For Each s As String In Remove
Console.WriteLine(s)
Next
For i As Integer = 0 To Remove.Count - 1
Remove(i) = MyFunction(Remove(i))
Next
Console.WriteLine("After execution")
For Each s As String In Remove
Console.WriteLine(s)
Next
Console.ReadLine()
End Sub
Private Function MyFunction(path As String) As String
Return IO.Path.GetFileName(path)
End Function
This outputs:

How to delete files containing string from listbox

I want to delete all files in the folder which names can not be found in listbox items. I assume i m missing a counter somewhere, but not really sure how to do this exactly.
Dim directoryName As String = "Folderimages"
For Each deleteFile In Directory.GetFiles(directoryName, "*.*",SearchOption.TopDirectoryOnly)
For Each item In ListBox1.Items
Dim items As Object = ListBox1.Items
Dim itemText As String = ListBox1.GetItemText(items)
If Not deleteFile.Contains(itemText) Then
File.Delete(deleteFile)
End If
Next
Next
I think it might be a bit easier to split, the functionality you are creating here, just for easier usage aftewards
You can first have a function that gets all the items from your ListBox
Public Function GetItemsFromListBox( lbox as ListBox ) as IEnumerable(Of String)
return from item in lbox.Items _
select lbox.GetItemText( item )
End Function
And then have a function that retrieves all the files from a directory that are not inside that IEnumerable(Of String)
Public Function GetNonMatchingFiles( folder As String, nonMatchingItems As IEnumerable(Of String)) As IEnumerable(Of String)
Return Directory.GetFiles( folder ) _
.Where( Function(filename) not nonMatchingItems.Contains( Path.GetFileName( filename ), StringComparer.OrdinalIgnoreCase ) )
End Function
And then you can run through that result and delete the resultset. One thing to note would be that I compare the files with the filename only and not with the full path, if you don't want that, you should remove Path.GetFileName and just compare with filename, and that it will also look case insensitive
I didn't include the pattern (*.*) nor the searchoption as these are the defaults for that function anyhow
You could use Linq, so your test will become easier:
Dim directoryName As String = "Folderimages"
For Each deleteFile In Directory.GetFiles(directoryName, "*.*", SearchOption.TopDirectoryOnly)
If Not ListBox1.Items.Cast(Of String)().Any(Function(x) x = SearchString)) Then
File.Delete(deleteFile);
End If
Next
Did not try it, not sure if you need to cast the x as string.

Recursive For Each loop doesnt seem to recurse properly

For some reason, it seems that the outer block doesn't seem to update recursively, as I expected it to. I want the loops to add all directories within "C:\Users\Drise"to the array internaldirs(). Any advice on the correct way to do this, as it seems I'm doing it improperly?
Static internaldirs() As String
internaldirs.add("C:\Users\Drise")
For Each internaldir As String In internaldirs
For Each direc As String In Directory.GetDirectories(internaldir)
internaldirs.Add(direc)
Next
Next
Solution:
Sub recursivedirs()
Static internaldirs As New List(Of String)
Try
If internaldirs(0) = "C:\Users\Drise" Then
Call AddDirToList(internaldirs, internaldirs(0))
End If
Catch
internaldirs.Add("C:\Users\Drise")
Call AddDirToList(internaldirs, internaldirs(0))
End Try
End Sub
Private Sub AddDirToList(ByRef dirs As List(Of String), ByVal currentDir As String)
dirs.Add(currentDir)
Try
For Each subDir As String In Directory.GetDirectories(currentDir)
AddDirToList(dirs, subDir)
Next
Catch
End Try
Short answer is: you can't modify a collection (internaldirs) that you're iterating over.
Longer answer: Looks like you're trying to build a string array listing the folder in the directory tree. A better way would be to use a List and a recursive function.
Static dirs As List(Of String)
dirs = New List(Of String)
AddDirToList(dirs, "C:\Users\Drise")
Private Sub AddDirToList (dirs as List(Of String), currentDir as String)
dirs.Add(currentDir)
For Each subDir As String In Directory.GetDirectories(currentDir)
AddDirToList(dirs, currentDir)
Next
End Sub
Please excuse any syntax issues. I'm more of a C# guy.
As Andrew Cooper said, you can't modify a collection being used in a For Each loop.
You can do it with an index counter.
Dim internaldir As List(Of String)
internaldir.Add("C:\Users\Drise")
Dim i As Integer = 0
Do Until i >= internaldir.Count
Dim internaldir As String = internaldirs(i)
For Each currentdir As String In Directory.GetDirectories(internaldir)
internaldirs.Add(currentdir)
Next
i += 1
Loop
' If you want an array as output, use:
Dim array As String() = internaldirs.ToArray()

How to split multiline string?

When I try to split a string into a string list where each element represents a line of the initial string, I get the "square" character, which I think is a linefeed or something, at the start of each line, except the first line. How can I avoid that? My code is as follows:
Dim strList as List(Of String)
If Clipboard.ContainsText Then
strList = Clipboard.GetText.Split(Environment.NewLine).ToList
End If
I find that a pretty reliable way to read the lines of a string is to use a StringReader:
Dim strList As New List(Of String)
Using reader As New StringReader(Clipboard.GetText())
While reader.Peek() <> -1
strList.Add(reader.ReadLine())
End While
End Using
Maybe that's weird; I don't know. It's nice, though, because it frees you from dealing with the different ways of representing line breaks between different systems (or between different files on the same system).
Taking this one step further, it seems you could do yourself a favor and wrap this functionality in a reusable extension method:
Public Module StringExtensions
<Extension()> _
Public Function ReadAllLines(ByVal source As String) As IList(Of String)
If source Is Nothing Then
Return Nothing
End If
Dim lines As New List(Of String)
Using reader As New StringReader(source)
While reader.Peek() <> -1
lines.Add(reader.ReadLine())
End While
End Using
Return lines.AsReadOnly()
End Function
End Module
Then your code to read the lines from the clipboard would just look like this:
Dim clipboardContents As IList(Of String) = Clipboard.GetText().ReadAllLines()
There is a carriage return and line feed on each of your lines. character 10 and character 13 combine them into a string and split and you will get what you need.

VB.NET - Load a List of Values from a Text File

I Have a text file that is like the following:
[group1]
value1
value2
value3
[group2]
value1
value2
[group3]
value3
value 4
etc
What I want to be able to do, is load the values into an array (or list?) based on a passed in group value. eg. If i pass in "group2", then it would return a list of "value1" and "value2".
Also these values don't change that often (maybe every 6 months or so), so is there a better way to store them instead of a plain old text file so that it makes it faster to load etc?
Thanks for your help.
Leddo
This is a home work question?
Use the StreamReader class to read the file (you will need to probably use .EndOfStream and ReadLine()) and use the String class for the string manipulation (probably .StartsWith(), .Substring() and .Split().
As for the better way to store them "IT DEPENDS". How many groups will you have, how many values will there be, how often is the data accessed, etc. It's possible that the original wording of the question will give us a better clue about what they were after hear.
Addition:
So, assuming this program/service is up and running all day, and that the file isn't very large, then you probably want to read the file just once into a Dictionary(of String, List(of String)). The ContainsKey method of this will determine if a group exists.
Function GetValueSet(ByVal filename As String) As Dictionary(Of String, List(Of String))
Dim valueSet = New Dictionary(Of String, List(Of String))()
Dim lines = System.IO.File.ReadAllLines(filename)
Dim header As String
Dim values As List(Of String) = Nothing
For Each line As String In lines
If line.StartsWith("[") Then
If Not values Is Nothing Then
valueSet.add(header, values)
End If
header = GetHeader(line)
values = New List(Of String)()
ElseIf Not values Is Nothing Then
Dim value As String = line.Trim()
If value <> "" Then
values.Add(value)
End If
End If
Next
If Not values Is Nothing Then
valueSet.add(header, values)
End If
Return valueSet
End Function
Function GetHeader(ByVal line As String)
Dim index As Integer = line.IndexOf("]")
Return line.Substring(1, index - 1)
End Function
Addition:
Now if your running a multi-threaded solution (that includes all ASP.Net solutions) then you either want to make sure you do this at the application start up (for ASP.Net that's in Global.asax, I think it's ApplicationStart or OnStart or something), or you will need locking. WinForms and Services are by default not multi-threaded.
Also, if the file changes you need to restart the app/service/web-site or you will need to add a file watcher to reload the data (and then multi-threading will need locking because this is not longer confined to application startup).
ok, here is what I edned up coding:
Public Function FillFromFile(ByVal vFileName As String, ByVal vGroupName As String) As List(Of String)
' open the file
' read the entire file into memory
' find the starting group name
Dim blnFoundHeading As Boolean = False
Dim lstValues As New List(Of String)
Dim lines() As String = IO.File.ReadAllLines(vFileName)
For Each line As String In lines
If line.ToLower.Contains("[" & vGroupName.ToLower & "]") Then
' found the heading, now start loading the lines into the list until the next heading
blnFoundHeading = True
ElseIf line.Contains("[") Then
If blnFoundHeading Then
' we are at the end so exit the loop
Exit For
Else
' its another group so keep going
End If
Else
If blnFoundHeading And line.Trim.Length > 0 Then
lstValues.Add(line.Trim)
End If
End If
Next
Return lstValues
End Function
Regarding a possible better way to store the data: you might find XML useful. It is ridiculously easy to read XML data into a DataTable object.
Example:
Dim dtTest As New System.Data.DataTable
dtTest.ReadXml("YourFilePathNameGoesHere.xml")