Accessing detailed file information provided by the Operating System

by Wayne Phillips, 03 March 2010
Applies to: Access 2000+
User Level: Beginner
Views: 1,236
Rating: ***** (7 votes)
Comments: 23   Have your say...

Download Demonstration MDB (Access 2000+) (Zipped)

 UPDATE v1.2a: 16/03/2010 -- added WriteSupport parameter to OpenFile
 UPDATE v1.2: 16/03/2010 -- added write support for the properties (requires Vista or 7).
 UPDATE v1.1: 16/03/2010 -- added XP support. Added fast name resolution for single props.
 UPDATE v1.0c: 03/03/2010 -- minor performance improvements.
 UPDATE v1.0b: 03/03/2010 -- minor bug fix.

Background


Have you ever wanted to find out generic properties about a file, such as the duration length of an MP3 file, or the date a JPEG picture file was taken on a digital camera?

Windows Explorer shows us this sort of information, so surely we can tap into this facility to programmatically access these "properties"?

Note that I use the term "property" rather generically. Some information that Windows Explorer provides is calculated - e.g. the duration of an MP3 file isn't actually stored in the file, but instead is calculated from the filesize divided by the audio bitrate (for fixed bitrate files, anyway). Also, some of the displayed information comes from real properties in the file, such as MP3 ID3 tags or JPEG EXIF data. Windows Explorer properties can also originate from the filesystem, such as the shared status of the file.


So can we make use of the Operating System support for these detailed properties using VBA?


Fortunately, Windows does provide mechanisms for obtaining these detailed properties directly from the Operating System.

