Visual Basic File API Routines
FindFirstFile: Fast Directory File Count
     
Posted:   Thursday September 9, 1999
Updated:   Monday December 26, 2011
     
Applies to:   VB4-32, VB5, VB6
Developed with:   VB6, Windows NT4
OS restrictions:   None
Author:   VBnet - Randy Birch
     

Related:  

 

FindFirstFile: Changing File and/or Folder Attributes Recursively
FindFirstFile: Determining the Oldest Folder (Recursive)
FindFirstFile: Fast Directory File Count
FindFirstFile: Extract Filename from a Full Path
FindFirstFile: Performance Comparison - FSO vs. API
FindFirstFile: Comparison of FindFirstFile and SearchTreeForFile
FindFirstFile: Save a Recursive Search of All Drives to Disk
FindFirstFile: Save a Recursive Search of Specified Drives to Disk
GetFileVersionInfo: File Search and File Property Info
     
 Prerequisites
None.

The routines here demonstrates how to use FindFirstFile, FindNextFile and GetFileAttributes to selectively count all files matching a given criteria (filespec) and/or file attribute. This makes the routine ideal for counting all files in a given folder (*.*), or for determining hidden or system files, or directories on a given path.

Although the majority of the code is repetitive amongst routines, it is presented this way to isolate each routine for simplicity. In addition, the code (as written) is not recursive, so will only determine the number of files under the initial starting directory. Other VBnet pages show the modifications required to use recursive functions to obtain the total files under the start path.
 BAS Module Code
None.

 Form Code
Add three command buttons (Command1, Command2, Command3), and a listbox (List1) to the form, along with the following code:

Option Explicit
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Copyright 1996-2011 VBnet/Randy Birch, All Rights Reserved.
' Some pages may also contain other copyrights by the author.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Distribution: You can freely use this code in your own
'               applications, but you may not reproduce 
'               or publish this code on any web site,
'               online service, or distribute as source 
'               on any media without express permission.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Const vbDot = 46
         
Private Const INVALID_HANDLE_VALUE As Long = -1
Private Const MAX_PATH As Long = 260
Private Const FILE_ATTRIBUTE_READONLY As Long = &H1
Private Const FILE_ATTRIBUTE_HIDDEN As Long = &H2
Private Const FILE_ATTRIBUTE_SYSTEM As Long = &H4
Private Const FILE_ATTRIBUTE_DIRECTORY As Long = &H10
Private Const FILE_ATTRIBUTE_ARCHIVE As Long = &H20
Private Const FILE_ATTRIBUTE_NORMAL As Long = &H80
Private Const FILE_ATTRIBUTE_TEMPORARY As Long = &H100
Private Const FILE_ATTRIBUTE_COMPRESSED As Long = &H800
Private Const FILE_ATTRIBUTE_ALL As Long = FILE_ATTRIBUTE_READONLY Or _
                                           FILE_ATTRIBUTE_HIDDEN Or _
                                           FILE_ATTRIBUTE_SYSTEM Or _
                                           FILE_ATTRIBUTE_ARCHIVE Or _
                                           FILE_ATTRIBUTE_NORMAL
Private Type FILETIME
   dwLowDateTime As Long
   dwHighDateTime As Long
End Type

Private Type WIN32_FIND_DATA
   dwFileAttributes As Long
   ftCreationTime As FILETIME
   ftLastAccessTime As FILETIME
   ftLastWriteTime As FILETIME
   nFileSizeHigh As Long
   nFileSizeLow As Long
   dwReserved0 As Long
   dwReserved1 As Long
   cFileName As String * MAX_PATH
   cAlternate As String * 14
End Type

Private Declare Function FindClose Lib "kernel32" _
  (ByVal hFindFile As Long) As Long
   
Private Declare Function FindFirstFile Lib "kernel32" _
   Alias "FindFirstFileA" _
  (ByVal lpFileName As String, _
   lpFindFileData As WIN32_FIND_DATA) As Long
   
Private Declare Function FindNextFile Lib "kernel32" _
   Alias "FindNextFileA" _
  (ByVal hFindFile As Long, _
   lpFindFileData As WIN32_FIND_DATA) As Long
   
