Background Color:
 
Background Pattern:
Reset
Monday, December 11, 2017
menu click
Search

Traversing Folders and Files using Managed Code or the Win API

Author: Ibrahim/Sunday, October 20, 2013/Categories: Code Blog, VB Solutions

Rate this article:
No rating

A short article on how to traverse a directory's files and sub directories using managed code, and then how to do the same using the Win API within a Managed Code environment using VB.Net

 

A question came up about using managed code to traverse drive folders and files. The code by the questioner was close, but was written in such a way that broke when getting directory information that contained shortcuts, hidden or system files.  The code sample also was written in such a way that the Access Violation Exception would never be handled.  So when the code ran in some cases it would return files, and in others it would return empty.  The times it returned empty was when an access violation would occur.
After looking at the original code,  I refactored that code into something that should work more reliably.
Here is that code below:

Imports System.IO
Imports System.Security
Imports System.Security.Permissions
Imports System.Runtime.InteropServices


Public Function LoadCatalog(Path As String, Parent As FolderItem) As FolderItem

        Dim MyFolderItem As New FolderItem

        Dim attributeReader As System.IO.FileAttributes
        Dim DirectoryInfo As DirectoryInfo = New DirectoryInfo(Path)
        attributeReader = DirectoryInfo.Attributes

        Try

            Dim FilePermission As New FileIOPermission(FileIOPermissionAccess.PathDiscovery Or FileIOPermissionAccess.Read, Path)




            '===================================================
            'Add test for permissions here and modify as needed
            '===================================================

            If ((attributeReader And System.IO.FileAttributes.ReparsePoint) > 0) Then
                Debug.WriteLine("Not Readable " & DirectoryInfo.FullName & ":" & attributeReader.ToString())
            Else

                ' Debug.WriteLine("Readable " & DirectoryInfo.FullName & ":" & attributeReader.ToString())



                Dim FileList As New List(Of FileInfo)(DirectoryInfo.GetFiles())
                Dim FolderList As New List(Of DirectoryInfo)(DirectoryInfo.GetDirectories())

                MyFolderItem.Foldername = DirectoryInfo.Name
                MyFolderItem.FolderPath = DirectoryInfo.FullName

                For Each MyFileInfo As FileInfo In FileList

                    If File.Exists(MyFileInfo.FullName) Then

                        Dim MyFile As New FileItem

                        With MyFile
                            .FileName = MyFileInfo.Name
                            .FilePath = MyFileInfo.FullName
                            .FileID = If(Parent Is Nothing, "Root", Parent.Foldername) & "." & MyFileInfo.Name
                        End With

                        MyFolderItem.Files.Add(MyFile.FileID, MyFile)

                    End If
                Next

                For Each Folder As DirectoryInfo In FolderList

                    If (Directory.Exists(Folder.FullName)) Then

                        MyFolderItem.Folders.Add(Folder.Name, LoadCatalog(Folder.FullName, MyFolderItem))
                    End If
                Next
            End If


        Catch uae As UnauthorizedAccessException

            Debug.WriteLine("Not Readable " & DirectoryInfo.FullName & ":" & attributeReader.ToString())
            Debug.WriteLine(uae.Message)

        Catch ex As Exception
            Throw
        Finally
            '==============================================
            'add code here to restore folder permissions
            '==============================================
        End Try

        Return MyFolderItem

    End Function


