It's been built-into Windows since at least XP and still works. It's recursive, has depth control, can just return folders, it'll sum the file sizes, and once killed a rattlesnake with its bare hands. Truly we must give up our lives, our wives and our tithes and worship our new god, ShellTreeWalkerTexasRanger.
Code: Select all
#include "windows.bi"
#include "win/objbase.bi"
#include "crt/stdio.bi"
'' http://218.94.103.156:8090/download/developer/xpsource/Win2K3/NT/shell/published/inc/shpriv.idl
'' Standard caveats about undocumented, not guaranteed, might work fine for 20 years and then cause a meltdown on the day you're getting promoted etc
Static Shared IID_IShellTreeWalker As IID = Type(&h95ce8410, &h7027, &h11d1, {&hb8, &h79, &h00, &h60, &h08, &h05, &h93, &h82})
Static Shared IID_IShellTreeWalkerCB As IID = Type(&h95ce8411, &h7027, &h11d1, {&hb8, &h79, &h00, &h60, &h08, &h05, &h93, &h82})
Static Shared CLSID_ShellTreeWalker As CLSID = Type(&h95ce8412, &h7027, &h11d1, {&hb8, &h79, &h00, &h60, &h08, &h05, &h93, &h82})
Type TREEWALKERSTATS
As Long nFiles '' number of files we have seen
As Long nFolders '' number of folders we have seen (does not include the root)
As Long nDepth '' the current depth
As DWORD dwClusterSize '' cluster size of the disk we are currently on
As ULONGLONG ulTotalSize '' total size of all files we have seen
As ULONGLONG ulActualSize '' total size on disk of all files, taking into account compression, sparse files, and cluster slop
End Type
Enum ShellTreeWalkFlags
WT_FOLDERFIRST = &h01, '' otherwise files first
WT_MAXDEPTH = &h02, '' specify the maximum depth we go into
WT_FOLDERONLY = &h04, '' only walks directories: default is to only return files
WT_NOTIFYFOLDERENTER = &h08, '' notifies whenever entering a folder :default is to notify on every file
WT_NOTIFYFOLDERLEAVE = &h10, '' notifies whenever leaving a folder
WT_GOINTOREPARSEPOINT = &h20, '' default is to stop at any reparse points (directory symbolic links)
WT_EXCLUDEWALKROOT = &h40 '' default is to EnterFolder the starting point
End Enum
Type TreeWalkCallback_ As TreeWalkCallback
Type IShellTreeWalker extends Object
'' IUnknown
Declare Abstract Function QueryInterface(ByVal iid As REFIID, Byval ppv as PVOID ptr) As HRESULT
Declare Abstract Function AddRef() As ULONG
Declare Abstract Function Release() As ULONG
'' IShellTreeWalker
Declare Abstract Function WalkTree(ByVal flags As DWORD, ByVal pwszRoot As PCWSTR, ByVal pattern as PCWSTR, ByVal maxDepth As Long, ByVal pCallback As TreeWalkCallback_ ptr) As HRESULT
End Type
Type TreeWalkCallback extends Object
As ULONGLONG totalSize
'' IUnknown
Declare Virtual Function QueryInterface(ByVal iid As REFIID, Byval ppv as PVOID ptr) As HRESULT
Declare Virtual Function AddRef() As ULONG
Declare Virtual Function Release() As ULONG
'' IShellTreeWalkerCallback
Declare Virtual Function FoundFile(ByVal pwszFile As PCWSTR, ByVal ptws As Const TREEWALKERSTATS ptr, ByVal pwfd As WIN32_FIND_DATAW ptr) As HRESULT
Declare Virtual Function EnterFolder(ByVal pwszFolder As PCWSTR, ByVal ptws As Const TREEWALKERSTATS ptr, ByVal pwfd As WIN32_FIND_DATAW ptr) As HRESULT
Declare Virtual Function LeaveFolder(ByVal pwszFolder As PCWSTR, ByVal ptws As Const TREEWALKERSTATS ptr) As HRESULT
Declare Virtual Function HandleError(ByVal pwszPath As PCWSTR, ByVal ptws As Const TREEWALKERSTATS ptr, ByVal hrError As HRESULT) As HRESULT
End Type
Function TreeWalkCallback.QueryInterface(ByVal iid As REFIID, ByVal ppv As PVOID ptr) As HRESULT
Dim hr As HRESULT
If IsEqualIID(iid, @IID_IShellTreeWalkerCB) OrElse IsEqualIID(iid, @IID_IUnknown) Then
AddRef()
*ppv = @This
Else
hr = E_NOINTERFACE
End If
Return hr
End Function
Function TreeWalkCallback.AddRef() As ULONG
Return 1
End Function
Function TreeWalkCallback.Release() As ULONG
Return 1
End Function
Function TreeWalkCallback.FoundFile(ByVal pwszFile As PCWSTR, ByVal ptws As Const TREEWALKERSTATS ptr, ByVal pwfd As WIN32_FIND_DATAW ptr) As HRESULT
'' You can just return S_OK or E_NOTIMPL for any of these functions if you don't want to do anything
'' Any other error code (like E_FAIL) will stop the enumeration
totalSize = ptws->ulTotalSize
wprintf(@WStr(!"Walker found file %d. %s of size %I64u\n"), ptws->nFiles, pwszFile, *Cast(ULONGLONG ptr, @pwfd->nFileSizeLow))
Return S_OK
End Function
Function TreeWalkCallback.EnterFolder(ByVal pwszFolder As PCWSTR, ByVal ptws As Const TREEWALKERSTATS ptr, ByVal pwfd As WIN32_FIND_DATAW ptr) As HRESULT
wprintf(@WStr(!"Walker entering folder %s\n"), pwszFolder)
Return S_OK
End Function
Function TreeWalkCallback.LeaveFolder(ByVal pwszFolder As PCWSTR, ByVal ptws As Const TREEWALKERSTATS ptr) As HRESULT
wprintf(@WStr(!"Walker leaving folder %s\n"), pwszFolder)
Return S_OK
End Function
Function TreeWalkCallback.HandleError(ByVal pwszPath As PCWSTR, ByVal ptws As Const TREEWALKERSTATS ptr, ByVal hrError As HRESULT) As HRESULT
wprintf(@WStr(!"Walker encountered error %#x at %s\n"), hrError, pwszPath)
Return S_OK
End Function
'' Stop any * typed on the command line from expanding to a file name
'' https://www.freebasic.net/wiki/KeyPgCommand
Extern _dowildcard Alias "_dowildcard" As Long
Dim Shared _dowildcard As Long = 0
If Command(2) = "" Then
Print "ListFiles searchRoot pattern <depth>"
Print
Print "For example: ListFiles C:\windows *.exe"
Print "depth is optional"
End 1
End If
Dim depth As Long
If Command(3) <> "" Then
depth = ValLng(Command(3))
End If
Print "Looking for " & Command(2) & " in " & Command(1)
dim root As WString*MAX_PATH = WStr(Command(1))
dim pattern As WString*MAX_PATH = WStr(Command(2))
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED Or COINIT_DISABLE_OLE1DDE)
Dim pWalker As IShellTreeWalker ptr = Any
Dim hr As HRESULT = CoCreateInstance(@CLSID_ShellTreeWalker, NULL, CLSCTX_INPROC_SERVER, @IID_IShellTreeWalker, @pWalker)
If hr = S_OK Then
Dim as TreeWalkCallback cb
pWalker->WalkTree(_
WT_NOTIFYFOLDERENTER Or WT_NOTIFYFOLDERLEAVE Or WT_MAXDEPTH, _
@root, _
@pattern, _
depth, _
@cb _
)
pWalker->Release()
Print "Total size of files found is " & Str(cb.totalSize)
End If
Print "Finished"
CoUninitialize()