[VB6, Vista+] Code snippet: KnownFolders made easy with IKnownFolderManager
Using the KnownFolderManager Object
oleexp includes the IKnownFolderManager and IKnownFolder interfaces.
If plan on doing any work with the Known Folders that replaced CSIDL Special Locations and you're working exclusively with Vista and higher, there's now the IKnownFolderManager interface, for which Windows provides a default instance of, which makes your job much easier.
Code:
Dim pKFM as KnownFolderManager
Set pKFM = New KnownFolderManager
Now you have a ready-to-use manager that gives you the following:
.FindFolderFromPath /IDList - Have the path of a special folder and want to get its IKnownFolder interface to find out information about it? You can specify a full or partial path. If you work with PIDLs, e.g. the result from a folder browser that you could use here directly without converting back and forth to a string path, there's a function to get a known folder directly from that as well.
.FolderIdFromCsidl - Still working with CSIDLs? This will ease the transition into support Known Folders.
.GetFolder / .GetFolderByName - You can use either the GUID or canonical name to return a Known Folder object.
Code:
Dim pikf As IKnownFolder
pKFM.FindFolderFromPath StrPtr("C:\Users\Jon\Downloads"), FFFP_EXACTMATCH, pikf
Once you have a Known Folder, in the form of a IKnownFolder object, you can get tons of information about it:
From the main IKnownFolder object, you can get all its file system information, like its PROPERTYKEY, path, pidl, or even an IShellItem interface for it (you can also change the path with SetPath), then there's a significant subset of information in the description:
This is by far the easiest way to work with these special folders on newer versions of Windows.
Most of the oleexp projects use this, but again:
Code:
Public Declare Function SysReAllocString Lib "oleaut32.dll" (ByVal pBSTR As Long, Optional ByVal pszStrPtr As Long) As Long
Public Declare Sub CoTaskMemFree Lib "ole32.dll" (ByVal pv As Long) ' Frees memory allocated by the shell
Public Function BStrFromLPWStr(lpWStr As Long, Optional ByVal CleanupLPWStr As Boolean = True) As String
SysReAllocString VarPtr(BStrFromLPWStr), lpWStr
If CleanupLPWStr Then CoTaskMemFree lpWStr
End Function
Public Sub FreeKnownFolderDefinitionFields(pKFD As KNOWNFOLDER_DEFINITION)
Call CoTaskMemFree(pKFD.pszName)
Call CoTaskMemFree(pKFD.pszDescription)
Call CoTaskMemFree(pKFD.pszRelativePath)
Call CoTaskMemFree(pKFD.pszParsingName)
Call CoTaskMemFree(pKFD.pszToolTip)
Call CoTaskMemFree(pKFD.pszLocalizedName)
Call CoTaskMemFree(pKFD.pszIcon)
Call CoTaskMemFree(pKFD.pszSecurity)
End Sub
'also handy,
Public Declare Function StringFromGUID2 Lib "ole32.dll" (ByRef rguid As Any, ByVal lpsz As String, ByVal cchMax As Long) As Long
Public Sub PrintGUID(TempGUID As UUID)
Dim GuidStr As String
Dim lLen As Long
GuidStr = Space(80)
lLen = StringFromGUID2(TempGUID, GuidStr, 80)
If (lLen) Then
GuidStr = StrConv(Left$(GuidStr, (lLen - 1) * 2), vbFromUnicode)
Debug.Print GuidStr
End If
End Sub
Last edited by fafalone; Sep 4th, 2017 at 02:49 AM.
Re: [VB6, Vista+] Code snippet: KnownFolders made easy with IKnownFolderManager
Here's a practical code example, generating a menu with special folders complete with their custom icons.
UPDATE Sep 4, 2017: Code has been updated to use mIID.bas; you'll need that module to run the code as it appears now. mIID.bas is an included add-on module in the oleexp download. The declares were also missing enums for MENUITEMINFOW, they've been added.
Code:
Dim MII As MENUITEMINFOW
Dim i As Long, j As Long, k As Long
Dim hIcon As Long
Dim pKFM As KnownFolderManager
Dim pKNF As IKnownFolder
Dim kfd As KNOWNFOLDER_DEFINITION
Dim tID As UUID
Dim isiif As IShellItemImageFactory
Dim isi As IShellItem
Dim hMenu As Long
Dim sCap As String
Dim idCmd As Long
Dim pt As POINTAPI
Dim s1 As String, lp1 As Long
Const widBaseDSF As Long = 1000
hMenu = CreatePopupMenu()
Set pKFM = New KnownFolderManager
'Begin folder blocks
For k = 0 To 10 'number special folders in GetSpecialFolderMenuItem
GetSpecialFolderMenuItem k, tID
pKFM.GetFolder tID, pKNF
pKNF.GetFolderDefinition kfd
sCap = BStrFromLPWStr(kfd.pszName)
pKNF.GetShellItem 0, IID_IShellItemImageFactory, isiif
isiif.GetImage 16, 16, SIIGBF_ICONONLY, hIcon
With MII
.cbSize = Len(MII)
.fMask = MIIM_ID Or MIIM_STRING Or MIIM_BITMAP
.wID = widBaseDSF + j
.cch = Len(sCap)
.dwTypeData = StrPtr(sCap)
.hbmpItem = hIcon
Call InsertMenuItemW(hMenu, j, True, MII)
Call DestroyIcon(hIcon)
j = j + 1
End With
Set isiif = Nothing
Set pKNF = Nothing
Next k
'end folder blocks
Call GetCursorPos(pt)
idCmd = TrackPopupMenu(hMenu, TPM_LEFTBUTTON Or TPM_RIGHTBUTTON Or TPM_LEFTALIGN Or TPM_TOPALIGN Or TPM_HORIZONTAL Or TPM_RETURNCMD, pt.X - 50, pt.Y + 10, 0, Me.hWnd, 0)
If idCmd Then
GetSpecialFolderMenuItem idCmd - 1000, tID
pKFM.GetFolder tID, pKNF
pKNF.GetShellItem 0, IID_IShellItem, isi
isi.GetDisplayName SIGDN_FILESYSPATH, lp1
s1 = BStrFromLPWStr(lp1)
ShellExecute Me.hWnd, "explore", s1, "", "", SW_NORMAL
End If
End Sub
Private Sub GetSpecialFolderMenuItem(nIdx As Long, tID As UUID)
Select Case nIdx
Case 0: tID = FOLDERID_Profile
Case 1: tID = FOLDERID_Downloads
Case 2: tID = FOLDERID_Documents
Case 3: tID = FOLDERID_Videos
Case 4: tID = FOLDERID_Music
Case 5: tID = FOLDERID_Pictures
Case 6: tID = FOLDERID_Contacts
Case 7: tID = FOLDERID_Favorites
Case 8: tID = FOLDERID_Links
Case 9: tID = FOLDERID_RoamingAppData
Case 10: tID = FOLDERID_Windows
End Select
End Sub
This shows how handy the object is... instead of extracting and converting and using menu image classes, it's just a couple lines to get the shell item image factory, which returns an hBitmap directly compatible with menus. That code can be placed anywhere, like a button click, and will open the folder clicked on.
Here's the supporting API declares (and all the known folders, not just the ones I picked, in case you want others)... most of these should be standard in any major project so it's not really as bad as it looks...
Code:
Public Declare Function CLSIDFromString Lib "ole32" (ByVal lpszGuid As Long, pGuid As Any) As Long
Public Declare Function SysReAllocString Lib "oleaut32.dll" (ByVal pBSTR As Long, Optional ByVal pszStrPtr As Long) As Long
Public Declare Sub CoTaskMemFree Lib "ole32.dll" (ByVal pv As Long) ' Frees memory allocated by the shell
Public Declare Function InsertMenuItemW Lib "user32" (ByVal hMenu As Long, ByVal uItem As Long, ByVal fByPosition As Boolean, lpmii As MENUITEMINFOW) As Boolean
Public Declare Function DestroyIcon Lib "user32.dll" (ByVal hIcon As Long) As Long
Public Declare Function CreatePopupMenu Lib "user32" () As Long
Public Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
Public Declare Function TrackPopupMenu Lib "user32" (ByVal hMenu As Long, ByVal wFlags As TPM_wFlags, ByVal X As Long, ByVal Y As Long, ByVal nReserved As Long, ByVal hWnd As Long, lprc As Any) As Long
Public Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" (ByVal hWnd As Long, ByVal lpOperation As String, ByVal lpFile As String, ByVal lpParameters As String, ByVal lpDirectory As String, ByVal nShowCmd As ShowWindowTypes) As Long
Public Enum ShowWindowTypes
SW_HIDE = 0
SW_SHOWNORMAL = 1
SW_NORMAL = 1
SW_SHOWMINIMIZED = 2
SW_SHOWMAXIMIZED = 3
SW_MAXIMIZE = 3
SW_SHOWNOACTIVATE = 4
SW_SHOW = 5
SW_MINIMIZE = 6
SW_SHOWMINNOACTIVE = 7
SW_SHOWNA = 8
SW_RESTORE = 9
SW_SHOWDEFAULT = 10
End Enum
Public Enum TPM_wFlags
TPM_LEFTBUTTON = &H0
TPM_RIGHTBUTTON = &H2
TPM_LEFTALIGN = &H0
TPM_CENTERALIGN = &H4
TPM_RIGHTALIGN = &H8
TPM_TOPALIGN = &H0
TPM_VCENTERALIGN = &H10
TPM_BOTTOMALIGN = &H20
TPM_HORIZONTAL = &H0 ' Horz alignment matters more
TPM_VERTICAL = &H40 ' Vert alignment matters more
TPM_NONOTIFY = &H80 ' Don't send any notification msgs
TPM_RETURNCMD = &H100
TPM_HORPOSANIMATION = &H400
TPM_HORNEGANIMATION = &H800
TPM_VERPOSANIMATION = &H1000
TPM_VERNEGANIMATION = &H2000
TPM_NOANIMATION = &H4000
End Enum
Public Type MENUITEMINFOW
cbSize As Long
fMask As MII_Mask
fType As MF_Type ' MIIM_TYPE
fState As MF_State ' MIIM_STATE
wID As Long ' MIIM_ID
hSubMenu As Long ' MIIM_SUBMENU
hbmpChecked As Long ' MIIM_CHECKMARKS
hbmpUnchecked As Long ' MIIM_CHECKMARKS
dwItemData As Long ' MIIM_DATA
dwTypeData As Long ' MIIM_TYPE
cch As Long ' MIIM_TYPE
hbmpItem As Long
End Type
Public Enum MII_Mask
MIIM_STATE = &H1
MIIM_ID = &H2
MIIM_SUBMENU = &H4
MIIM_CHECKMARKS = &H8
MIIM_TYPE = &H10
MIIM_DATA = &H20
MIIM_BITMAP = &H80
MIIM_STRING = &H40
End Enum
Public Enum MF_Type
MFT_STRING = MF_STRING
MFT_BITMAP = MF_BITMAP
MFT_MENUBARBREAK = MF_MENUBARBREAK
MFT_MENUBREAK = MF_MENUBREAK
MFT_OWNERDRAW = MF_OWNERDRAW
MFT_RADIOCHECK = &H200
MFT_SEPARATOR = MF_SEPARATOR
MFT_RIGHTORDER = &H2000
MFT_RIGHTJUSTIFY = MF_RIGHTJUSTIFY
End Enum
Public Enum MF_State
MFS_GRAYED = &H3
MFS_DISABLED = MFS_GRAYED
MFS_CHECKED = MF_CHECKED
MFS_HILITE = MF_HILITE
MFS_ENABLED = MF_ENABLED
MFS_UNCHECKED = MF_UNCHECKED
MFS_UNHILITE = MF_UNHILITE
MFS_DEFAULT = MF_DEFAULT
End Enum
Public Function BStrFromLPWStr(lpWStr As Long, Optional ByVal CleanupLPWStr As Boolean = True) As String
SysReAllocString VarPtr(BStrFromLPWStr), lpWStr
If CleanupLPWStr Then CoTaskMemFree lpWStr
End Function
Last edited by fafalone; Sep 4th, 2017 at 02:37 AM.
Re: [VB6, Vista+] Code snippet: KnownFolders made easy with IKnownFolderManager
Hey fafalone. This looks interesting and potentially promising for an issue I'm working on. Can you post some sample code as to how to get the path for one of these KnownFolders? That would be most helpful!
Re: [VB6, Vista+] Code snippet: KnownFolders made easy with IKnownFolderManager
Code:
Dim pikf As IKnownFolder
pKFM.FindFolderFromPath "C:\Users\Jon\Downloads", FFFP_EXACTMATCH, pikf
This code fails. It appears that the path argument is wanting a Long, not a String. I tried passing the path like this StrPtr(Path) but that didn't help the problem. It seems that the interface to this changed since you created your code examples?
Re: [VB6, Vista+] Code snippet: KnownFolders made easy with IKnownFolderManager
Is your name Jon too?
If you did change the username to yours, what do you mean fails. Does pikf = Nothing after that? What version of Windows-- is that path still the Downloads folder in it? It works on Win7 for me. But in any case, you're much better off loading from folder id rather than worry about getting the current user name to figure out a hard path... mIID has all the KnownFolder ids to use with .GetFolder (FOLDERID_x).
And yes you are meant to pass StrPtr(path); it was updated to that so that Unicode worked.
That's going from a file system path to a IKnownFolder object though-- your post before that says you want to get the path?
To get your Downloads path, for example, you'd go
Code:
Dim lpPath As Long, sPath As String
pKFM.GetFolder FOLDERID_Downloads, pikf
pikf.GetPath KF_FLAG_DEFAULT, lpPath
SysReAllocString VarPtr(sPath), lpPath
CoTaskMemFree lpPath
You can get the official path with that code, then feed the result back to FindFolderFromPath, if you want to double check that it's working.
Last edited by fafalone; Sep 4th, 2017 at 02:45 AM.
Re: [VB6, Vista+] Code snippet: KnownFolders made easy with IKnownFolderManager
Thanks fafalone! I'll try out your code snippet.
And no my name is not Jon! LOL. I guess you had two different users checking this out over the weekend? Which would be odd since your post is over 2 years old!
Re: [VB6, Vista+] Code snippet: KnownFolders made easy with IKnownFolderManager
Nono.. you put my Username in your post, C:\Users\Jon, thats why I was saying that lol, to make sure you were using your username and not a hard coded path w/ mine.
Also check out my code in your recycle bin thread that started this, I went a little crazy with it
Re: [VB6, Vista+] Code snippet: KnownFolders made easy with IKnownFolderManager
When I try this on the Recycle Bin folder I get an automation error on this line:
Code:
pikf.GetPath KF_FLAG_DEFAULT, lpPath
Here's the complete code I'm using, which works fine when I use FOLDERID_Downloads rather than FOLDERID_RecycleBinFolder:
Code:
Dim pKFM As KnownFolderManager
Set pKFM = New KnownFolderManager
Dim pikf As IKnownFolder
Dim lpPath As Long
Dim sPath As String
'pKFM.GetFolder FOLDERID_Downloads, pikf
pKFM.GetFolder FOLDERID_RecycleBinFolder, pikf '<--Automation Error on this line
pikf.GetPath KF_FLAG_DEFAULT, lpPath
SysReAllocString sPath, lpPath
MsgBox sPath
CoTaskMemFree lpPath
Set pKFM = Nothing
Re: [VB6, Vista+] Code snippet: KnownFolders made easy with IKnownFolderManager
Do you have any files in your Recycle Bin? As noted in the other thread, the folder only exists if it's in use. And to confirm, you added the mIID.bas module right? FOLDERID_RecycleBinFolder should be
Code:
Public Function FOLDERID_RecycleBinFolder() As UUID
Static iid As UUID
If (iid.Data1 = 0) Then Call DEFINE_UUID(iid, &HB7534046, CInt(&H3ECB), CInt(&H4C18), &HBE, &H4E, &H64, &HCD, &H4C, &HB7, &HD6, &HAC)
FOLDERID_RecycleBinFolder = iid
End Function
Re: [VB6, Vista+] Code snippet: KnownFolders made easy with IKnownFolderManager
Yes, many files are in the Recycle Bin.
Latest OLEEXP tlb downloaded just last week and in Project references, mIID.bas is in the project, FolderId_RecycleBinFolder UUID definition is correct.
I even created a brand new project with just your code in it and these references and modules with the same result.
Re: [VB6, Vista+] Code snippet: KnownFolders made easy with IKnownFolderManager
Hi, fafalone!
Thank you for information about this interface and nice examples.
I'm trying to use IKnownFolderManager::GetFolderIds method to enum all known folder ids, however VB returns: "Compile error: function or interface marked as restricted, or function uses an Automation type not supported in Visual Basic".
Can you please take a look, how can it be resolved?
Code:
Dim kfid As UUID
Dim nCount As Long
Dim pakfid As Long
Dim i As Long
Dim ptr As Long
Dim pKFM As KnownFolderManager
Set pKFM = New KnownFolderManager
If S_OK = pKFM.GetFolderIds(pakfid, nCount) Then
ptr = pakfid
For i = 1 To nCount
memcpy kfid, ByVal ptr, LenB(kfid)
ptr = ptr + LenB(kfid)
Stop
Next
CoTaskMemFree pakfid
End If
Set pKFM = Nothing
Re: [VB6, Vista+] Code snippet: KnownFolders made easy with IKnownFolderManager
Probably going to be the first param there, don't think VB likes the out-only usertype. First thought is to change it to long* and try to get a pointer to use; dimension an array of uuid(pCount) and use CopyMemory. If that works I'll post it in a bit.
Re: [VB6, Vista+] Code snippet: KnownFolders made easy with IKnownFolderManager
OMG, there are 104 special folders on my Win7 ))))))))))))
fafalone, hi, and thank you.
Code:
long GetFolderIds(
[out] long* ppKFId,
[in, out] UINT *pCount);
do the job. I also changed HRESULT return type by long to be able to check operation for success.
I combined two methods to get physical path:
1) your method with child items enum - (IShellItem -> GetDisplayName (SIGDN_FILESYSPATH)) + added the same for the root IShellItem
2) and IKnownFolder -> GetPath
It looks like m.1 with child enum doesn't make sense for anything, but Recycle bin.
Also, Root IShellItem -> GetDisplayName worse than m.2 because it return Public special folders, not current user.
Code:
Option Explicit
Private Declare Function memcpy Lib "kernel32.dll" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long) As Long
Private Declare Sub CoTaskMemFree Lib "ole32.dll" (ByVal pv As Long)
Private Declare Function SysReAllocString Lib "oleaut32" (ByVal pBSTR As Long, ByVal lpWStr As Long) As Long
Private Sub Form_Load()
Dim kfid As UUID
Dim nCount As Long
Dim pakfid As Long
Dim i As Long
Dim ptr As Long
Dim flags As Long
Dim lpPath As Long
Dim sPath$, sName$, sLocName$
Dim pKFM As KnownFolderManager
Set pKFM = New KnownFolderManager
Dim pKF As IKnownFolder
Dim pKFD As KNOWNFOLDER_DEFINITION
Dim pItem As IShellItem
Dim penum1 As IEnumShellItems
Dim pChild As IShellItem
Dim pcl As Long
pKFM.GetFolderIds pakfid, nCount
if nCount > 0 then
ptr = pakfid
For i = 1 To nCount
memcpy kfid, ByVal ptr, LenB(kfid) 'array[idx] -> UUID
ptr = ptr + LenB(kfid)
If S_OK = pKFM.GetFolder(kfid, pKF) Then 'UUID -> IKnownFolder
If Not (pKF Is Nothing) Then
pKF.GetFolderDefinition pKFD 'IKnownFolder -> KNOWNFOLDER_DEFINITION
sName = BStrFromLPWStr(pKFD.pszName) 'get name
sPath = "": lpPath = 0
On Error Resume Next
pKF.GetShellItem KF_FLAG_DEFAULT, IID_IShellItem, pItem
On Error GoTo 0
'RETRIEVE PATH - method 1 (IShellItem -> GetDisplayName (SIGDN_FILESYSPATH))
If Not (pItem Is Nothing) Then
'try root
pItem.GetAttributes SFGAO_FILESYSTEM, flags
If flags And SFGAO_FILESYSTEM Then
pItem.GetDisplayName SIGDN_FILESYSPATH, lpPath
sPath = BStrFromLPWStr(lpPath, True)
Else
'if no success -> try child
If sName <> "NetworkPlacesFolder" Then 'it can freeze the program
pItem.BindToHandler ByVal 0&, BHID_EnumItems, IID_IEnumShellItems, penum1
If Not (penum1 Is Nothing) Then
If penum1.Next(1&, pChild, pcl) = S_OK Then
pChild.GetAttributes SFGAO_FILESYSTEM, flags
If flags And SFGAO_FILESYSTEM Then
pChild.GetDisplayName SIGDN_FILESYSPATH, lpPath
sPath = BStrFromLPWStr(lpPath, True)
End If
End If
End If
End If
End If
End If
'RETRIEVE PATH - method 2 (IKnownFolder -> GetPath)
sPath = "": lpPath = 0
flags = (KF_FLAG_SIMPLE_IDLIST Or KF_FLAG_DONT_VERIFY Or KF_FLAG_DEFAULT_PATH Or KF_FLAG_NOT_PARENT_RELATIVE)
On Error Resume Next
pKF.GetPath flags, lpPath 'IKnownFolder -> physical path
If Err.Number = 0 And lpPath <> 0 Then
sPath = BStrFromLPWStr(lpPath, True)
End If
On Error GoTo 0
Debug.Print sName & " = " & sPath
End If
End If
Next
CoTaskMemFree pakfid
end if
Set pKFM = Nothing
End Sub
Private Function BStrFromLPWStr(lpWStr As Long, Optional ByVal CleanupLPWStr As Boolean = True) As String
If lpWStr = 0 Then Exit Function
SysReAllocString VarPtr(BStrFromLPWStr), lpWStr
If CleanupLPWStr Then CoTaskMemFree lpWStr
End Function
Last edited by Dragokas; Aug 3rd, 2018 at 01:30 AM.
Reason: Fixed the code
Re: [VB6, Vista+] Code snippet: KnownFolders made easy with IKnownFolderManager
Thanks, will include the updated def with next release.
I'm not following you with the User vs. Public issue; I looked at both results and the only difference I see is Library objects where the shell item gives the default save location (more useful) and the getpath gives the actual object file. And current drive recycler parsing path vs. nothing.
If you plan on really doing anything with these folders, you're probably going to want SIGDN_DESKTOP_ABSOLUTEPARSING, you'll lose the friendly name in the root but be able to enumerate/read/write the ones currently returning blank in cases where they contain file sys objects.
I saw somewhere recommendations to use only User Shell Folders. However, by comaparing this list with IKnownFolderManager method, I see that special folders taken from both reg. keys.
There are some duplicates. Curious, how they interact.
Of course, in XP it's better to use SHGetFolderPath.
If you plan on really doing anything with these folders, you're probably going to want SIGDN_DESKTOP_ABSOLUTEPARSING
Thank you. SIGDN_DESKTOP_ABSOLUTEPARSING returns a little bit better results that m2, like:
CSCFolder = csc://{S-1-5-21-4161311594-4244952198-1204953518-1000}
DocumentsLibrary = ::{031E4825-7B94-4DC3-B131-E946B44C8DD5}\Documents.library-ms
Games = ::{ED228FDF-9EA8-4870-83B1-96B02CFE0D52}
instead of:
CSCFolder =
DocumentsLibrary = C:\Users\Alex\AppData\Roaming\Microsoft\Windows\Libraries\Documents.library-ms
Games =
However, I don't need shell parsing names. And m2 is exactly what I want. My goal: to create a log where specialist can evaluate anomalies in system settings and make decision is system malfunction caused by them; or just for cases where need to do identification where some spec.folder located, like if somebody optimized free space on C: using moving spec.folder / symlink method or w/e and if now he no longer remembers where and what he moved