Unfortunately, the low-level interfaces that are defined to access these properties are not VB-friendly (they don't support the IDispatch interface and therefore need an external typelibrary reference at a minimum, or preferably a small DLL written in a lower level language). As Sascha Trowitzsch points out later in the comments of this article, there is a type library already defined for helping scripting languages access some of these properties, although I have to say that the options are more limited than the solution I'm going to provide here.

Some of you reading this will be familiar with my article on Disabling the Mouse Wheel in Access Forms and know that I provided a convenient solution to including native machine code into a small VBA module which doesn't require any external references. In effect, it is like having a small DLL file contained completely within your VBA project. Hopefully you can you see where this is heading...

I have done the same here in that the sample database file contains native machine code inside a VBA code module in order to avoid you needing to distribute an extra DLL or typelibrary file with your application should you choose to use this code.

At this point, I must thank Crystal Long (Microsoft Access MVP) for the inspiration to write the code and this article. Check out her ListFiles Database that makes use of this code.

If you like this solution and appreciate the effort that went into creating it, please consider donating:


Usage guide


  1. Import the three VBA class modules 'FilePropertyExplorer', 'FileProperties' and 'FileProperty' into your project.
  2. Call the FilePropertyExplorer.OpenFile(FilePath) method to obtain a reference to an object of type FileProperties.

Overview of each class


FilePropertyExplorer class is the root class. It is a self-instantiated class that contains the native machine code used throughout.

    Public Function OpenFile(FilePath As String, _
                    Optional WriteSupport As Boolean) As FileProperties

Given a valid file path, will return an object of type FileProperties. FileProperties are an enumerable collection of all the available properties for that file.

If you want to support writing to property values, you must open the file with the WriteSupport parameter set to True. This indirectly causes the property set to be more restricted (in terms of enumeration) in that you can then only enumerate direct properties of the file (for example, you won't get properties about the shared status of the file). Write-support is only applicable when run on Windows Vista or newer - in older systems, this is ignored. When the file is opened with write-support, the file is locked until the FileProperties object is released.

TIP: You do not need to declare a variable of type FilePropertyExplorer in your code. Just call FilePropertyExplorer.OpenFile() directly. See the examples below and in the provided database.


FileProperties is a collection class and supports the following members;

    Property Get Item(IndexOrNameOrID) As FileProperty

IndexOrNameOrID can be an integer index (from zero to .Count)
IndexOrNameOrID can be a name that corresponds to FileProperty.Name
IndexOrNameOrID can be an ID that corresponds to FileProperty.ID

    Property Get Count() As Long

Returns the count of properties defined in the collection

This collection class supports the use of For-Each enumeration -- see examples in the following section.


FileProperty is an item class and supports the following methods;

    Property Get Name() As String

Returns the system identity name of the property. e.g. "System.ItemFolderNameDisplay". This is safe to use as an identity field even on non-English systems. A handful of properties do not have a name assigned and the only method of referencing them is by the ID property.

    Property Get NameDesc() As String

This is a localized version of the name, but is not always available.

    Property Get ID() As String

This is a unique identity that can always be used to refer to the property. It consists of a GUID (identifying the property schema) followed by an integer (identifying the property index in the schema).

    Property Get Value() As Variant

This is a VARIANT value respresenting the current value of the property.
TIP: This can be an array for multi-value type properties.

    Property Let Value(Val As Variant)

This is the write method for the property Value. Requires Vista or newer.
Returns a "Permission Denied" error if the property is not writable.

    Property Get ValueDesc() As String

This is a STRING value representing the current value of the property, in LOCALIZED form. Multi-value properties are already combined into a single string. Properties such as "Filesize" might return "5.6Mb", and "Frequency" might return "44khz" rather than the full raw value.

Some simple examples


Obtaining a single property of a file:

    MsgBox FilePropertyExplorer.OpenFile(Path).Item("System.ItemFolderNameDisplay").Value

Obtaining a few properties of a file:

    With FilePropertyExplorer.OpenFile(Path)
        MsgBox .Item("System.ItemFolderNameDisplay").Value
        MsgBox .Item("System.ItemType").Value
        MsgBox .Item("{B725F130-47EF-101A-A5F1-02608C9EEBAC}/14").Value
    End With

Enumerating through all available properties of a file (using For-Each):

    Dim FileProp As FileProperty
    For Each FileProp In FilePropertyExplorer.OpenFile(Path)

        MsgBox FileProp.Name & ": " & FileProp.ValueDesc

    Next

Enumerating through all available properties of a file (using a While loop):

    Dim FileProps As FileProperties
    Dim FileProp As FileProperty
    Dim FilePropsCount As Long
    Dim Index As Long

    Set FileProps = FilePropertyExplorer.OpenFile(Path)
    FilePropsCount = FileProps.Count
    While Index < FilePropsCount

        Set FileProp = FileProps.Item(Index)
			
        MsgBox FileProp.Name & ": " & FileProp.ValueDesc
			
        Index = Index + 1

    Wend

Dumping all properties of a file to the immediate window

    Dim FileProps As FileProperties
    Set FileProps = FilePropertyExplorer.OpenFile(FilePath)

    ' Output header
    Debug.Print String(280, "-")
    Debug.Print "PROPERTY ID"; _
                Tab(50); " | "; "PROPERTY NAME"; _
                Tab(90); " | "; "PROPERTY NAME DESCRIPTION"; _
                Tab(130); " | "; "PROPERTY VALUE DESCRIPTION"; _
                Tab(205); " | "; "PROPERTY VALUE"
    Debug.Print String(280, "-")
    
    ' Enumerate through the properties
    Dim FileProp As FileProperty
    For Each FileProp In FileProps
        
        With FileProp
    
            Debug.Print .ID; _
                        Tab(50); " | "; .Name; _
                        Tab(90); " | "; .NameDesc; _
                        Tab(130); " | "; .ValueDesc; _
                        Tab(205); " | "; VarToStr(.Value)
        
        End With
        
    Next

Enjoy! If you like this code, you could always leave a comment :)

Wayne Phillips



Rate this article:   Your rating: Poor Your rating: Not so good Your rating: Average Your rating: Good Your rating: Excellent

Have your say - comment on this article.

What did you think of 'Accessing detailed file information provided by the Operating System'?


1.

Crystal says...

01 March 2010

 
amazing, simply amazing. I have wanted to do this for months ... and here is such a perfect solution -- better than anything I imagined it could be.

Thank you, Wayne!

Warm Regards,
Crystal

* (: have an awesome day :) *


2.

Wayne says...

01 March 2010

 
Thanks Crystal! As always, you're very welcome.

As you know, this was primarily written for you, but hopefully others can make use of it too.

:)

3.

