Results 1 to 6 of 6

Thread: [VB6, Vista+] List all file properties, locale/unit formatted, by modern PROPERTYKEY

  1. #1

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

    [VB6, Vista+] List all file properties, locale/unit formatted, by modern PROPERTYKEY


    Previous VB6 methods for listing file properties haven't used the newer methods, which are especially handy if you're already working with IShellItem. This code is a tour of the modern property system, covering PROPERTYKEY, IPropertyStore, IPropertyDescription, and propsys.dll APIs to take raw values and format them according to the system locale; e.g. adding 'pixels' or 'dpi' to image properties, showing dates/times according to system settings, changing the unreadable number representing attributes into letters, etc. It also goes on to show the raw data, exposing an important method if you do need to work with PROPVARIANT in VB.

    Note that the project does have the option to hide empty properties:



    Requirements
    -Requires oleexp 4.0 or higher (for IDE only, add reference to oleexp.tlb)
    -mIID.bas (included with oleexp)
    -Only works with Windows Vista and higher

    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.

    UPDATE: (2015Dec05-2) - I'm attaching a sample project that shows these properties in a ListView. The values are locally formatted, printed to the LV in Unicode, and the project shows how to load ALL (as opposed to some/most-- trickier than you think) properties into a management array, for not only showing files like the demo, but as an easy system to track Details columns in an Explorer-type file list.
    Project requirements- attached sample project
    (the requirements for the sample code in this post haven't changed, but the attached project does things different and uses more recent things)
    -Windows Vista or higher
    -oleexp v4.0 or higher
    -oleexp addon mIID.bas for IID_ entries (included with oleexp)

    Code
    Code:
    Public Declare Function ILCreateFromPathW Lib "shell32" (ByVal pwszPath As Long) As Long
    Public Declare Function SHCreateItemFromIDList Lib "shell32" (ByVal pidl As Long, riid As UUID, ppv As Any) As Long
    Public Declare Function CoInitialize Lib "ole32.dll" (ByVal pvReserved As Long) As Long
    Public Declare Sub CoTaskMemFree Lib "ole32.dll" (ByVal pv As Long) ' Frees memory allocated by the shell
    Public Declare Function SysReAllocString Lib "oleaut32.dll" (ByVal pBSTR As Long, Optional ByVal pszStrPtr As Long) As Long
    Public Declare Function PSGetNameFromPropertyKey Lib "propsys.dll" (PropKey As PROPERTYKEY, ppszCanonicalName As Long) As Long
    Public Declare Function PSGetPropertyDescription Lib "propsys.dll" (PropKey As PROPERTYKEY, riid As UUID, ppv As Any) 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 PropVariantToVariant Lib "propsys.dll" (ByRef propvar As Any, ByRef var As Variant) As Long
    
    Public Sub EnumFileProperties(sPath As String)
    'sPath can be a file or a folder. Other objects that you might want properties
    'for, a slight re-work can be used to start from its pidl or IShellItem directly
    Dim isif As IShellItem2
    Dim pidlt As Long
    Dim pProp As IPropertyDescription
    Dim pk As PROPERTYKEY
    Dim pPStore As IPropertyStore
    Dim lpe As Long
    Dim lpProp As Long
    Dim i As Long, j As Long
    Dim vProp As Variant
    Dim vrProp As Variant
    Dim vte As VbVarType
    Dim sPrName As String
    Dim sFmtProp As String
    
    Call CoInitialize(0)
    
    'Create a reference to IShellItem2
    pidlt = ILCreateFromPathW(StrPtr(sPath))
    Call SHCreateItemFromIDList(pidlt, IID_IShellItem2, isif)
    Call CoTaskMemFree(pidlt)
    If (isif Is Nothing) Then
        Debug.Print "Failed to get IShellItem2"
        Exit Sub
    End If
    
    'Get the IPropertyStore interface
    isif.GetPropertyStore GPS_DEFAULT Or GPS_BESTEFFORT Or GPS_OPENSLOWITEM, IID_IPropertyStore, pPStore
    If (pPStore Is Nothing) Then
        Debug.Print "Failed to get IPropertyStore"
        Exit Sub
    End If
    
    'Get the number of properties
    pPStore.GetCount lpe
    Debug.Print "Total number of properties=" & lpe
    
    On Error GoTo eper
    For i = 0 To (lpe - 1)
        'Loop through each property; starting with information about which property we're working with
        pPStore.GetAt i, pk
        PSGetNameFromPropertyKey pk, lpProp
        sPrName = BStrFromLPWStr(lpProp)
        Debug.Print "Property Name=" & sPrName & ",SCID={" & Hex$(pk.fmtid.Data1) & "-" & Hex$(pk.fmtid.Data2) & "-" & Hex$(pk.fmtid.Data3) & "-" & Hex$(pk.fmtid.Data4(0)) & Hex$(pk.fmtid.Data4(1)) & "-" & Hex$(pk.fmtid.Data4(2)) & Hex$(pk.fmtid.Data4(3)) & Hex$(pk.fmtid.Data4(4)) & Hex$(pk.fmtid.Data4(5)) & Hex$(pk.fmtid.Data4(6)) & Hex$(pk.fmtid.Data4(7)) & "}, " & pk.pid
    
    
        
        'Some properties don't return a name; if you don't catch that it leads to a full appcrash
        If Len(sPrName) > 1 Then
            'PSFormatPropertyValue takes the raw data and formats it according to the current locale
            'Using these APIs lets us completely avoid dealing with PROPVARIANT, a huge bonus.
            'If you don't need the raw data, this is all it takes
            PSGetPropertyDescription pk, IID_IPropertyDescription, pProp
            PSFormatPropertyValue ObjPtr(pPStore), ObjPtr(pProp), PDFF_DEFAULT, lpProp
            sFmtProp = BStrFromLPWStr(lpProp)
            Debug.Print "Formatted value=" & sFmtProp
        Else
            Debug.Print "Unknown Propkey; can't get formatted value"
        End If
        
        'Now we'll display the raw data
        isif.GetProperty pk, vProp
        PropVariantToVariant vProp, vrProp 'PROPVARIANT is exceptionally difficult to work with in VB, but at
                                           'least for file properties this seems to work for most
        
        vte = VarType(vrProp)
        If (vte And vbArray) = vbArray Then 'this always seems to be vbString and vbArray, haven't encountered other types
            For j = LBound(vrProp) To UBound(vrProp)
                Debug.Print "Value(" & j & ")=" & CStr(vrProp(j))
            Next j
        Else
        Select Case vte
            Case vbDataObject, vbObject, vbUserDefinedType
                Debug.Print "<cannot display this type>"
            Case vbEmpty, vbNull
                Debug.Print "<empty or null>"
            Case vbError
                Debug.Print "<vbError>"
            Case Else
                Debug.Print "Value=" & CStr(vrProp)
        End Select
        End If
    Next i
    Exit Sub
    eper:
        Debug.Print "Property conversion error->" & Err.Description
        Resume Next
    
    End Sub
    
    'Supporting functions
    Public Function IID_IShellItem2() As UUID
    '7e9fb0d3-919f-4307-ab2e-9b1860310c93
    Static IID As UUID
    If (IID.Data1 = 0) Then Call DEFINE_UUID(IID, &H7E9FB0D3, CInt(&H919F), CInt(&H4307), &HAB, &H2E, &H9B, &H18, &H60, &H31, &HC, &H93)
    IID_IShellItem2 = 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
    
    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 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 BStrFromLPWStr(lpWStr As Long, Optional ByVal CleanupLPWStr As Boolean = True) As String
    SysReAllocString VarPtr(BStrFromLPWStr), lpWStr
    If CleanupLPWStr Then CoTaskMemFree lpWStr
    End Function
    Sample output:
    Code:
    Property Name=System.FileAttributes,SCID={B725F130-47EF-101A-A5F1-2608C9EEBAC}, 13
    Formatted value=A
    Value=32
    Also, if your user is selecting which properties to display, which is still done by column IDs, you can map a column id to a PROPERTYKEY like this, where isfPar is the IShellFolder2 the properties are selected from:
    Code:
                isfPar.MapColumnToSCID lColumn, SHColEx
                pk.fmtid = SHColEx.fmtid
                pk.pid = SHColEx.pid
    Reminder: The attached project uses a slightly different method than the code in this post, and has a bunch of other features.


    NOTE
    If you're missing a property you can see in Explorer, like I noticed GPS longitude/latitude, they're not in the PDEF_VIEWABLE property enumeration this sample uses, which avoids properties that don't apply to files and lots of duplicates, but also some less common ones like GPS. So if you're looking for a missing property you saw in Explorer, at the bottom of the InitPKEYs function, change it to PDEF_SYSTEM and then if that doesn't have it, PDEF_ALL.
    Attached Files Attached Files
    Last edited by fafalone; May 18th, 2018 at 04:49 AM. Reason: Attached project updated to reference oleexp.tlb 4.0 or higher

  2. #2

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

    Re: [VB6, Vista+] List all file properties, locale/unit formatted, by modern PROPERTY

    As an addition, I mentioned how column select is still usually done with IShellFolder2, but there is a way to do it with the identical friendly names directly with the modern property system:

    Code:
    Public Declare Function PSGetPropertySystem Lib "propsys.dll" (riid As UUID, ppv As Any) As Long
    
    Dim ppsys As IPropertySystem
    PSGetPropertySystem IID_IPropertySystem, ppsys
    ppsys.EnumeratePropertyDescriptions PDEF_ALL, IID_IPropertyDescriptionList, pPropList
    pPropList.GetCount nProp
    For i = 0 To (nProp - 1)
        pPropList.GetAt i, IID_IPropertyDescription, pProp
        pProp.GetDisplayName lpProp
        sProp = BStrFromLPWStr(lpProp)
        Debug.Print "Prop=" & sProp
        pProp.GetCanonicalName lpProp
        sProp = BStrFromLPWStr(lpProp)
        Debug.Print "CanonicalName=" & sProp
    Next i
    Exit Sub
    
    Public Function IID_IPropertySystem() As UUID
    'IID_IPropertySystem, 0xca724e8a, 0xc3e6, 0x442b, 0x88,0xa4, 0x6f,0xb0,0xdb,0x80,0x35,0xa3
    Static IID As UUID
     If (IID.Data1 = 0) Then Call DEFINE_UUID(IID, &HCA724E8A, CInt(&HC3E6), CInt(&H442B), &H88, &HA4, &H6F, &HB0, &HDB, &H80, &H35, &HA3)
      IID_IPropertySystem = IID
      
    End Function
    
    Public Function IID_IPropertyDescriptionList() As UUID
    'IID_IPropertyDescriptionList, 0x1f9fc1d0, 0xc39b, 0x4b26, 0x81,0x7f, 0x01,0x19,0x67,0xd3,0x44,0x0e
    Static IID As UUID
     If (IID.Data1 = 0) Then Call DEFINE_UUID(IID, &H1F9FC1D0, CInt(&HC39B), CInt(&H4B26), &H81, &H7F, &H1, &H19, &H67, &HD3, &H44, &HE)
      IID_IPropertyDescriptionList = IID
    
    End Function
    The first string displays the friendly name (e.g. 'Author'), and the canonical name is like 'System.Author'. You can get the actual PROPERTYKEY then with pProp.GetPropertyKey.
    Last edited by fafalone; Jun 1st, 2015 at 11:00 AM.

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

    Re: [VB6, Vista+] List all file properties, locale/unit formatted, by modern PROPERTY

    Maybe show an example how one can search for a specific property/value? Think this interface::method would be used: IPropertyEnumTypeList::FindMatchingIndex
    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}

  4. #4

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

    Re: [VB6, Vista+] List all file properties, locale/unit formatted, by modern PROPERTY

    There's quite a few PS__ APIs.. obviously there's using the enumeration above, but if you want to search by the canonical name, you can get a property key directly with PSGetPropertyKeyFromName. Were you thinking of searching by partial match? If so testing display/canonical name as you loop through them in the posted enum would be the easiest.
    Searching by value... not sure what situation you'd have the value before the property you queried for it, but using IPropertyEnumTypeList seems a little awkward... it seems to derive only from IPropertyDescription, which describes a single property.

    Edit: I won't have time to play around with it more until tonight, but if you or anyone else wants to, attached is a version of oleexp with IPropertyEnumType, IPropertyEnumType2, and IPropertyEnumTypeList added it. Edit: Removed; these interfaces are now in the main oleexp release.
    Last edited by fafalone; Dec 5th, 2015 at 02:53 AM.

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

    Re: [VB6, Vista+] List all file properties, locale/unit formatted, by modern PROPERTY

    fafalone, your example is probably good enough. I wasn't familiar with the usage of those interfaces and when reading through this thread, the question I thought of was, "ok, cool, but how do I test to see if a property exists and what it's value is without enumerating"? Thought an answer to that question would be a useful addition to your thread. Thanx.
    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}

  6. #6

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

    Re: [VB6, Vista+] List all file properties, locale/unit formatted, by modern PROPERTY

    Office file bugfix: The code in this post and others relating to IPropertyStore has been updated. I'm not sure if it was every system since I hadn't heard of this bug before, but getting an IPropertyStore for Office files was failing with 0x80040154-Class not registered. For some reason, they were marked as "slow items", even when on the primary drive/partition. Changing GPS_DEFAULT to GPS_DEFAULT Or GPS_BESTEFFORT Or GPS_OPENSLOWITEM fixes this bug, and the inclusion of best effort should also eliminate any similar bugs as it means a store should always be returned, even if some properties can't be loaded.

    ---
    One of the other threads updated with this also shows looking up a single property without enumerating like LaVolpe was talking about, [VB6, Vista+] A compact function to retrieve any property by name, locally formatted

    ---
    PROJECT UPDATED: Added sample project that uses a slightly better method, has some extra features (most significantly showing things in a listview), and provides a property tracking array.
    Last edited by fafalone; Dec 5th, 2015 at 09:20 AM.

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