Verzeichnisse und Dateien rekursiv einlesen in VB.NET

Fr, 19.02.2010 - 16:37 -- admin

Um mit VB.NET ganze Verzeichnisstrukturen mit Unterverzeichnisse rekursiv einlesen zulassen, gibt es einige gute Wege, fertige Klassen und Funktionen. Da ich allerdings mittlerweile bestimmt bald an die 1 Million Objekte (Ordner und Dateien) zu administrativen Zwecken durchsuchen lasse und weiterverarbeite, braucht man doch eine etwas robustere Lösung.
So hab ich einige teils fertige und auch eigene Entwicklungen genutzt, bin dann irgendwie allerdings auf ein recht ordentliche Klasse gestoßen und habe sie meinen Bedürfnissen angepasst und erweitert.

Probleme

Im Laufe der Zeit bin ich auf diverse Probleme gestoßen, die allesamt mittlerweile irgendwie übergangen werden können. :)

  • Datei- und Verzeichnisnamen dürfen maximal 255 Zeichen lang sein
  • Überspringen von Verzeichnissen und Dateien auf denen man keine Zugriffsberechtigung hat
  • Verzeichnisse und Dateinamen mit wirklich sehr ausgefallenen Sonderzeichen

Funktionen

Einige Funktionen sollten kurz genannt werden, finden sich aber auch im Beispiel wieder.

  • Es kann eine Maske zur Filter sowohl für Dateien als auch Ordner übergeben werden, wie man es aus der Explorersuche kennt (*.exe;*.zip)
  • Die zurückgegebene Ergebnisliste stellt die Werte nicht wie üblich als String Liste bereit, sondern als System.IO Objekt, z.B. eine FileInfo oder Directory Liste

.NET Bordmittel

Natürlich kann man auch direkt die .NET Funktion zum rekursiven einlesen nutzen, für einfache Strukturen vollkommen ausreichend. Bricht bei 255 Zeichen langen Pfaden allerdings konsequent ab.

System.IO.Directory.GetFiles(RootDir, "*.exe", IO.SearchOption.AllDirectories)

VCS File: 

/trunk/FileSearchRecursive/example.vb

'$Id: example.vb 11 2010-02-17 06:54:48Z espendiller $
Module example
    'or in single line: Dim _Files As New FileSearch(New IO.DirectoryInfo("d:\Media\"), "*.nfo")
    Dim RecursiveScan As New FileSearch
 
    Sub Main()
        Dim RootDir As String = "C:\Windows"
        Console.WriteLine("Scanning: " & RootDir)
        RecursiveScan.Search(RootDir, "*.exe")
 
        'print out scan result
        Console.WriteLine(RecursiveScan.Files.Count & " Files found, press key for details")
        Console.ReadKey()
 
        'walk through ArrayList of Files
        For Each File As System.IO.FileInfo In RecursiveScan.Files
            Console.WriteLine(File.FullName & " - " & File.Length & " - " & File.LastWriteTime)
        Next
 
        Console.ReadKey()
 
        'you can also use this method to scan recursive but there is no error handling
        'System.IO.Directory.GetFiles(RootDir, "*.exe", IO.SearchOption.AllDirectories)
    End Sub
 
End Module

/trunk/FileSearchRecursive/FileSearch.vb

'$Id: FileSearch.vb 11 2010-02-17 06:54:48Z espendiller $
'Option Strict On
'Option Explicit On
Imports System.IO
Imports System.Collections.Specialized
''' <summary>
''' Recursive Scan a Directory for Files with possibility to use File/Directory Masks
''' 
''' http://www.vbforums.com/showthread.php?t=341919
''' </summary>
''' <remarks></remarks>
Public Class FileSearch
    Private Const DefaultFileMask As String = "*.*"
    Private Const DefaultDirectoryMask As String = "*"
#Region " Member Variables "
    Private _InitialDirectory As DirectoryInfo
    Private _DirectoryMasks As StringCollection
    Private _FileMasks As StringCollection
    Private _Directories As New ArrayList
    Private _Files As New ArrayList