Private Declare Function GetFileAttributes Lib "kernel32" _
   Alias "GetFileAttributesA" _
  (ByVal lpFileName As String) As Long

Private Declare Function lstrlenW Lib "kernel32" _
  (ByVal lpString As Long) As Long
         


Private Sub Command1_Click()

   Dim sSource As String
   Dim sFileType As String
   Dim numFiles As Long
   
  'initialize values
   sSource = "c:\windows\system32\"
   sFileType = "*.*"
   
  'get the count
   numFiles = FilesCountAll(sSource, sFileType)
   
   MsgBox numFiles & " files found matching " & sSource & sFileType, _
          vbOKOnly Or vbInformation, _
          "VBnet FindFirstFile File Count Demo"

End Sub


Private Sub Command2_Click()

   Dim sSource As String
   Dim sFileType As String
   Dim dwAttributes As Long
   Dim numFiles As Long
   
  'initialize values
   sSource = "c:\windows\"
   sFileType = "*.*"
   
  'set the attribute(s) to search for
   dwAttributes = FILE_ATTRIBUTE_NORMAL Or FILE_ATTRIBUTE_ARCHIVE
   
  'get the count
   numFiles = FilesCountByAttribute(sSource, sFileType, dwAttributes)
   
   MsgBox numFiles & " files found matching " & _
          sSource & sFileType & " with attribute(s) " & _
          GetAttributeString(dwAttributes), _
          vbOKOnly Or vbInformation, _
          "VBnet FindFirstFile File Count Demo"

End Sub


Private Sub Command3_Click()

   Dim sSource As String
   Dim sFileType As String
   Dim dwAttributes As Long
   Dim numFiles As Long
   
  'initialize values
   sSource = "c:\windows\"
   sFileType = "*.*"
   
  'set the attribute(s) to search for
   dwAttributes = FILE_ATTRIBUTE_ARCHIVE
   
  'clear the list and hide to
  'prevent updating until the
  'search is complete
   With List1
      .Clear
      .Refresh
      .Visible = False
   End With
   
  'get the list and count
   numFiles = FilesListByAttribute(sSource, sFileType, dwAttributes)
   
   With List1
      .Visible = True
      .ListIndex = 37
   End With
      
   MsgBox numFiles & " files found matching " & _
          sSource & sFileType & " with attribute(s) " & _
          GetAttributeString(dwAttributes), _
          vbOKOnly Or vbInformation, _
          "VBnet FindFirstFile File Count Demo"

End Sub


Private Function FilesCountAll(sSource As String, sFileType As String) As Long

   Dim wfd As WIN32_FIND_DATA
   Dim hFile As Long
   Dim fCount As Long
   
  'Start searching for files in
  'sSource by obtaining a file
  'handle to the first file matching
  'the filespec passed
   hFile = FindFirstFile(sSource & sFileType, wfd)
   
   If hFile <> INVALID_HANDLE_VALUE Then
   
     'must have at least one, so ...
      Do
      
        'increment the counter and
        'find the next file - see comments below
         If (Asc(wfd.cFileName) <> vbDot) Then FilesCountAll = FilesCountAll + 1
               
      Loop Until FindNextFile(hFile, wfd) = 0
      
   End If
   
  'Close the search handle
   Call FindClose(hFile)
   
End Function


Private Function FilesCountByAttribute(sSource As String, _
                                      sFileType As String, _
                                      dwAttributes As Long) As Long

   Dim wfd As WIN32_FIND_DATA
   Dim hFile As Long
   
   hFile = FindFirstFile(sSource & sFileType, wfd)
   
   If (hFile <> INVALID_HANDLE_VALUE) Then
      
      Do
      
        'compare the file attributes
        'against the passed flag(s).
        'No need to remove trailing nulls.
         If (dwAttributes And GetFileAttributes(sSource & wfd.cFileName)) And _
            (Asc(wfd.cFileName) <> vbDot) Then
      
           'increment the counter and
           'find the next file matching - see comments below
           'the file spec
            FilesCountByAttribute = FilesCountByAttribute + 1
            
         End If
         
      Loop Until FindNextFile(hFile, wfd) = 0
      
   End If
   
  'Close the search handle
   Call FindClose(hFile)
   
End Function