Crystal says...

02 March 2010

 
Just saw your form ... oh you are too kind! You are the genius that figured this out so fast ;) I am lucky to know you.

I have a question though: Some folks will not have the new OS -- how do I test for that before I run code to get extended properties?

Thanks, Wayne


Warm Regards,
Crystal

*
(: have an awesome day :)
*

4.

Wayne says...

02 March 2010

 
Well, I wouldn't go that far ;), but thank you.

If the operating system isn't supported, then OpenFile will throw an error of &H80042202 "Your operating system does not support this feature.". Therefore, you just need to catch that error.

In your particular situation, I would suggest that if that particular error is thrown, don't keep calling OpenFile() for the remaining files. The reason I say this is because raising and catching exceptions is relatively slow - not noticeable for the most part, but if used in a potentially massive file search, I would aim to avoid it.

Cheers,

Wayne

5.

Alex Dybenko says...

02 March 2010

 
Cool, well done Wayne!

6.

Wayne says...

02 March 2010

 
Thanks, Alex! Hope you are well.

7.

Larry Larsen says...

03 March 2010

 
Hi.. Just what I was looking for..

I do have a small issue/bug/error with the demo, although I'm able to browse and select a file(in this case a simple jpg file) it populates the application with data, but as soon as I attempt to scroll down the list it errors out.

RunTime Error 5

If I select Debug is indicates this area of code:

    If IsArray(Value) Then
VarToStr = Join(Value, ", ")
Else
VarToStr = CStr(Value)
End If

VarToStr = Join(Value, ", ") being the highlighted line.
Using A2003/Vista

Regards

Larry

8.

Wayne says...

03 March 2010

 
Hi Larry,

I know we've resolved this privately by e-mail, but I'll just repeat here in case anyone else has the same issue in future.

You need to make sure that the VBA debugger is NOT set to "break on all errors", since the VarToStr function uses error handling to trap data types that VBA can't work with. This setting is in VBA window > Tools menu > Options > Error trapping.

Kind regards,
Wayne

9.

Sascha says...

05 March 2010

 
Hi Wayne,

As always great code!
(BTW: Would you mind to publish the underlying assembler listing? We never know what you have inside these NativeCode strings. ;-) )

What do you think about the following code that only refers to the Shell Library we can expect is always installed:


Sub TestFileProps(ByVal sPathFile As String)
'Set a reference to 'Microsoft Shell Controls And Automation' !
'ToDo: Error handling
Dim sPath As String, sFile As String
Dim oSH As New Shell32.Shell
Dim oFld As Shell32.Folder
Dim IItms As FolderItems3
Dim oItm As ShellFolderItem
Dim i As Long
Dim sPropName As String, sProp As String
Const SHCONTF_NONFOLDERS = 64&