To use the method you would call it like this:

	Dim MyFolder As New FolderItem
	MyFolder = LoadCatalog("C:\", Nothing)

This works well in most circumstances.  But the original question’s requirement was to be able to traverse all of the folders and files of a given machine.  Using managed code this could be quite slow.  So the discussion included using WinAPI  calls to accomplish the same thing.  Actually, this whole discussion came up about the need to migrate a process that already used the Win32 API in unmanaged code to a Managed Code environment.  The original source code of the unmanaged process was no longer available so the managed route was taken.
Below is the same process, but instead of using the managed code GetFiles anf GetDirectories, it uses the Win API FindFirstFile and FindNextFile process.  With the API methods, both directories and files are returned from the FindNextFile method and you have to determine the difference through a check on the attributes associated with the file data returned.  Also you have to ferret out the “.” And “..” Directory items since they are useless in this context.

\
 

Const FIND_FIRST_EX_CASE_SENSITIVE = 1
    Const FIND_FIRST_EX_LARGE_FETCH = 2
    Const INVALID_HANDLE_VALUE As Long = -1


    Public Enum FINDEX_INFO_LEVELS
        FindExInfoStandard = 0
        FindExInfoBasic = 1
    End Enum

    Public Enum FINDEX_SEARCH_OPS
        FindExSearchNameMatch = 0
        FindExSearchLimitToDirectories = 1
        FindExSearchLimitToDevices = 2
    End Enum

    ' The CharSet must match the CharSet of the corresponding PInvoke signature
    <structlayout(layoutkind.sequential)> _
    Structure WIN32_FIND_DATA
        Public dwFileAttributes As UInteger
        Public ftCreationTime As System.Runtime.InteropServices.ComTypes.FILETIME
        Public ftLastAccessTime As System.Runtime.InteropServices.ComTypes.FILETIME
        Public ftLastWriteTime As System.Runtime.InteropServices.ComTypes.FILETIME
        Public nFileSizeHigh As UInteger
        Public nFileSizeLow As UInteger
        Public dwReserved0 As UInteger
        Public dwReserved1 As UInteger
        <marshalas(unmanagedtype.byvaltstr)> Public cFileName As String
       <marshalas(unmanagedtype.byvaltstr)> Public cAlternateFileName As String
    End Structure

    <dllimport("kernel32.dll")> _
    Private Shared Function FindNextFile(ByVal hFindFile As IntPtr, ByRef lpFindFileData As WIN32_FIND_DATA) As Boolean
    End Function



    <dllimport("kernel32.dll")> _
    Private Shared Function FindFirstFileEx(ByVal lpFileName As String,
                                            ByVal fInfoLevelId As FINDEX_INFO_LEVELS,
                                            ByRef lpFindFileData As WIN32_FIND_DATA,
                                            ByVal fSearchOp As FINDEX_SEARCH_OPS,
                                            lpSearchFilter As Int32,
                                            dwAdditionalFlags As Integer) As IntPtr
    End Function

    Declare Function FindClose Lib "kernel32" (ByVal hFindFile As IntPtr) As Long




    Public Function LoadCatalogAPI(Path As String, Pattern As String, Parent As FolderItem) As FolderItem

        Dim MyFolderItem As New FolderItem
        Dim SubFolderItem As FolderItem
        Dim MyFileItem As FileItem = Nothing
        Dim FirstFileName As String = Nothing
        Dim Searchhandle As IntPtr
        Dim MyData As New WIN32_FIND_DATA
        Dim MyPath As String

        Try

            MyPath = Path & Pattern
            MyFolderItem.Foldername = Path

            Searchhandle = FindFirstFileEx(
                 MyPath,
                 FINDEX_INFO_LEVELS.FindExInfoStandard, MyData, FINDEX_SEARCH_OPS.FindExSearchNameMatch, 0, 1)

            If Searchhandle <> INVALID_HANDLE_VALUE Then

                FirstFileName = MyData.cFileName '  TrimNull(MyData.cFileName)

                Do While FindNextFile(Searchhandle, MyData)
                    FirstFileName = MyData.cFileName

                    If Not FirstFileName.Equals(".") And Not FirstFileName.Equals("..") Then
                        'if it id not a directory then process item as file
                        If Not (MyData.dwFileAttributes And FileAttributes.Directory) = FileAttributes.Directory Then
                            MyFileItem = New FileItem()
                            MyFileItem.FileName = FirstFileName
                            MyFileItem.FilePath = Path & FirstFileName
                            MyFileItem.FileID = If(Parent Is Nothing, "Root", Parent.Foldername) & "." & MyFileItem.FileName
                            MyFolderItem.Files.Add(MyFileItem.FileID, MyFileItem)
                        Else
                            'oitherwise process item as subdirectory making a recursive call to process it.
                            SubFolderItem = LoadCatalogAPI(Path & FirstFileName & "\", Pattern, MyFolderItem)
                            MyFolderItem.Folders.Add(SubFolderItem.Foldername, SubFolderItem)
                        End If
                    End If

                Loop

            Else
                'handle no search handle the error here as needed
            End If

        Catch ex As Exception
            Throw
        Finally
            FindClose(Searchhandle)
        End Try

        Return MyFolderItem

    End Function

</marshalas(unmanagedtype.byvaltstr)></marshalas(unmanagedtype.byvaltstr)>

 
To use the API version you would call it like this:

 Dim MyFolder As New FolderItem
 MyFolder = LoadCatalogAPI("C:\", "*.*", Nothing)

Use the code as a starting point and modify and test for your own particular purposes. The code presented above has not been tested in a production environment and I make no warrantees as to the fitness for any purpose...blah blah blah.

 

Number of views (5757)/Comments (0)

Tags:

Please login or register to post comments.