Private Function FilesListByAttribute(sSource As String, _
                                      sFileType As String, _
                                      dwAttributes As Long) As Long

   Dim wfd As WIN32_FIND_DATA
   Dim hFile As Long
   
  'Start searching for files
  'in the Source directory
   hFile = FindFirstFile(sSource & sFileType, wfd)
   
   If (hFile <> INVALID_HANDLE_VALUE) Then
      
      Do
      
        'compare the file attributes
        'against the passes flag(s)
         If (dwAttributes And GetFileAttributes(sSource & wfd.cFileName)) = dwAttributes And 
            (Asc(wfd.cFileName) <> vbDot) Then
      
           'increment the counter and find
           'the next file matching the file spec
           '- see comments below
            List1.AddItem TrimNull(wfd.cFileName)
            FilesListByAttribute = FilesListByAttribute + 1
            
         End If
                        
      Loop Until FindNextFile(hFile, wfd) = 0
      
   End If
   
  'Close the search handle
   Call FindClose(hFile)
   
End Function


Private Function GetAttributeString(attr As Long) As String

   Dim tmp As String
   
   If attr And FILE_ATTRIBUTE_ARCHIVE Then tmp = tmp & "ARCHIVE  "
   If attr And FILE_ATTRIBUTE_NORMAL Then tmp = tmp & "NORMAL  "
   If attr And FILE_ATTRIBUTE_HIDDEN Then tmp = tmp & "HIDDEN  "
   If attr And FILE_ATTRIBUTE_READONLY Then tmp = tmp & "READONLY  "
   If attr And FILE_ATTRIBUTE_SYSTEM Then tmp = tmp & "SYSTEM  "
   If attr And FILE_ATTRIBUTE_TEMPORARY Then tmp = tmp & "TEMPORARY  "
   If attr And FILE_ATTRIBUTE_COMPRESSED Then tmp = tmp & "COMPRESSED  "
   If attr And FILE_ATTRIBUTE_DIRECTORY Then tmp = tmp & "DIRECTORY  "

   GetAttributeString = tmp

End Function


Private Function TrimNull(startstr As String) As String

   TrimNull = Left$(startstr, lstrlenW(StrPtr(startstr)))
   
End Function
 Comments
Save the project. Before running, set the desired paths and file specs in the command click subs.

Each routine uses the code "(Asc(wfd.cFileName) <> vbDot)" to prevent counting the two special and hidden directories ('.' and '..') present in every folder. The Asc() code compares the ASCII value of the first letter of the retrieved file against the ASCII value of a period (ASCII 46), skipping the item if a match.  The accuracy of this method is predicated on the presumption that users don't *begin* their file names or folders with a period.

If you believe this presumption is too, well, presumptuous, there are other options available to eliminate these two system files from the file count, each with corresponding caveats.

1) probably the simplest method is to subtract 2 from the file count if the enumeration was performed on a folder other than the root drive, or subtract 1 if a drive was specified. All subfolders have the '.' (current folder) and '..' (parent folder) members;  root folders (drive letters) only have the '.' (current folder) item.

2) Both '.' and '..' have the FILE_ATTRIBUTE_DIRECTORY attribute bit set, thus the If-Then could perform a directory test. And since the routine is to count *files* this won't impact the immediate functionality. It will however prevent the ability to use the same code to perform a directory check if the FILE_ATTRIBUTE_DIRECTORY flag is passed:

    If (wfd.dwFileAttributes And vbDirectory) <> FILE_ATTRIBUTE_DIRECTORY Then ...

3) Test the items as strings - the least efficient method but the most specific:

    If (Left$(wfd.cFileName, 1) <> ".") And (Left$(wfd.cFileName, 2) <> "..") Then ...

Note: While it may be convenient to utilize VB's built-in constants in place of the FILE_ATTRIBUTE_* API values, care must be taken. There is a difference between related constant values that may cause unexpected performance at some point. For example, the constant 'vbNormal' is defined as having a value of 0, whereas the API FILE_ATTRIBUTE_NORMAL has a value of &H80 (decimal 128).


 
 

PayPal Link
Make payments with PayPal - it's fast, free and secure!

 
 
 
 

Copyright 1996-2011 VBnet and Randy Birch. All Rights Reserved.
Terms of Use  |  Your Privacy

 

Hit Counter