sPath = Left(sPathFile, InStrRev(sPathFile, "\") - 1)
sFile = Mid(sPathFile, InStrRev(sPathFile, "\") + 1)
Set oFld = oSH.NameSpace(CStr(sPath))
Set IItms = oFld.Items
'Filter folder for file items and exact file name only
IItms.Filter SHCONTF_NONFOLDERS, sFile
Set oItm = IItms.Item(0)
'Props enumeration:
For i = 0 To 300 'Never saw bigger values than 245
sPropName = oFld.GetDetailsOf(vbNullChar, i)
sProp = ""
sProp = oFld.GetDetailsOf(oItm, i)
If Len(sProp) > 0 Then Debug.Print "prop no." & i, sPropName & " = " & sProp
Next i

Set oItm = Nothing
Set IItms = Nothing
Set oFld = Nothing
Set oSH = Nothing
End Sub


It works for my on any file in Vista and shows all included props.

Cheers,
Sascha

10.

Wayne says...

05 March 2010

 
Hi Sascha,

Thanks -- and great find :)

I haven't yet looked at your code in great detail, but from looking at the output, here are my initial observations;

1. The documentation for Folder::GetDetailsOf() is rather lacking. It doesn't indicate that we can get the property name of the property in this manner, nor does it make it very clear that it can be used to get these detailed properties. Typical... but good find.

2. Both the property name and property value appear to relate to the NameDesc and ValueDesc methods of my solution. What concerns me is that these are localized versions of the name and value -- are they outputted the same on your German locale compared to my English locale? Probably not... so we can't use that property name to uniquely identify that property across different language setups. The documentation indicates that the available properties will depend on the folder implementation, and therefore we probably shouldn't assume that the column index number is unique for that property either... (e.g. does the column index number relate to the same property on Vista and Windows 7?)

Can we get the non-localized versions (System.*) using GetDetailsOf??... Since the documentation is lacking, that is anyones guess. (but maybe you know!)

3. Properties that don't have a localized name description (see my demo) aren't included in your output.

4. There's probably a better way than to have a fixed loop for enumeration. :)

Cheers
Wayne

11.

Wayne says...

06 March 2010

 
Sascha; ShellFolderItem::ExtendedProperty looks interesting. It's not enumerable, but you can pass in the ID of the property you want (which you can find by using my demo), rather than using the localized name. It also returns the non-localized version of the Value (i.e. as a Variant rather than a String).

Still, I think I'd use my wrapper for the lower level APIs/interfaces since you can then reference the properties by the more meaningful system identity name, also choose whether you want localized or non-localized versions of the value, and it is optimized for enumeration and reading of multiple properties. The interfaces provided by the Shell32 typelibrary (for scripting), are a bit limited. That said, it's nice to know that there are options.

The next update to my wrapper will include support for older versions of Windows, such as XP, and will be available in a week or so.

12.

Sascha Trowitzsch says...

06 March 2010

 
Hi Wayne,

Sure that was just experimental code.
I rembered there were classes in the shell automation library that could deliver similar results, so that the C interfaces are not necessary. I started from scratch because I didn't found a module I had written some years ago. ;-)
It's just the result of an hour trial&error.
(BTW: I do similar things sometimes to establish COM-Interfaces using VBA user defined types and calling the methods with a short assembler routine and CallWindowProc to avoid a reference to a tlb.)

To 2.: Yes my output is localized. I get the german expressions with my code. On the other side what doesn't work is to use them as an input parameter:
oFld.GetDetailsOf(oItm, "Abmessungen")
I have to use the english locale:
oFld.GetDetailsOf(oItm, "Dimensions")

I also could not find any relation between the SCIDs and the ordinal columnids I use in the loop. I looked into shguids.h and some other includes, into the registry, into the Shell SDK documentation, traced the routine with proc monitor. No find. It's pretty complicated what shell does here.
The only thing I found out is that the guids and expressions reside in propsys.dll and propsys.dll.mui (localized) in Vista.
In propsys.dll there is a XML resource "WINDOWSPROPERTYDESCRIPTION" containing all the possible FMTIDs and PropIDs.

Enough for today. ;-)

"... in a week or so."
I thought you were spending holidays? ;-)
Don't work on this too much an have a nice week.

Ciao, Sascha

13.

Crystal says...

06 March 2010

 
"The next update to my wrapper will include support for older versions of Windows, such as XP, and will be available in a week or so."

cool! Looking forward to it!

thanks, Wayne


Warm Regards,
Crystal

*
(: have an awesome day :)
*

14.

Wayne says...

07 March 2010

 
Hey Sascha,

Thanks for the info. Interesting... English locale for looking up a particular value, but German locale when enumerating the collection. Doesn't seem that much thought went into that design decision.

At least at the lower API/interfaces level we've sorted that out. BTW, the column IDs are folder-dependant and are therefore not static (e.g. the "My Music" folder might return the properties in a different order to other folders). At the lower interface level, we have IShellFolder2::MapColumnToSCID which is what you really need access to at the scripting level too.

ps. Nice to know you use native code in VBA too. I've used the CallWindowProc trick as well in the past... it's great for small routines. As I'm sure you appreciate, a lot of work went into the COM implementation of this design. Lots of things are coming soon that are using it... one of which is the Global Error Handler v2 :)

Cheers,
Wayne

15.

Wayne says...

07 March 2010

 
Hi Crystal, thanks -- I'll be in touch when it's ready.

16.

Crystal says...

16 March 2010

 
Hi Wayne,

Wow ... and wow again !!! I won't have time to dig in to this for a few weeks or more, but am excited to finally build a capability into ListFiles that I thought was just a wild dream.

" added XP support. "

This is big! I cannot wait to test it ... but currently I am working in Win7 where the extra properties are available ...

"Added very fast name resolution for looking up single props."

... as you know we will, thanks, Wayne!

~~~~~~~~~~~~~~~

Can we take this another step? ... to writing property values back? ... is this feasible?

~~~

You are a genius, Wayne

Warm Regards,
Crystal

*
(: have an awesome day :)
*

17.

Wayne says...

16 March 2010

 
Hi Crystal,

As always, thanks :)

