JLION.COM
Windows API functions for finding and copying files.
6/18/2009 VB.NET

The SYSTEM.IO namespace is great and very easy to use but occasionally I've had a need to fall back on the API calls for finding and copying files. To make these API calls easier to use, I've placed them together in following class.

The FileInfo class encapsulates the WIN32_FIND_INFO structure and includes handy functions for decoding timestamps and filesize as well as some attributes such as readonly and directory.

The FileManagement class includes the standard calls such as FindFile and CopyFile, and also more interesting ones such as CopyFileEX (aliased here to CopyFileWithProgress) which allows for the showing of a progress indicator when copying very large files.

Inside FileManagement you'll find...

  • RecursiveCopy: Copies the contents of a directory and all subdirectories to another path.
  • FindAllFiles: Returns an arraylist containing all files and subdirectories that were found in a directory. Requires a parameter (EXT) representing the file and extension to look for. The wildcard "*.*" returns all files and subdirectories.
  • FileExists: Returns true if a file and path exists, otherwise returns false.
  • GetDirectoryInfo: Fills a FileInfo.WIN32_FIND_DATA class with information about a specified directory.
  • GetFileInfo: Fills a FileInfo.WIN32_FIND_DATA class with information about the specified file.
  • CopyFileWithProgress: Copies a file and periodically raises FileCopyProgress events. A FileCopyComplete event is raised when the copy is complete.
  • FindAllDirectories: Finds all directories in a given path and returns the results as an arraylist.

Here's the VB.NET source for the two classes:

Option Explicit On

Imports System.Runtime.InteropServices
Imports System.Xml

Namespace jlion

    ''' 
    ''' Encapsulates information about a file, on either a remote or local location.
    ''' 
    ''' 
    Public Class FileInfo
        Private msRoot As String
        Private msFullName As String
        Private msName As String
        Private mbIsDirectory As Boolean
        Private mbIsReadonly As Boolean
        Private moCreateTime As Object
        Private moLastWriteTime As Object
        Private moLastAccessTime As Object
        Private mlFileSize As Int64
        Private msParentDirectory As String = ""
        Private msErrorMessage As String

#Region "APIs"
         _
        Private Shared Function FileTimeToSystemTime( _
                            <[In]()> ByVal lpFileTime As FILETIME, _
                             ByRef lpSystemTime As SYSTEMTIME) _
                   As Boolean

        End Function

        Public Const FILE_ATTRIBUTE_READONLY = &H1
        Public Const FILE_ATTRIBUTE_HIDDEN = &H2
        Public Const FILE_ATTRIBUTE_SYSTEM = &H4
        Public Const FILE_ATTRIBUTE_DIRECTORY = &H10
        Public Const FILE_ATTRIBUTE_ARCHIVE = &H20
        Public Const FILE_ATTRIBUTE_NORMAL = &H80
        Public Const FILE_ATTRIBUTE_TEMPORARY = &H100
        Public Const FILE_ATTRIBUTE_COMPRESSED = &H800
        Public Const FILE_ATTRIBUTE_OFFLINE = &H1000

        ''' 
        ''' Structure used by windows to store security attributes
        ''' 
        ''' 
         _
        Public Class SECURITY_ATTRIBUTES
            Public nLength As Long
            Public lpSecurityDescriptor As Long
            Public bInheritHandle As Long
        End Class

        ''' 
        ''' Structure used by windows to store file times. Referenced by WIN32_FIND_DATA structure
        ''' 
        ''' 
         _
        Public Class FILETIME
            Public dwHighDateTime As Int32
            Public dwLowDateTime As Int32
        End Class

        ''' 
        ''' Information about a remote file, returned by the FTPFindFirstFile and InternetFindNextFile API calls.
        ''' 
        ''' 
         _
        Public Class WIN32_FIND_DATA
            Public dwFileAttributes As System.IO.FileAttributes
            Public ftCreationTime As FILETIME
            Public ftLastAccessTime As FILETIME
            Public ftLastWriteTime As FILETIME
            Public nFileSizeHigh As Integer
            Public nFileSizeLow As Integer
            Public dwReserved0 As Integer
            Public dwReserved1 As Integer
             _
            Public cFileName As String
             _
            Public cAlternate As String
        End Class

         _
        Private Structure SYSTEMTIME
             Public Year As Short
             Public Month As Short
             Public DayOfWeek As Short
             Public Day As Short
             Public Hour As Short
             Public Minute As Short
             Public Second As Short
             Public Milliseconds As Short
        End Structure
