Fancy File Finding Facilitator

Post your FreeBASIC source, examples, tips and tricks here. Please don’t post code without including an explanation.
Post Reply
adeyblue
Posts: 300
Joined: Nov 07, 2019 20:08

Fancy File Finding Facilitator

Post by adeyblue »

Everybody's doing or needing this again all of a sudden but Dir sometimes has issues and FindFirstFile is boring, so here's a fancy pants COM way of getting someone else to enumerate files matching a pattern for you and just tell you what they find.

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()
srvaldez
Posts: 3379
Joined: Sep 25, 2005 21:54

Re: Fancy File Finding Facilitator

Post by srvaldez »

😂🤣😁
UEZ
Posts: 988
Joined: May 05, 2017 19:59
Location: Germany

Re: Fancy File Finding Facilitator

Post by UEZ »

@adeyblue: thanks for sharing it. It seems that the COM stuff is very powerful (and complicated), e.g. MS Edge automation. What I've been trying to do for some time is to control MS Edge to take a screenshot of a website without displaying it. With IE this was possible.
Post Reply