Results 1 to 15 of 15

Thread: [VB6, Vista+] A compact function to retrieve any property by name, locally formatted

  1. #1

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

    [VB6, Vista+] A compact function to retrieve any property by name, locally formatted

    This is related to the greatly expanded property system available in Vista+, and is closely related to the more complete tour of the system in my other projects.

    While this method is inefficient and shouldn't be used for large numbers of properties or large numbers of files*, if you just need a few specific properties from a single file this method is a quick way to get them. The results appear as they do in Explorer's Details view; according to your locale, with units, etc. The key shortcut here is the SHGetPropertyStoreFromParsingName function and other PS_ APIs, which let us skip over all the IShellItem interface work.

    Requirements
    -Windows Vista or higher
    -oleexp 2.0 or higher (no new release related to this code)

    Usage
    After putting the below code in a module, just call the GetPropertyDisplayString(file, property) function, it will return a string with the property as it appears in Explorer. For example, System.Dimensions on a JPG file might return "640 x 480", or System.Width as "100 pixels"; or an AVI's System.Length as "01:30:20". It's more than just raw numbers (although those can be retrieved too; see the larger project).
    sResult = GetPropertyDisplayString("C:\myfile.jpg", "System.Width")

    UPDATE: (2015Dec05) - I think I would have heard about it if it was always the case; but on my system, office documents were counted as slow items even on the OS drive, and getting the IPropertyStore for them failed (0x80040154 Class not registered). This is resolved by adding flags to open slow items and always return a property store, even if some properties couldn't be loaded.

    Code
    Code:
    Public Declare Function PSGetPropertyKeyFromName Lib "propsys.dll" (ByVal pszName As Long, ppropkey As PROPERTYKEY) As Long
    Public Declare Function PSFormatPropertyValue Lib "propsys.dll" (ByVal pps As Long, ByVal ppd As Long, ByVal pdff As PROPDESC_FORMAT_FLAGS, ppszDisplay As Long) As Long
    Public Declare Function SHGetPropertyStoreFromParsingName Lib "shell32" (ByVal pszPath As Long, pbc As Any, ByVal Flags As GETPROPERTYSTOREFLAGS, riid As UUID, ppv As Any) As Long
    Public Declare Function PSGetPropertyDescription Lib "propsys.dll" (PropKey As PROPERTYKEY, riid As UUID, ppv 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 Function GetPropertyDisplayString(szFile As String, szProp As String) As String
    'Gets the string value of the given canonical property; e.g. System.Company, System.Rating, etc
    'This would be the value displayed in Explorer if you added the column in details view
    Dim pkProp As PROPERTYKEY
    Dim pps As IPropertyStore
    Dim lpsz As Long
    Dim ppd As IPropertyDescription
    
    PSGetPropertyKeyFromName StrPtr(szProp), pkProp
    SHGetPropertyStoreFromParsingName StrPtr(szFile), ByVal 0&, GPS_DEFAULT Or GPS_BESTEFFORT Or GPS_OPENSLOWITEM, IID_IPropertyStore, pps
    PSGetPropertyDescription pkProp, IID_IPropertyDescription, ppd
    PSFormatPropertyValue ObjPtr(pps), ObjPtr(ppd), PDFF_DEFAULT, lpsz
    SysReAllocString VarPtr(GetPropertyDisplayString), lpsz
    CoTaskMemFree lpsz
    
    
    End Function
    Include the following in your module only if you're not using the mIID.bas module from the oleexp thread:
    Code:
    Public Sub DEFINE_UUID(Name As UUID, L As Long, w1 As Integer, w2 As Integer, B0 As Byte, b1 As Byte, b2 As Byte, B3 As Byte, b4 As Byte, b5 As Byte, b6 As Byte, b7 As Byte)
      With Name
        .Data1 = L
        .Data2 = w1
        .Data3 = w2
        .Data4(0) = B0
        .Data4(1) = b1
        .Data4(2) = b2
        .Data4(3) = B3
        .Data4(4) = b4
        .Data4(5) = b5
        .Data4(6) = b6
        .Data4(7) = b7
      End With
    End Sub
    Public Function IID_IPropertyStore() As UUID
    'DEFINE_GUID(IID_IPropertyStore,0x886d8eeb, 0x8cf2, 0x4446, 0x8d,0x02,0xcd,0xba,0x1d,0xbd,0xcf,0x99);
    Static IID As UUID
     If (IID.Data1 = 0) Then Call DEFINE_UUID(IID, &H886D8EEB, CInt(&H8CF2), CInt(&H4446), &H8D, &H2, &HCD, &HBA, &H1D, &HBD, &HCF, &H99)
      IID_IPropertyStore = IID
      
    End Function
    Public Function IID_IPropertyDescription() As UUID
    '(IID_IPropertyDescription, 0x6f79d558, 0x3e96, 0x4549, 0xa1,0xd1, 0x7d,0x75,0xd2,0x28,0x88,0x14
    Static IID As UUID
     If (IID.Data1 = 0) Then Call DEFINE_UUID(IID, &H6F79D558, CInt(&H3E96), CInt(&H4549), &HA1, &HD1, &H7D, &H75, &HD2, &H28, &H88, &H14)
      IID_IPropertyDescription = IID
      
    End Function
    ALTERNATIVE: Get directly by PROPERTYKEY
    Now that I've published a complete list of PROPERTYKEY's from propkey.h, if you include the mPKEY.bas module from the oleexp project, you can use those directly like this:
    Code:
    Public Function GetPropertyKeyDisplayString(szFile As String, pkProp As PROPERTYKEY) As String
    Dim pps As IPropertyStore
    Dim lpsz As Long
    Dim ppd As IPropertyDescription
    
    SHGetPropertyStoreFromParsingName StrPtr(szFile), ByVal 0&, GPS_DEFAULT Or GPS_BESTEFFORT Or GPS_OPENSLOWITEM, IID_IPropertyStore, pps
    PSGetPropertyDescription pkProp, IID_IPropertyDescription, ppd
    PSFormatPropertyValue ObjPtr(pps), ObjPtr(ppd), PDFF_DEFAULT, lpsz
    SysReAllocString VarPtr(GetPropertyKeyDisplayString), lpsz
    CoTaskMemFree lpsz
    End Function
    Common Properties
    For a full list of system properties, see propkey.h in the SDK (or unofficial copies online); or the larger projects I have that will enumerate them all.

    Otherwise, see the MSDN post Metadata Properties for Media Files for the popular ones.

    -------------------------------
    * - When working with large numbers of files, or user-selectable properties, it's best to implement IShellItem and IPropertySystem based solutions from the ground up.
    Last edited by fafalone; Dec 5th, 2015 at 02:48 AM.

  2. #2
    Junior Member SaschaT's Avatar
    Join Date
    Mar 2017
    Location
    Berlin, Germany
    Posts
    23

    Re: [VB6, Vista+] A compact function to retrieve any property by name, locally format

    Just to mention...
    This function can be done without any references using the Shell object:
    Code:
    Function GetPropertyDisplayString2(szFile As String, szProp As String) As String
        Dim sFld As String, sFile As String
        Dim oSH As Object   'Shell32.Shell
        Dim oFld As Object  'Shell32.Folder3
        Dim oItm As Object  'Shell32.ShellFolderItem
        
        Set oSH = CreateObject("Shell.Application")
            
        sFld = Left(szFile, InStrRev(szFile, "\") - 1)
        sFile = Mid(szFile, InStrRev(szFile, "\") + 1)
        Set oFld = oSH.NameSpace(CStr(sFld))
        Set oItm = oFld.Items.Item(CStr(sFile))
        GetPropertyDisplayString2 = oItm.ExtendedProperty(szProp)
    End Function
    But honors to you for the oleexp library which I just came across! Really great!

  3. #3

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

    Re: [VB6, Vista+] A compact function to retrieve any property by name, locally format

    Well apart from that being far less fun, there's a few actual differences... one biggie being that it's read-only; using the property system directly can alter some items, and the other being integration with the larger shell API set. Like say the file you're interested in is picked by the user with a common dialog (the new, non-deprecated IFileDialog); that returns an IShellItem with which you can work with the property system immediately instead of resolving the path then passing off (in addition to being able to work with virtual items you can't resolve to a path, making shell.application impossible). This applies more to other ways accessing the property system than the shortcut function here tho; some of my other examples show that and more extensive tasks that the shell object you're using can't do (like enumerate all properties). As always, my work is all about when basic stuff isn't good enough and you really want to dig into advanced shell stuff, or just like doing things the hard way

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

    Re: [VB6, Vista+] A compact function to retrieve any property by name, locally format

    Deleted since I see the answer is listed in the original post...
    Last edited by AAraya; Jan 26th, 2018 at 03:49 PM.

  5. #5
    Hyperactive Member
    Join Date
    Aug 2011
    Location
    Palm Coast, FL
    Posts
    416

    Re: [VB6, Vista+] A compact function to retrieve any property by name, locally format

    Two further questions:

    1) I believe that this solution is going to be limited to files whose path lengths do not exceed MAX_PATH? Shell API does not support long file names. Correct?
    2) Are unicode folder/file paths supported by the Shell automation approach suggested by SashaT? I would think not since we're passing the file as a string rather than as a pointer to the string...

  6. #6

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

    Re: [VB6, Vista+] A compact function to retrieve any property by name, locally format

    1) Yes all shell APIs and features (including property stores) are limited to MAX_PATH. If you have Windows 10 Redstone 2, Build 15063 (Creators Update), you can apparently create an IShellItem for files in long paths, but I don't know if you could open a property store for it.

    2) My guess is no but can't hurt to test, I'll check it out tomorrow.

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

    Re: [VB6, Vista+] A compact function to retrieve any property by name, locally format

    When I include the OLEEXP type lib in my project, I get random crashes and general instability. I'm guessing it's because I've already got many of the same APIs, types, constants declared in this large project but that there are some differences between the two?

    I am interested in the property store API however.

    Can this be used without a type lib?

  8. #8
    PowerPoster
    Join Date
    Feb 2006
    Posts
    24,482

    Re: [VB6, Vista+] A compact function to retrieve any property by name, locally format

    Quote Originally Posted by AAraya View Post
    2) Are unicode folder/file paths supported by the Shell automation approach suggested by SashaT? I would think not since we're passing the file as a string rather than as a pointer to the string...
    Yes, full Unicode. This is not like using the crippled Declare Function approach to making flat DLL calls.

  9. #9

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

    Re: [VB6, Vista+] A compact function to retrieve any property by name, locally format

    Quote Originally Posted by AAraya View Post
    When I include the OLEEXP type lib in my project, I get random crashes and general instability. I'm guessing it's because I've already got many of the same APIs, types, constants declared in this large project but that there are some differences between the two?

    I am interested in the property store API however.

    Can this be used without a type lib?
    Can you give an example of what's causing issues?

    Preference is given for locally declared APIs and Types... if they're defined in your project, the oleexp version can only be used if you explicitly type it that way (Dim x As oleexp.CommonType or Call oleexp.CommonAPI). Only other time would be if it was defined in a different external reference that was lower in priority (the order they appear in References)*. Even in cases of such conflicts, if it's the wrong type you get a wrong type error, not a crash. And the entire typelib isn't loaded; only what's used, which is why it only adds a few bytes to an exe, so oleexp can't effect anything that's not directly calling it.


    There are some particular interfaces that have stability issues; though not any of the ones relating to this thread AFAIK.

    If there is an issue, I'd definitely like to fix it if you could provide more detail.

    While you could use write a class to do all the property store stuff without a typelib, it's very complicated stuff; have a look at e.g. LaVolpe's IFileDialog project to see how you can use the interface vtable without a typelib. In addition to the complexity, you lose the ability to just have typed variables to pass around to interfaces and APIs, making overall design much more complicated.


    * - If your project uses other typelibs with public Windows APIs or Windows system COM interfaces, like OLEGuids or ISHFl_Ex, make sure a newly added oleexp is last in that list so that its definitions don't supercede the other TLB. And never have olelib as a reference-- this project is a fork of that, so if you do, just remove that reference as oleexp contains everything in it. Same for ISHFl_Ex; that is all duplicates as well.
    Last edited by fafalone; May 13th, 2018 at 02:29 AM.

  10. #10
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,541

    Re: [VB6, Vista+] A compact function to retrieve any property by name, locally format

    Quote Originally Posted by fafalone
    ... Only other time would be if it was defined in a different external reference that was lower in priority (the order they appear in References)*. Even in cases of such conflicts, if it's the wrong type you get a wrong type error, not a crash.
    One reason for such a crash in that scenario is ByRef, ByVal differences. Passing ByVal when ByRef is expected and vice versa is a good recipe for crashes and "general instability".

    Maybe, just maybe this could be a generic issue. Users must know what is expected from the TypeLib and API parameters. Sometimes we are so used to a specific API's parameter passed a certain way, we don't notice that the code being added to a project modified the ByVal,ByRef portion of a parameter. Not that this is the case, but CopyMemory API is common culprit. Vast majority of time, you'll see the 1st two parameters of that API declared ByRef. But some will declare them ByVal. If you don't pay attention then crashes can happen. I know that specific case has bitten me a couple times testing OPC (other people's code)
    Last edited by LaVolpe; May 13th, 2018 at 08:33 AM.
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  11. #11

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

    Re: [VB6, Vista+] A compact function to retrieve any property by name, locally format

    Forgot about that but yeah it's definitely an issue; quite a few APIs differ in that manner. I never really liked the inclusion of APIs in the TLB, as I've been hit with that issue a lot because of code reuse (individual functions). But I didn't want to break backwards compatibility, so they stayed; I added a few, but almost all uncommon shell APIs.
    CopyMemory doesn't appear in the TLB; the biggest one for this issue though might be SendMessage, which does. The TLB, and normal use, have the last parameter as ByRef Any, but I've seen ByVal Long too.

    It's still subject to those APIs actually being called though; if there was a Private Declare for one in the form/mod/class, or a Public Declare anywhere in the current project, then the TLB version wouldn't be called. So just adding a TLB to an existing project shouldn't cause an issue as presumably the API not being defined would have caused an error already.

  12. #12
    Junior Member SaschaT's Avatar
    Join Date
    Mar 2017
    Location
    Berlin, Germany
    Posts
    23

    Re: [VB6, Vista+] A compact function to retrieve any property by name, locally format

    Just because I am curious:
    An alternative to the ShellFolderItem.ExtendedProperty method (up in #2) would be Folder3.GetDetailsOf ([ShellFolderItem], columnid) (https://docs.microsoft.com/en-us/win...r-getdetailsof)
    Although there are mentioned just six Column-IDs you can indeed use higher integer values. E.g. for a video file use 285 to get the width of it, 283 to get the height. I found this out enumerating values in the range of 0-1000. Somewhere around 300 seems to be the upper limit.
    Question: How does ColumnID relate to FMTID and PID in a PROPERTYKEY or SHCOLUMNID structure? I did not find any function to convert between these; tried alot. Also I am not sure if the columnids are fixed values or if they are system dependent calculated values - maybe stored in the registry. Any thoughts?

  13. #13

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

    Re: [VB6, Vista+] A compact function to retrieve any property by name, locally format

    One of the tradeoffs for a simpler experience using shell32 instead of the lower level things like IShellFolder is the higher level stuff doesn't include some of the more technical functionality. Column IDs are used to reference information that tells you which system property is displayed by the given column; to get the lower level information on that for a particular folder, the IShellFolder2 interface has MapColumnToSCID. SHCOLUMNID and PROPERTYKEY are the same thing; you can switch the values around without issue.
    For example, if you use MapColumnToSCID for column 0 and 1 (assuming your IShellFolder2 represents a normal folder), and then look at the FMTID/PID of the resulting SHCOLUMNID, you'll see for column 0 it's {B725F130-47EF-101A-A5F1-02608C9EEBAC, 10}, and for column 1 it's {B725F130-47EF-101A-A5F1-02608C9EEBAC, 12} -- which are exactly equal to PKEY_ItemNameDisplay and PKEY_Size, respectively.

    The values you get enumerating the IDs in GetDetailsOf can't be relied on to be constant. They represent different properties not only on different versions of Windows, but special folders will have their own sets of values; e.g. Computer/ThisPC on Win7 the first 6 are Name, Type, Total Size, Free Space, File System, Comments and Network Location. Fonts, Printers.. again, entirely different sets.


    If you're going to be doing something complicated with the properties system, I strongly recommend avoiding the Shell32 ShellFolderItem/etc system all together and going straight to the IShellItem and IPropertyStore system. (Important to note also, if you need support for Unicode, and/or the ability to write properties where applicable, the shell32.dll objects don't support these two things at all).



    To describe this on an even lower level... the ColumnID represents an index in the Column Manager for a given folder. This is how I process columns in my shell browser. You can request an IColumnManager interface for a given folder and enumerate the columns in two ways, first, the default set, which you see when you open it in Explorer, and second, all of the supported columns. This has the advantage of including custom columns supported only by that folder, which won't appear elsewhere.
    But in any case, IColumnManager returns an enumeration in the form of an array of PROPERTYKEYs, and the column IDs are indexes to this.

    Consider the following code,
    Code:
    Private Sub EnumProps()
    Dim siItem As IShellItem
    Dim psv As oleexp.IShellView
    Dim pfv As oleexp.IFolderView
    Dim pColMgr As oleexp.IColumnManager
    Dim nCol As Long, nMax As Long
    Dim lpNm As Long
    Dim pkCols() As PROPERTYKEY
    
    SHCreateItemFromParsingName StrPtr("C:\"), Nothing, IID_IShellItem, siItem
    siItem.BindToHandler 0&, BHID_SFViewObject, IID_IShellView, psv
    If (psv Is Nothing) = False Then
        Set pfv = psv
        If (pfv Is Nothing) = False Then
            Set pColMgr = pfv
            If (pColMgr Is Nothing) = False Then
                pColMgr.GetColumnCount CM_ENUM_ALL, nMax
                ReDim pkCols(nMax - 1)
                pColMgr.GetColumns CM_ENUM_ALL, pkCols(0), nMax
                For nCol = 0 To 10 'Max=UBound(pkCols)
                    PSGetNameFromPropertyKey pkCols(nCol), lpNm
                    Debug.Print "Property(" & nCol & ")=" & LPWSTRtoStr(lpNm)
                Next nCol
            End If
        End If
    End If
    End Sub
    (The code above is applicable to standard folders only, to handle special folders with entirely custom columns, you'd need to get the name from pColMgr.GetColumnInfo, but that's unnecessarily complex for the overview here)

    The first 10 items in the key list returned by the manager are:
    Property(0)=System.ItemNameDisplay
    Property(1)=System.DateModified
    Property(2)=System.ItemTypeText
    Property(3)=System.Size
    Property(4)=System.DateCreated
    Property(5)=System.Author
    Property(6)=System.Keywords
    Property(7)=System.Title
    Property(8)=System.ItemType
    Property(9)=System.DateAccessed
    Property(10)=System.FileAttributes

    and sure enough if we keep going, we find your video properties in the 280s
    Property(280)=System.Video.Compression
    Property(281)=System.Video.Director
    Property(282)=System.Video.EncodingBitrate
    Property(283)=System.Video.FrameHeight
    Property(284)=System.Video.FrameRate
    Property(285)=System.Video.FrameWidth
    Property(286)=System.Video.TotalBitrate
    Last edited by fafalone; Jan 10th, 2021 at 11:26 PM.

  14. #14
    Junior Member SaschaT's Avatar
    Join Date
    Mar 2017
    Location
    Berlin, Germany
    Posts
    23

    Re: [VB6, Vista+] A compact function to retrieve any property by name, locally format

    Ah, thanx alot for the solution! So this means it cannot be retrieved without an IShellview interface as the columns rely on the specific folder representation.
    Great!

  15. #15

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

    Re: [VB6, Vista+] A compact function to retrieve any property by name, locally format

    The top and bottom half of my post are two different ways of accomplishing the same goal; you can use IShellFolder2.GetDetailsOf and .GetDetailsEx without going into any of the lower level stuff in the 2nd half.

    This code will produce the list, including the values of the properties, for a file in C:\

    Code:
    Dim i As Long
    Dim lpNm As Long
    Dim shc As SHCOLUMNID
    Dim pk As PROPERTYKEY
    Dim szc As String, szp As String
    Dim vpv As Variant
    Dim psfDesk As IShellFolder
    Dim psf2 As IShellFolder2
    Dim pidl As Long, pidlChild As Long, pidlRel As Long
    Dim psfDir As IShellFolder
    Set psfDesk = SHGetDesktopFolder()
    
    pidl = ILCreateFromPathW(StrPtr("C:\"))
    psfDesk.BindToObject pidl, 0&, IID_IShellFolder, psfDir
    CoTaskMemFree pidl
    pidlChild = ILCreateFromPathW(StrPtr("C:\procexp.exe"))
    pidlRel = ILFindLastID(pidlChild)
    Set psf2 = psfDir
    
    
    For i = 0 To 10
        psf2.MapColumnToSCID i, shc
        psf2.GetDetailsEx pidlRel, shc, vpv
        
        pk.fmtid = shc.fmtid
        pk.pid = shc.pid
        
        PSGetNameFromPropertyKey pk, lpNm
        Debug.Print "Col(" & i & ")->" & LPWSTRtoStr(lpNm) & "=" & CStr(vpv)
    Next i
    
    CoTaskMemFree pidlChild

    -
    You'll generally want to enumerate the files rather than manually target a single specific file; you do that with the IShellFolder.EnumObjects function which provides an IEnumIDList object:
    Code:
    Dim pEnum As IEnumIDList, pcl As Long
    psfDir.EnumObjects 0&, SHCONTF_NONFOLDERS Or SHCONTF_FOLDERS, pEnum
    Do While pEnum.Next(1&, pidlRel, pcl) = S_OK
    That's where your pidlRel for GetDetailsOf/Ex will come from, instead of the manual single file way with pidlChild I put in the code to keep it compact and clear)
    Last edited by fafalone; Jan 11th, 2021 at 06:47 PM.

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