Results 1 to 9 of 9

Thread: [VB6, Vista] List the Recycle Bin location(s) on a drive

  1. #1

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    5,714

    Arrow [VB6, Vista] List the Recycle Bin location(s) on a drive


    Hard coded paths aren't the best system, since they change from Windows version to version. So AAraya was asking about how to do this, and after seeing a StackOverflow and OldNewThing post about using IPersist to get the class ID of a folder, I wrote this in response. The code guarantees that the locations returned are the current, official system bin locations. Note that, especially on non-system drives, a Recycle Bin location may only exist when there are files in it, so if you don't get any results it doesn't mean that there won't be the next time you delete a file on that drive.

    Main Code

    This project wraps a single main function, FindRecycleBinsOnDrive:

    Code:
    Private Declare Function ILCreateFromPathW Lib "shell32" (ByVal pwszPath As Long) As Long
    Private Declare Sub CoTaskMemFree Lib "ole32.dll" (ByVal PV As Long) ' Frees memory allocated by the shell
    Private Declare Function SysReAllocString Lib "oleaut32.dll" (ByVal pBSTR As Long, Optional ByVal pszStrPtr As Long) As Long
    Private Declare Function SHCreateItemFromParsingName Lib "shell32" (ByVal pszPath As Long, pbc As Any, riid As UUID, ppv As Any) As Long
    
    Private Function FindRecycleBinsOnDrive(sDrive As String) As String()
    Dim pItem As IShellItem
    Dim penum1 As IEnumShellItems, penum2 As IEnumShellItems
    Dim pChild As IShellItem, pChild2 As IShellItem
    Dim lpPath As Long, sPath As String
    Dim sParent As String
    Dim n As Long
    Dim pcl As Long, pcl2 As Long
    Dim gid As oleexp.UUID
    Dim pPersist As oleexp.IPersist
    Dim lAtr As SFGAO_Flags
    Dim sOut() As String
    ReDim sOut(0)
    
    Call SHCreateItemFromParsingName(StrPtr(sDrive), ByVal 0&, IID_IShellItem, pItem)
    
    If (pItem Is Nothing) = False Then
       pItem.BindToHandler ByVal 0&, BHID_EnumItems, IID_IEnumShellItems, penum1
       Do While penum1.Next(1&, pChild, pcl) = S_OK
            pChild.GetAttributes SFGAO_FOLDER Or SFGAO_HIDDEN Or SFGAO_SYSTEM, lAtr
            If ((lAtr And SFGAO_FOLDER) = SFGAO_FOLDER) And ((lAtr And SFGAO_HIDDEN) = SFGAO_HIDDEN) And ((lAtr And SFGAO_SYSTEM) = SFGAO_SYSTEM) Then
                pChild.BindToHandler ByVal 0&, BHID_EnumItems, IID_IEnumShellItems, penum2
                Do While penum2.Next(1&, pChild2, pcl2) = S_OK
                    pChild2.BindToHandler ByVal 0&, BHID_SFObject, IID_IPersist, pPersist
                    If (pPersist Is Nothing) = False Then
                       pPersist.GetClassID gid
                       pChild2.GetDisplayName SIGDN_DESKTOPABSOLUTEPARSING, lpPath
                       If IsEqualGUID(gid, CLSID_RecycleBin) Then
                           pChild2.GetDisplayName SIGDN_FILESYSPATH, lpPath
                           ReDim Preserve sOut(n)
                           sOut(n) = LPWSTRtoStr(lpPath)
                           n = n + 1
                       End If
                    End If
                Loop
            End If
        Loop
    Else
        Debug.Print "Failed to get drive object"
    End If
    
    FindRecycleBinsOnDrive = sOut
    End Function
    
    Private Function CLSID_RecycleBin() As UUID
    '{645ff040-5081-101b-9f08-00aa002f954e}
    Static iid As UUID
     If (iid.Data1 = 0) Then Call DEFINE_UUID(iid, &H645FF040, CInt(&H5081), CInt(&H101B), &H9F, &H8, &H0, &HAA, &H0, &H2F, &H95, &H4E)
     CLSID_RecycleBin = iid
    End Function
    
    Private Function LPWSTRtoStr(lPtr As Long, Optional ByVal fFree As Boolean = True) As String
    SysReAllocString VarPtr(LPWSTRtoStr), lPtr
    If fFree Then
        Call CoTaskMemFree(lPtr)
    End If
    End Function
    The code increases efficiency by only examining folders that have the properties of the Recycle Bin, a folder with the Hidden and System attributes.

    Alternative Method

    I had also written a different method to get the top level bin names. This method uses the KnownFolderManager class to enumerate (just the first level of) files in the Recycle Bin virtual object (which includes all the physical locations on different drives), and then get the top level bin folders of all drives at once:

    Code:
    Private Sub EnumRecycleBinPaths(sBinPaths() As String)
    Dim kfm As New KnownFolderManager
    Dim pk As IKnownFolder
    Dim pItem As IShellItem
    Dim penum1 As IEnumShellItems
    Dim pChild As IShellItem
    Dim lpPath As Long, sPath As String
    Dim sParent As String
    Dim n As Long
    Dim pcl As Long
    
    ReDim sBinPaths(0)
    kfm.GetFolder FOLDERID_RecycleBinFolder, pk
    If (pk Is Nothing) = False Then
       pk.GetShellItem KF_FLAG_DEFAULT, IID_IShellItem, pItem
       pItem.BindToHandler ByVal 0&, BHID_EnumItems, IID_IEnumShellItems, penum1
       Do While penum1.Next(1&, pChild, pcl) = S_OK
            pChild.GetDisplayName SIGDN_FILESYSPATH, lpPath
            sPath = LPWSTRtoStr(lpPath)
            sParent = Left$(sPath, 3)
            sPath = Mid$(sPath, 4)
            sParent = sParent & Left$(sPath, InStr(sPath, "\"))
            arr_add_dedupe sBinPaths, sParent
        Loop
    End If
    
    End Sub
    Private Sub arr_add_dedupe(sAr() As String, sNew As String)
    Dim i As Long
    For i = 0 To UBound(sAr)
        If sAr(i) = sNew Then Exit Sub
    Next
    Debug.Print "New entry=" & sNew
    If (UBound(sAr) = 0) And (sAr(0) = "") Then
        sAr(0) = sNew
    Else
        ReDim Preserve sAr(UBound(sAr) + 1)
        sAr(UBound(sAr)) = sNew
    End If
    End Sub
    This method is simpler, but might be slower if you have tens of thousands of items in the root of your recycle bin (like Plex does).

    Requirements
    -Windows Vista or newer (you can get to IPersist while enumerating with IShellFolder, if you really needed to do this on XP)
    -oleexp.tlb v4.0 or higher
    -oleexp AddOn mIID.bas (included in oleexp download)
    Attached Files Attached Files
    Last edited by fafalone; Sep 4th, 2017 at 11:17 AM.

  2. #2
    Hyperactive Member
    Join Date
    Aug 2011
    Location
    Palm Coast, FL
    Posts
    436

    Re: [VB6, Vista] List the Recycle Bin location(s) on a drive

    Thanks fafalone - works great!

  3. #3
    Hyperactive Member
    Join Date
    Aug 2011
    Location
    Palm Coast, FL
    Posts
    436

    Re: [VB6, Vista] List the Recycle Bin location(s) on a drive

    nevermind...
    Last edited by AAraya; Sep 4th, 2017 at 12:31 PM.

  4. #4
    Hyperactive Member
    Join Date
    Aug 2011
    Location
    Palm Coast, FL
    Posts
    436

    Re: [VB6, Vista] List the Recycle Bin location(s) on a drive

    I believe that the original intent of the FindRecycleBinOnDrive function in the StackOverflow submission was to get the one Recycle Bin folder, not all of the subfolders. That's also what I was looking for in my original question which spawned this awesome code of yours fafalone. So I'm submitting a routine here which will return the parent folder rather than each of the subfolders:

    It's a slight modification to your FindRecycleBinsOnDrive() routine. Rather than returning an array of each of the Recycle Bin subfolders, it just returns the parent folder name and quits the enumeration when a Recycle Bin subfolder is found. Changes to your code are color coded.

    Code:
    Private Function FindRecycleBinOnDrive(sDrive As String) As String
        Dim pItem As IShellItem
        Dim penum1 As IEnumShellItems, penum2 As IEnumShellItems
        Dim pChild As IShellItem, pChild2 As IShellItem, pParent As IShellItem
        Dim lpPath As Long, sPath As String
        Dim sParent As String
        Dim n As Long
        Dim pcl As Long, pcl2 As Long
        Dim gid As oleexp.UUID
        Dim pPersist As oleexp.IPersist
        Dim lAtr As SFGAO_Flags
     
        On Error GoTo e0
        
        Call SHCreateItemFromParsingName(StrPtr(sDrive), ByVal 0&, IID_IShellItem, pItem)
        
        If (pItem Is Nothing) = False Then
           pItem.BindToHandler ByVal 0&, BHID_EnumItems, IID_IEnumShellItems, penum1
           Do While penum1.Next(1&, pChild, pcl) = S_OK
                pChild.GetAttributes SFGAO_FOLDER Or SFGAO_HIDDEN Or SFGAO_SYSTEM, lAtr
                If ((lAtr And SFGAO_FOLDER) = SFGAO_FOLDER) And ((lAtr And SFGAO_HIDDEN) = SFGAO_HIDDEN) And ((lAtr And SFGAO_SYSTEM) = SFGAO_SYSTEM) Then
                    pChild.BindToHandler ByVal 0&, BHID_EnumItems, IID_IEnumShellItems, penum2
                    Do While penum2.Next(1&, pChild2, pcl2) = S_OK
                        pChild2.BindToHandler ByVal 0&, BHID_SFObject, IID_IPersist, pPersist
                        If (pPersist Is Nothing) = False Then
                           pPersist.GetClassID gid
                           pChild2.GetDisplayName SIGDN_DESKTOPABSOLUTEPARSING, lpPath
                           If IsEqualGUID(gid, CLSID_RecycleBin) Then
                               pChild2.GetParent pParent
                               pParent.GetDisplayName SIGDN_FILESYSPATH, lpPath)
                               FindRecycleBinOnDrive = LPWSTRtoStr(lpPath
                               Exit Do
                           End If
                        End If
                    Loop
                End If
            Loop
        Else
            Debug.Print "Failed to get drive object"
        End If
    Exit Function
    e0:
        Debug.Print "FindRecycleBinOnDrive.Error->" & Err.Description & " (0x" & Hex$(Err.Number) & ")"
    End Function
    And here's a modification of your EnumRecycleBinPaths method, which likewise returns only a single path for a given drive. BTW - this routine is the MUCH faster of the two methods for me even when the input drive contains no Recycle Bin.

    Code:
    Private Function EnumRecycleBinPath(sDrive As String) As String
        Dim kfm As New KnownFolderManager
        Dim pk As IKnownFolder
        Dim pItem As IShellItem
        Dim penum1 As IEnumShellItems
        Dim pChild As IShellItem
        Dim lpPath As Long, sPath As String
        Dim sParent As String
        Dim n As Long
        Dim pcl As Long
        On Error GoTo e0
      
        
        kfm.GetFolder FOLDERID_RecycleBinFolder, pk
        If (pk Is Nothing) = False Then
           pk.GetShellItem KF_FLAG_DEFAULT, IID_IShellItem, pItem
           pItem.BindToHandler ByVal 0&, BHID_EnumItems, IID_IEnumShellItems, penum1
           Do While penum1.Next(1&, pChild, pcl) = S_OK                        
                pChild.GetDisplayName SIGDN_FILESYSPATH, lpPath
                sPath = LPWSTRtoStr(lpPath)
                sParent = Left$(sPath, 3)
                If StrComp(sParent, sDrive, vbTextCompare) = 0 Then     'case insensitive comparison of the drives
                    sPath = Mid$(sPath, 4)
                    sParent = sParent & Left$(sPath, InStr(sPath, "\"))
                
                    EnumRecycleBinPath = sParent
                
                    Exit Do
                End If
                
            Loop
        End If
    
    Exit Function
    e0:
        Debug.Print "EnumRecycleBinPath.Error->" & Err.Description & " (0x" & Hex$(Err.Number) & ")"
    End Function
    Note that this code will need to be tweaked for UNC paths since it's expecting a mapped drive letter format as input.
    Last edited by AAraya; Sep 4th, 2017 at 12:59 PM.

  5. #5

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    5,714

    Re: [VB6, Vista] List the Recycle Bin location(s) on a drive

    Yeah I left it returning the S-whatever because I was finding 2 top level bins; I had both C:\$Recycle.Bin\, and C:\RECYCLER, with both of them being system-created and both having the CLSID_RecycleBin class name. So a true case of multiple bins. But everyone's got different needs, so modify away

  6. #6
    Hyperactive Member
    Join Date
    Aug 2011
    Location
    Palm Coast, FL
    Posts
    436

    Re: [VB6, Vista] List the Recycle Bin location(s) on a drive

    I've done some more testing with this and found another big difference in how these two approaches work.

    The EnumRecycleBinPaths method returns only the Recycle Bin(s) on my C drive. I've got an external hard drive E: which is not found.
    The FindRecycleBinOnDrives does find the Recycle Bins on my E drive.

    So even though it's the much slower method of the two, FindRecycleBinOnDrives() is the way I need to go for my needs.

    Hope that this back and forth is helpful to others who may follow this thread later on.

  7. #7
    Hyperactive Member
    Join Date
    Aug 2011
    Location
    Palm Coast, FL
    Posts
    436

    Re: [VB6, Vista] List the Recycle Bin location(s) on a drive

    I left it returning the S-whatever because I was finding 2 top level bins; I had both C:\$Recycle.Bin\, and C:\RECYCLER
    Oh boy, that's a scenario I wasn't considering. I was always expecting one Recycle Bin. I've got to rethink my approach if it's possible that there are multiple.

  8. #8

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    5,714

    Re: [VB6, Vista] List the Recycle Bin location(s) on a drive

    You can still filter out the S- whatever; like the EnumRecycleBins returns, just take the logic it was using.

    But yeah I'm not sure if my situation is some kind of error (not in the code, an error Windows made to create it... it's definitely there) or something common.

    And for UNC, you meant unmapped drives right? If it's mapped it's just another drive letter. You're concerned for \\share\$Recycle.Bin? (does that even happen? on my computer even mapped network drives get permanently deleted, no recycle possible)


    ---------
    Edit: Would also like more information on the situation with the external drive; it's not found even when files from that drive are in the recycle bin, put there with the normal Explorer delete function? Can you output the files as they're being enumerated to see if it's just not listing files even though they're present?
    I was thinking external drive files didn't even get recycled... just permanently deleted like network drives. But a recycle folder might have been created at some point anyway, even if not used. I don't have a true external drive handy at the moment, but network drives and USB flash drives are both permanent-delete-only.
    Last edited by fafalone; Sep 4th, 2017 at 04:11 PM.

  9. #9
    Hyperactive Member
    Join Date
    Aug 2011
    Location
    Palm Coast, FL
    Posts
    436

    Re: [VB6, Vista] List the Recycle Bin location(s) on a drive

    You're concerned for \\server\share\$Recycle.Bin? does that even happen?
    YES. I used to think at one time that removable and network drives did not have a Recycle Bin folder. I'm not certain any more what the current state of this is. Here's some more info about the Recycle Bin and removable drives but it doesn't totally resolve the ambiguity about what type of drive Windows considers to be a removable one. And here's a discussion which seems to indicate that network paths (mapped or UNC) can have the Recycle Bin enabled with some tweaking.

    Would also like more information on the situation with the external drive; it's not found even when files from that drive are in the recycle bin, put there with the normal Explorer delete function
    I checked the Recycle Bin contents on the E drive and there are files in there from as recently as three days ago so something is putting the files in there. I don't know exactly what however since this is not a drive I'm doing anything with, to the best of my knowledge. I'll have to investigate this further.
    Last edited by AAraya; Sep 6th, 2017 at 10:09 AM.

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width