#End Region
#Region " Properites "
 
    Public Property InitialDirectory() As DirectoryInfo
        Get
            Return _InitialDirectory
        End Get
        Set(ByVal Value As DirectoryInfo)
            _InitialDirectory = Value
        End Set
    End Property
 
    Public Property DirectoryMask() As StringCollection
        Get
            Return _DirectoryMasks
        End Get
        Set(ByVal Value As StringCollection)
            _DirectoryMasks = Value
        End Set
    End Property
    ''' <summary>
    ''' FileMask to filter Files
    ''' </summary>
    ''' <value>*.zip or *.zip;*.jpg;*.bmp or </value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property FileMask() As StringCollection
        Get
            Return _FileMasks
        End Get
        Set(ByVal Value As StringCollection)
            _FileMasks = Value
        End Set
    End Property
    ''' <summary>
    ''' List all Directories that has been found; use Search() first
    ''' </summary>
    ''' <value></value>
    ''' <returns>ArrayList with Directories</returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Directories() As ArrayList
        Get
            Return _Directories
        End Get
    End Property
    ''' <summary>
    ''' List all Files that has been found; use Search() first
    ''' </summary>
    ''' <value>ArrayList with Files</value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Files() As ArrayList
        Get
            Return _Files
        End Get
    End Property
#End Region
#Region " Constructors "
    Public Sub New()
 
    End Sub
 
    Public Sub New( _
        ByVal BaseDirectory As String)
        Me.New(New DirectoryInfo(BaseDirectory))
    End Sub
    Public Sub New( _
        ByVal InitialDirectory As DirectoryInfo)
        _InitialDirectory = InitialDirectory
    End Sub
#End Region
 
    ''' <summary>
    ''' Perfomer Recursive Search
    ''' </summary>
    ''' <param name="InitalDirectory">Parent directory</param>
    ''' <param name="FileMask">FileMask to filter Files: "*.zip" or "*.zip;*.jpg;*.bmp"</param>
    ''' <param name="DirectoryMask">Makes for Files</param>
    ''' <remarks></remarks>
    Public Overloads Sub Search(ByVal InitalDirectory As String, Optional ByVal FileMask As String = Nothing, Optional ByVal DirectoryMask As String = Nothing)
        Search(New DirectoryInfo(InitalDirectory), FileMask, DirectoryMask)
    End Sub
    Public Overloads Sub Search( _
         Optional ByVal InitalDirectory As DirectoryInfo = Nothing, _
         Optional ByVal FileMask As String = Nothing, _
         Optional ByVal DirectoryMask As String = Nothing)
        _Files = New ArrayList
        _Directories = New ArrayList
        If Not IsNothing(InitalDirectory) Then
            _InitialDirectory = InitalDirectory
        End If
        If IsNothing(_InitialDirectory) Then
            Throw New ArgumentException("A Directory Must be specified!", "Directory")
        End If
        If IsNothing(FileMask) OrElse FileMask.Length = 0 Then
            _FileMasks = New StringCollection
            _FileMasks.Add(DefaultFileMask)
        Else
            _FileMasks = ParseMask(FileMask)
        End If
        If IsNothing(DirectoryMask) OrElse DirectoryMask.Length > 0 Then
            _DirectoryMasks = New StringCollection
            _DirectoryMasks.Add(DefaultDirectoryMask)
        Else
            _DirectoryMasks = ParseMask(DirectoryMask)
        End If
        DoSearch(_InitialDirectory)
    End Sub
    Private Sub DoSearch(ByVal BaseDirectory As DirectoryInfo)
        Try
            For Each fm As String In _FileMasks
                Try 'added for directory/file which are longer then 255
                    Files.AddRange(BaseDirectory.GetFiles(fm))
                Catch ex As Exception
 
                End Try
            Next
        Catch u As UnauthorizedAccessException
 
        End Try
 
        Try
            Dim Directories As New ArrayList
            For Each dm As String In _DirectoryMasks
                Try 'added for directory/file which are longer then 255
                    Directories.AddRange(BaseDirectory.GetDirectories(dm))
                    _Directories.AddRange(Directories)
                Catch ex As Exception
 
                End Try
            Next
            For Each di As DirectoryInfo In Directories
                DoSearch(di)
            Next
        Catch u As UnauthorizedAccessException
        End Try
    End Sub
    Private Shared Function ParseMask(ByVal Mask As String) As StringCollection
        If IsNothing(Mask) Then
            Return Nothing
        End If
        Mask = Mask.Trim(";"c)
        If Mask.Length = 0 Then
            Return Nothing
        End If
        Dim Masks As New StringCollection
 
        Masks.AddRange(Mask.Split(";"c))
        Return Masks
 
    End Function
 
    Protected Overrides Sub Finalize()
        _Files = Nothing
        _Directories = Nothing
        MyBase.Finalize()
    End Sub
End Class

Disqus - noscript

Hi,

klasse Arbeit, jedoch ein kleiner Fehler...DirectoryMask.Length = 0 wäre richtig :)


If IsNothing(DirectoryMask) OrElse DirectoryMask.Length > 0 Then
_DirectoryMasks = New StringCollection
_DirectoryMasks.Add(DefaultDirectoryMask)
Else
_DirectoryMasks = ParseMask(DirectoryMask)
End If

PS: Die DirectoryMask ist eine Positivliste, könnte mann da noch eine Negativliste einbauen. Damit meine ich Verzeichnisse sollten nicht durchsucht werden.

Gruß
Markus
Hallo,
jawohl das sieht logischer aus da haste recht. werd's bei Gelegenheit mal anpassen, dank.