[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)
Last edited by fafalone; Sep 4th, 2017 at 11:17 AM.
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.
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
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.
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.
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.
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.