#End Region

        Public Sub New(ByVal oFile As WIN32_FIND_DATA)
            msName = oFile.cFileName
            mbIsDirectory = oFile.dwFileAttributes And FileInfo.FILE_ATTRIBUTE_DIRECTORY
            mbIsReadonly = oFile.dwFileAttributes And FileInfo.FILE_ATTRIBUTE_READONLY
            mlFileSize = CalculateFileSize(oFile.nFileSizeHigh, oFile.nFileSizeLow)
            moLastWriteTime = CalculateFileTime(oFile.ftLastWriteTime)
            moLastAccessTime = CalculateFileTime(oFile.ftLastAccessTime)
        End Sub

        Public Sub New( _
                    ByVal oFile As WIN32_FIND_DATA, _
                    ByVal sRoot As String, _
                    ByVal sBaseDir As String, _
                    ByVal sParentDir As String, _
                    ByVal bIsDir As Boolean)

            msRoot = sRoot
            msName = oFile.cFileName
            If sParentDir > "" Then
                msFullName = sBaseDir & "\" & sParentDir & "\" & oFile.cFileName
            Else
                msFullName = sBaseDir & "\" & oFile.cFileName
            End If

            msParentDirectory = sParentDir

            moCreateTime = CalculateFileTime(oFile.ftCreationTime)
            moLastWriteTime = CalculateFileTime(oFile.ftLastWriteTime)
            moLastAccessTime = CalculateFileTime(oFile.ftLastAccessTime)

            If Not bIsDir Then
                NewFile(oFile)
            Else
                NewDir(oFile)
            End If
        End Sub

        Private Sub NewFile(ByVal oFile As WIN32_FIND_DATA)
            mbIsDirectory = oFile.dwFileAttributes And FileInfo.FILE_ATTRIBUTE_DIRECTORY
            mbIsReadonly = oFile.dwFileAttributes And FileInfo.FILE_ATTRIBUTE_READONLY
            mlFileSize = CalculateFileSize(oFile.nFileSizeHigh, oFile.nFileSizeLow)
        End Sub

        Private Sub NewDir(ByVal oFile As WIN32_FIND_DATA)
            mbIsDirectory = True
            mbIsReadonly = False
            mlFileSize = 0
        End Sub

        Public ReadOnly Property Root() As String
            Get
                Return msRoot
            End Get
        End Property

        Public ReadOnly Property FullName() As String
            Get
                Return msFullName
            End Get
        End Property

        Public ReadOnly Property Name() As String
            Get
                Return msName
            End Get
        End Property

        Public ReadOnly Property Extension() As String
            Get
                Return ""
            End Get
        End Property

        Public ReadOnly Property ParentDirectory() As String
            Get
                Return msParentDirectory
            End Get
        End Property

        Public ReadOnly Property IsDirectory() As Boolean
            Get
                Return mbIsDirectory
            End Get
        End Property

        Public ReadOnly Property IsReadonly() As Boolean
            Get
                Return mbIsReadonly
            End Get
        End Property

        Public ReadOnly Property FileSize() As Int64
            Get
                Return mlFileSize
            End Get
        End Property

        Public ReadOnly Property CreateTime() As Object
            Get
                Return moCreateTime
            End Get
        End Property

        Public ReadOnly Property LastWriteTime() As Object
            Get
                Return moLastWriteTime
            End Get
        End Property

        Public ReadOnly Property LastAccessTime() As Object
            Get
                Return moLastAccessTime
            End Get
        End Property

        Private Function CalculateFileSize(ByVal lFileSizeHigh As Long, ByVal lFileSizeLow As Long) As Int64
            Dim lFileSize64 As Int64 = lFileSizeLow

            If lFileSizeLow < 0 Then
                lFileSize64 = lFileSize64 + 4294967296@
            End If

            If lFileSizeLow > 0 Then
                lFileSize64 = lFileSize64 + (lFileSizeHigh * 4294967296@)
            End If

            Return lFileSize64
        End Function

        Private Function CalculateFileTime(ByVal oFileTime As FILETIME) As Object
            Dim oSystemTime As New SYSTEMTIME

            Dim bSuccess As Boolean = FileTimeToSystemTime(oFileTime, oSystemTime)

            If bSuccess = True Then
                Dim oTime As New DateTime( _
                                    oSystemTime.Year, _
                                    oSystemTime.Month, _
                                    oSystemTime.Day, _
                                    oSystemTime.Hour, _
                                    oSystemTime.Minute, _
                                    oSystemTime.Second, _
                                    oSystemTime.Milliseconds)

                Return oTime
            Else
                Return Nothing
            End If
        End Function

        Public Function NotTheSameAs(ByVal oFileInfo As FileInfo) As Boolean
            Dim mbNotTheSame As Boolean = False

            If oFileInfo.IsDirectory = True _
                AndAlso oFileInfo.Name <> msName Then

                mbNotTheSame = True

            Else
                With oFileInfo
                    If .Name <> msName Then
                        mbNotTheSame = True
                    End If

                    If mbNotTheSame = False _
                        AndAlso .LastWriteTime <> moLastWriteTime Then

                        If IsDate(.LastWriteTime) AndAlso IsDate(moLastWriteTime) _
                            AndAlso Math.Abs(DateDiff(DateInterval.Minute, CDate(.LastWriteTime), CDate(moLastWriteTime))) < 1 Then
                            '---they are the same
                        Else
                            mbNotTheSame = True
                        End If
                    End If

                    If mbNotTheSame = False _
                        AndAlso .FileSize <> mlFileSize Then
                        mbNotTheSame = True
                    End If
                End With
            End If

            Return mbNotTheSame
        End Function

        Private Function FileNameFromFullName(ByVal sFullName As String) As String
            Dim sResult As String = sFullName

            Dim iLastSlash As Integer = sFullName.LastIndexOf("\")
            If iLastSlash > -1 Then
                sResult = sFullName.Substring(iLastSlash, sFullName.Length - iLastSlash - 1)
            End If

            Return sResult
        End Function
    End Class

    Public Class FileManagement
        'By Joe Lynds, 2008
        'http://www.jlion.com

#Region "FindFile APIs"
        Private Const INVALID_HANDLE_VALUE As Long = -1

        ''' 
        ''' FindFirstFile API. Necessary because system.io functions can't access shadow volume.
        ''' http://msdn2.microsoft.com/en-us/library/aa364418.aspx
        ''' 
        ''' 
        ''' 
        ''' 
        ''' 
         _
            Private Shared Function FindFirstFile( _
                        <[In]()> ByVal lpszSearchFile As String, _
                        <[In](), Out()> ByVal lpFindFileData As FileInfo.WIN32_FIND_DATA) _
               As IntPtr
        End Function

        ''' 
        ''' FindNextFile API. Necessary because system.io functions can't access shadow volume.
        ''' http://msdn2.microsoft.com/en-us/library/aa364428.aspx
        ''' 
        ''' 
        ''' 
        ''' 
        ''' 
         _
            Private Shared Function FindNextFile( _
                    <[In]()> ByVal hFind As IntPtr, _
                    <[In](), Out()> ByVal lpFindFileData As FileInfo.WIN32_FIND_DATA) _
           As  Boolean
        End Function

        ''' 
        ''' FindClose API. Necessary because system.io functions can't access shadow volume.
        ''' http://msdn2.microsoft.com/en-us/library/aa364413.aspx
        ''' 
        ''' 
        ''' 
        ''' 
         _
            Private Shared Function FindClose( _
                    <[In]()> ByVal hFind As IntPtr) _
               As  Boolean
        End Function
#End Region

#Region "CopyFile APIs"
        Public Event FileCopyProgress(ByVal lTotalSize As Long, ByVal lTotalTransferred As Long)
        Public Event FileCopyComplete(ByVal sSourceFile As String, ByVal sTargetFile As String)

        ''' 
        ''' Copy File API. Necessary because system.io functions can't access shadow volume.
        ''' http://msdn2.microsoft.com/en-us/library/aa363851.aspx
        ''' 
        ''' 
        ''' 
        ''' 
        ''' 
        ''' 
         _
            Private Shared Function CopyFile( _
            <[In]()> ByVal lpExistingFileName As String, _
            <[In]()> ByVal lpNewFileName As String, _
            <[In]()> ByVal bFailIfExists As Byte) _
                           As Boolean

        End Function

        Private Enum CopyProgressResult As UInteger
            PROGRESS_CONTINUE = 0
            PROGRESS_CANCEL = 1
            PROGRESS_STOP = 2
            PROGRESS_QUIET = 3
        End Enum

        Private Enum CopyProgressCallbackReason As UInteger
            CALLBACK_CHUNK_FINISHED = 0
            CALLBACK_STREAM_SWITCH = 1
        End Enum

        Private Enum CopyFileFlags As UInteger
            COPY_FILE_FAIL_IF_EXISTS = 1
            COPY_FILE_RESTARTABLE = 2
            COPY_FILE_OPEN_SOURCE_FOR_WRITE = 4
            COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 8
        End Enum

        Public Delegate Function CopyProgressRoutine( _
                    ByVal TotalFileSize As Int64, _
                    ByVal TotalBytesTransferred As Int64, _
                    ByVal StreamSize As Int64, _
                    ByVal StreamBytesTransferred As Int64, _
                    ByVal dwStreamNumber As Int32, _
                    ByVal dwCallbackReason As Int32, _
                    ByVal hSourceFile As Int32, _
                    ByVal hDestinationFile As Int32, _
                    ByVal lpData As Int32 _
        ) As Int32

        Declare Auto Function CopyFileEx Lib "kernel32.dll" ( _
                    ByVal lpExistingFileName As String, _
                    ByVal lpNewFileName As String, _
                    ByVal lpProgressRoutine As CopyProgressRoutine, _
                    ByVal lpData As Int32, _
                    ByVal lpBool As Int32, _
                    ByVal dwCopyFlags As Int32 _
        ) As Int32

        Public Function myCopyProgressRoutine( _
                    ByVal TotalFileSize As Int64, _
                    ByVal TotalBytesTransfered As Int64, _
                    ByVal StreamSize As Int64, _
                    ByVal StreamBytesTransferred As Int64, _
                    ByVal dwStreamNumber As Int32, _
                    ByVal dqCallbackReason As Int32, _
                    ByVal hSourceFile As Int32, _
                    ByVal hDestinationFile As Int32, _
                    ByVal lpData As Int32 _
                    ) As Integer

            RaiseEvent FileCopyProgress(TotalFileSize, TotalBytesTransfered)
        End Function

        Public Function CopyFileWithProgress( _
                            ByVal sSource As String, _
                            ByVal sTarget As String, _
                            ByVal bFailIfExists As Boolean) As Boolean

            Dim dwFlags As Int32 = 0
            If bFailIfExists = True Then
                dwFlags = CopyFileFlags.COPY_FILE_FAIL_IF_EXISTS
            End If

            Dim cb As CopyProgressRoutine
            cb = AddressOf Me.myCopyProgressRoutine

            Dim iRet As Integer = CopyFileEx(lpExistingFileName:=sSource, lpNewFileName:=sTarget, lpProgressRoutine:=cb, lpData:=0, lpBool:=0, dwCopyFlags:=dwFlags)

            RaiseEvent FileCopyComplete(sSource, sTarget)

            If iRet = 0 Then
                '---function failed
                Return False
            Else
                Return True
            End If
        End Function
#End Region

        Public Sub RecursiveCopy( _
                ByVal sSourcePath As String, _
                ByVal sTargetPath As String, _
                ByVal sExt As String)

            If Not sSourcePath.EndsWith("\") Then sSourcePath = sSourcePath & "\"
            If Not sTargetPath.EndsWith("\") Then sTargetPath = sTargetPath & "\"

            Console.Write(sSourcePath)

            '---Get a list of subdirectories
            Dim oDirList As ArrayList = FindAllDirectories(sSourcePath & "*.*")

            '---Create each subdirectory in target path if it doesn't already exist.
            For Each sDirName As String In oDirList
                If Not System.IO.Directory.Exists(sTargetPath & sDirName) Then
                    System.IO.Directory.CreateDirectory(sTargetPath & sDirName)
                End If
            Next

            '---Get a list of files
            Dim oFileList As ArrayList = FindAllFiles(sSourcePath, "*.*")

            For Each sFile As String In oFileList
                Console.Write(".")
                CopySpecificFile(sSourcePath & sFile, sTargetPath & sFile)
            Next

            Console.WriteLine()

            '---Recurse subdirectories
            For Each sDirName As String In oDirList
                RecursiveCopy(sSourcePath & sDirName, sTargetPath & sDirName, sExt)
            Next
        End Sub

        Public Function FindAllFiles( _
                ByVal sPath As String, _
                ByVal sExt As String) As ArrayList

            Dim wfd As New FileInfo.WIN32_FIND_DATA
            Dim hFile As Long
            Dim oList As New ArrayList

            Dim sSearchPath As String = sPath
            If sSearchPath.EndsWith("\") Then
                sSearchPath = sSearchPath & sExt
            Else
                sSearchPath = sSearchPath & "\" & sExt
            End If

            'Start searching for files in
            'sSource by obtaining a file
            'handle to the first file matching
            'the filespec passed
            hFile = FindFirstFile(sSearchPath, wfd)

            If hFile <> INVALID_HANDLE_VALUE Then

                'must have at least one, so ...
                Do
                    If (wfd.dwFileAttributes And IO.FileAttributes.Directory) = 0 Then
                        Dim oWFD As New FileInfo.WIN32_FIND_DATA
                        CopyStructure(From:=wfd, To:=oWFD)
                        oList.Add(oWFD)
                    End If
                Loop Until FindNextFile(hFile, wfd) = 0

            End If

            'Close the search handle
            Call FindClose(hFile)

            Return oList
        End Function

        Public Function FindAllDirectories( _
            ByVal sPath As String) As ArrayList

            Dim sSearchPath As String = sPath
            If sSearchPath.EndsWith("\") Then
                sSearchPath = sSearchPath & "*.*"
            Else
                sSearchPath = sSearchPath & "\*.*"
            End If

            Dim wfd As New FileInfo.WIN32_FIND_DATA
            Dim hFile As Long
            Dim oList As New ArrayList

            'Start searching for files in
            'sSource by obtaining a file
            'handle to the first file matching
            'the filespec passed
            hFile = FindFirstFile(sSearchPath, wfd)

            If hFile <> INVALID_HANDLE_VALUE Then

                'must have at least one, so ...
                Do
                    If (wfd.dwFileAttributes And IO.FileAttributes.Directory) > 0 _
                        AndAlso wfd.cFileName <> "." _
                        AndAlso wfd.cFileName <> ".." Then

                        Dim oWFD As New FileInfo.WIN32_FIND_DATA
                        CopyStructure(From:=wfd, To:=oWFD)
                        oList.Add(oWFD)
                    End If
                Loop Until FindNextFile(hFile, wfd) = 0

            End If

            'Close the search handle
            Call FindClose(hFile)

            Return oList
        End Function

        Private Sub CopyStructure( _
                        ByVal [From] As FileInfo.WIN32_FIND_DATA, _
                        ByRef [To] As FileInfo.WIN32_FIND_DATA)

            With [To]
                .cAlternate = [From].cAlternate
                .cFileName = [From].cFileName
                .dwFileAttributes = [From].dwFileAttributes
                .dwReserved0 = [From].dwReserved0
                .dwReserved1 = [From].dwReserved1
                .ftCreationTime = [From].ftCreationTime
                .ftLastAccessTime = [From].ftLastAccessTime
                .ftLastWriteTime = [From].ftLastWriteTime
                .nFileSizeHigh = [From].nFileSizeHigh
                .nFileSizeLow = [From].nFileSizeLow
            End With
        End Sub

        Public Sub CopySpecificFile(ByVal sSource As String, ByVal sTarget As String)
            Dim bsuccess As Boolean = CopyFileWithProgress(sSource, sTarget, bFailIfExists:=False)
            'Dim bSuccess As Boolean = CopyFile(sSource, sTarget, 0)

            If Not bSuccess Then
                Dim iErr As Integer = Err.LastDllError
                Dim sError As String = New System.ComponentModel.Win32Exception(iErr).Message

                Throw New util.FileCopyException("Unable to copy file " & sSource & ". Error Text: " & sError)
            End If
        End Sub

        Public Function FileExists(ByVal sFullName As String) As Boolean
            Dim bFileFound As Boolean = False

            Dim wfd As New FileInfo.WIN32_FIND_DATA
            Dim hFile As Long
            Dim oList As New ArrayList

            'Start searching for files in
            'sSource by obtaining a file
            'handle to the first file matching
            'the filespec passed
            hFile = FindFirstFile(sFullName, wfd)

            If hFile <> INVALID_HANDLE_VALUE Then
                bFileFound = True
            End If

            'Close the search handle
            Call FindClose(hFile)

            Return bFileFound
        End Function

        Public Function GetDirectoryInfo( _
                            ByVal sDirName As String, _
                            ByRef oDirInfo As FileInfo.WIN32_FIND_DATA) As Boolean

            Dim bFileFound As Boolean = False

            Dim wfd As New FileInfo.WIN32_FIND_DATA
            Dim hFile As Long
            Dim oList As New ArrayList

            'Start searching for files in
            'sSource by obtaining a file
            'handle to the first file matching
            'the filespec passed
            hFile = FindFirstFile(sDirName, wfd)

            If hFile <> INVALID_HANDLE_VALUE Then
                bFileFound = True

                CopyStructure(wfd, oDirInfo)
            End If

            'Close the search handle
            Call FindClose(hFile)

            Return bFileFound
        End Function

        Public Function GetFileInfo( _
                     ByVal sFileName As String, _
                     ByRef oFileInfo As FileInfo.WIN32_FIND_DATA) As Boolean

            Dim bFileFound As Boolean = False

            Dim wfd As New FileInfo.WIN32_FIND_DATA
            Dim hFile As Long
            Dim oList As New ArrayList

            'Start searching for files in
            'sSource by obtaining a file
            'handle to the first file matching
            'the filespec passed
            hFile = FindFirstFile(sFileName, wfd)

            If hFile <> INVALID_HANDLE_VALUE Then
                bFileFound = True

                CopyStructure(wfd, oFileInfo)
            End If

            'Close the search handle
            Call FindClose(hFile)

            Return bFileFound
        End Function
    End Class
End Namespace