Great idea. Now, let me wave my magic wand...

Check out v1.2 - adds write support for properties (Vista or newer). For example, this works fine for me on a simple MP3 file:

FilePropertyExplorer.OpenFile(FilePath, True).Item("System.Title").Value = "My Test Title"


Note that you must supply True to the second parameter of OpenFile in order to support writing to the value (otherwise a "permission denied" error will occur).

Naturally, many properties are going to be read-only (such as calculated values), but certainly properties like MP3 tags and JPEG EXIF data should be writable.

Let me know how you get on with it when you get chance!  No rush though - can't wait to see your ListFiles database when it's ready.

Kind regards,

Wayne

18.

Crystal says...

16 March 2010

 
Hi Wayne,

wait a minute, I have to get over my amazement ... I asked about that 5 minutes ago ... and now it is there?!?

"MP3 tags and JPEG EXIF data should be writable"

and those are exactly the ones I want to do for 2 of my other projects!

Did I ever mention that you are truly remarkable?

Thank you so much, Wayne <big smile>

Warm Regards,
Crystal

*
(: have an awesome day :)
*

19.

Wayne says...

16 March 2010

 
You're very welcome Crystal.

Most of the logic was already in place... it was just a case of connecting the dots so to speak.

It actually made me shake my head in amazement that I hadn't already implemented it... "Doh!" as Homer would say.

Cheers

20.

Theo Callahan says...

12 April 2010

 
OUTSTANDING! Thanks so much. I'm working with a surveyor (of sorts) and we were going through hell to match up GPS with pictures never knowing we could get the GPS _from_ the picture.

Don't know if you know Pivot (http://www.getpivot.com) but it's great for searching/displaying pictures. It's a pain though, to input filenames, tags, etc. and this will make it much easier!

Thanks!

21.

Jack says...

20 June 2010

 
Excellent! Way better than a DLL solution that I used previously. Any chance of extending its functionality to create the file properties? I've processed some MP3 files that lack a Title and Album. Raises an error if I try to modify the values because they don't yet exist.

Also, do you know anything about MP3 Genre? Windows 7 Explorer reports a Genre for various MP3 files, but the following code snippet does not mention anything about Genre.

Again, thanks for some excellent work!

Sub listProps()
Dim props As FileProperties
Dim prop As FileProperty

On Error Resume Next
Set props = FilePropertyExplorer.OpenFile("F:\PODCASTS.DIR\02-050-dgw1111.mp3")
For Each prop In props
Debug.Print prop.Name & " " & Nz(prop.Value, "")
Next prop
Set props = Nothing
Set prop = Nothing
End Sub


Jack

22.

Wayne says...

21 June 2010

 
Thanks, Jack.

Yep - that sounds like something relatively easy to fix. Although I'm pretty tied up on projects at the moment, I'll try to get to it at some point this week.

With regards to the Genre property, I believe the system name is "System.Music.Genre". The reason it's not being displayed by your routine, is because the property value is an array, and your code is throwing an error (masked by the On Error Resume Next). See the VarToStr function in the example form if you want to see how to handle the array easily.

Cheers,

Wayne

23.

Jack says...

22 June 2010

 
Thanks Wayne. Worked like a champ!

Have your say...

Name
E-mail (e-mail address will be kept private)
Comments
Verify Code
Verification Code
Please note: It may take 30 seconds after clicking submit - please be patient to avoid duplicate submissions.   All comments must be approved before being displayed on this web page.  This process may take up to 24 hours.


Site tools

SmartAccess
300+ articles.
Promo code EA-45K2D46TVS