Excel Geeking: Using VBA With Configuration Files

Nothing like a good old fashioned configuration file to make you feel like you’re programming in 1995. But you know what? Sometimes you just need to do it. And it still amazes me how many off-the-shelf applications still rely on INI files to store their settings (I’m looking at you, LotusNotes!).

Personally, I like INI files. I find them easy to work with and easy to deploy. And, of course, it doesn’t hurt that you can read and write to them using VBA.

If you Google reading or writing to INI files using VBA, you’ll get a bunch of different results, all pointing you in the same basic direction. Specifically, you need to use two different Windows APIs in order to do this “GetPrivateProfileString”, and “WritePrivateProfileString”. One reads from an INI file, the other writes to it. I’m going to assume you can figure out which is which.

Invariably, the examples you uncover on Google show that these routines are always separate. One routine fo reading, one for writing.

I hate that.

I like my code as concise as possible while still being as modular as possible. What do I mean by that? I mean that I want a routine (or function in this case) that I can call from another point in the code and choose whether I’m reading or writing to the configuration file. And I want the routine portable enough so I can drop it into any application I want and not have to worry about customizing it too terribly much.

And so, I give you the code below, commented for your pleasure:


'*******************************************************************************
' Declaration for Reading and Wrting to an INI file.
'*******************************************************************************

'++++++++++++++++++++++++++++++++++++++++++++++++++++
' API Functions for Reading and Writing to INI File
'++++++++++++++++++++++++++++++++++++++++++++++++++++

' Declare for reading INI files.
Private Declare Function GetPrivateProfileString Lib "kernel32" _
    Alias "GetPrivateProfileStringA" (ByVal lpApplicationName As String, _
                                      ByVal lpKeyName As Any, _
                                      ByVal lpDefault As String, _
                                      ByVal lpReturnedString As String, _
                                      ByVal nSize As Long, _
                                      ByVal lpFileName As String)As Long
                                      
' Declare for writing INI files.
Private Declare Function WritePrivateProfileString Lib "kernel32" _
    Alias "WritePrivateProfileStringA" (ByVal lpApplicationName As String, _
                                        ByVal lpKeyName As Any, _
                                        ByVal lpString As Any, _
                                        ByVal lpFileName As String)As Long


'++++++++++++++++++++++++++++++++++++++++++++++++++++
' Enumeration for sManageSectionEntry funtion
'++++++++++++++++++++++++++++++++++++++++++++++++++++

Enum iniAction
    iniRead = 1
    iniWrite = 2
End Enum
'*******************************************************************************
' End INI file declaratin Section.
'*******************************************************************************

Function sManageSectionEntry(inAction As iniAction, _
                             sSection As String, _
                             sKey As String, _
                             sIniFile As String, _
                             Optional sValue As String)As String
'*******************************************************************************
' Description:  This reads an INI file section/key combination and
'               returns the read value as a string.
'
' Author:       Scott Lyerly
' Contact:      scott_lyerly@tjx.com, or scott.c.lyerly@gmail.com
'
' Notes:        Requires "Private Declare Function GetPrivateProfileString" and
'               "WritePrivateProfileString" to be added in the declarations
'               at the top of the module.
'
' Name:                 Date:           Init:   Modification:
' sManageSectionEntry   26-Nov-2013     SCL     Original development
'
' Arguments:    inAction    The action to take in teh funciton, reading or writing to
'                           to the INI file. Uses the enumeration iniAction in the
'                           declarations section.
'               sSection    The seciton of the INI file to search
'               sKey        The key of the INI from which to retrieve a value
'               sIniFile    The name and directory location of the INI file
'               sValue      The value to be written to the INI file (if writing - optional)
'
' Returns:      string      The return string is one of three things:
'                           1) The value being sought from the INI file.
'                           2) The value being written to the INI file (should match
'                              the sValue parameter).
'                           3) The word "Error". This can be changed to whatever makes
'                              the most sense to the programmer using it.
'*******************************************************************************

On Error GoTo Err_ManageSectionEntry

    ' Variable declarations.
    Dim sRetBuf         As String
Dim iLenBuf         As Integer
Dim sFileName       As String
Dim sReturnValue    As String
Dim lRetVal         As Long
    
    ' Based on the inAction parameter, take action.
    If inAction = iniRead Then  ' If reading from the INI file.

        ' Set the return buffer to by 256 spaces. This should be enough to
        ' hold the value being returned from the INI file, but if not,
        ' increase the value.
        sRetBuf = Space(256)

        ' Get the size of the return buffer.
        iLenBuf = Len(sRetBuf)

        ' Read the INI Section/Key value into the return variable.
        sReturnValue = GetPrivateProfileString(sSection, _
                                               sKey, _
                                               "", _
                                               sRetBuf, _
                                               iLenBuf, _
                                               sIniFile)

        ' Trim the excess garbage that comes through with the variable.
        sReturnValue = Trim(Left(sRetBuf, sReturnValue))

        ' If we get a value returned, pass it back as the argument.
        ' Else pass "False".
        If Len(sReturnValue) > 0 Then
            sManageSectionEntry = sReturnValue
        Else
            sManageSectionEntry = "Error"
        End If
ElseIf inAction = iniWrite Then ' If writing to the INI file.

        ' Check to see if a value was passed in the sValue parameter.
        If Len(sValue) = 0 Then
            sManageSectionEntry = "Error"

        Else
            
            ' Write to the INI file and capture the value returned
            ' in the API function.
            lRetVal = WritePrivateProfileString(sSection, _
                                               sKey, _
                                               sValue, _
                                               sIniFile)

            ' Check to see if we had an error wrting to the INI file.
            If lRetVal = 0 Then sManageSectionEntry = "Error"

        End If
End If
    
Exit_Clean:
    Exit Function
    
Err_ManageSectionEntry:
    MsgBox Err.Number & ": " & Err.Description
    Resume Exit_Clean

End Function

Some thoughts on what’s going on here:

I’ve taken the two APIs that you would call to read and write to a configuration file and put them into a single function. I’m choosing which action to take based on the inbound inAction parameter.

That parameter, inAction, you may have noticed is an enumeration set up at the top of this module. I like the enumeration in this case because it lets me be very specific in my choice of action to take with regard to the INI file. Yes, I could have saved four lines of code by using a boolean instead. I could have set the function to “read” if TRUE and “write” if FALSE. Except I hate that option. I like enumerations for explicitly spelling out what my options are for value for a specific parameter. That way there’s no confusion.

So what does the implementation of this function look like? Glad you asked! I’ve included a sample routine below that you easily use to test this function out.


Sub SampleINIFunctionImplementaion()

    Const sINI_FILE As String = "C:\Users\scott\Desktop\fruits & veggies.ini"

    Dim sReturn As String

    ' Read the ini file
    sReturn = sManageSectionEntry(iniRead, "Produce", "Fruit", sINI_FILE)
    MsgBox sReturn
    sReturn = sManageSectionEntry(iniRead, "Produce", "Vegetable", sINI_FILE)
    MsgBox sReturn

    ' Write to the ini file
    sReturn = sManageSectionEntry(iniWrite, "Produce", "Fruit", sINI_FILE, "banana")
    sReturn = sManageSectionEntry(iniWrite, "Produce", "Vegetable", sINI_FILE, "squash")

 End Sub

That’s about it. Feel free to copy and paste and use for your own Excel applications.

(Fine print: use at your own risk, blah blah blah…)

Advertisements

14 thoughts on “Excel Geeking: Using VBA With Configuration Files

  1. Hi Scott,
    User settings and other data are not always best saved directly to the workbook file. Your solution of saving to an INI file is great, especially for arrays and larger data volumes.

    Since Excel 97, VBA has had an inbuilt function to save data directly to the registry. They are useful for storing data that is not complex or large. The choice is up to the developer whether to use an INI file or the registry, whichever would work best in a given situation.

    John Walkenbach (http://www.j-walk.com/ss/excel/tips/tip60.htm) has a basic explanation of these functions “SaveSeting”, “GetSetting”, and “DeleteSetting”.

    And the MS Excel Help pages have a reference for them at:
    http://office.microsoft.com/client/helppreview.aspx?AssetId=HV805571839990&lcid=2057&NS=EXCEL.DEV&Version=12

    1. Scott

      Hi David-
      It’s funny you say that, because I am using an INI file to save some data. Things like database path and database name. That way I can point users to any instance of the database I want/need. 

      The way I’m differentiating between data to store in the database versus data to store in an INI file is that anything that will require a list, such as data validation tables, will be database driven, while settings such as database directories or template version numbers will be INI file driven. 

      I love how easy Excel makes saving to the registry, but I have honestly yet to find a use for it. If I had a user form in the template workbook, I might save last selected values, or user form position, or something like that in the registery, but otherwise, no real use for it. 

      1. Hi Scott,
        It sounds like we are both on the same wavelength regarding what information is best stored where. Other than user preferences or “picking up where I left off” I haven’t used the registry for storage. It is mostly stored in the workbook, support file, or the database.

        I was surprised that you took the time to respond on Christmas Day. It is now 5:30 pm on 26 December here in Queensland, Australia. Enjoy the rest of the holiday period with your family and have a bountiful New Year.

  2. Pingback: Excel Geeking – How I Control Releases of Excel Templates | Scott C Lyerly

  3. Bill Benson

    Hi, I assume that something in the API handles avoidance of duplicate settings when the code is run more than once with iniWrite. Is there some documentation on how this API function performs its steps? Thank you for the wrapper by the way, very helpful. For my part, I preferred to allow creation if an ini file flow from this function rather than elsewhere, thus many conditions which in your function result in a value of Error, for me require me to create a path, a file, etc… because I want to be able to call this function (only) on first need, since I do not have a lot of ini files to set up.

    Thought I would mention also that with all these VBA solutions, where the “application” is not really a program and therefore does not have its own space in the same area that other programs register themself, it is challenging to have settings follow the workbook from local path to local path if they move it using windows explorer. That is for me a reason to consider the registry instead of an ini file, because possibly the user fails to keep the ini file in a path the workbook knows where to find it… On the other hand, the registry — if you rely on the same section and key of the VBA registry area — will always be whatever it is, unless the user or another application deletes or overwrites it.

    1. Scott

      I’m not immediately familiar with formal documentation about the API. You can try this link to see if it helps you, it was put together and maintained by one guy over the years and it’s the best resource I’ve seen: http://www.jasinskionline.com/windowsapi/ref/funca.html

      As for the registry, if your Excel/VBA project is going to store settings on a local machine, then yes, absolutely, the registry is the way to go. But, if you are distributing a file across a network with multiple users using the file on multiple machines, then I’ve found the best way (outside of using a database) to store settings that you want that one file to use no matter who is using it on what machine, then a single INI file in a shared network location is a great method to use (IMHO).

  4. Is there a reason you wouldn’t write a class that you instantiate with a .ini file then have methods that are a bit more parallel to other VBA objects like a Dictionary? I feel like that is the intuitive way of working with configuration files, particularly ini files since they’re so one-to-one.

    Now if only we could have something like YAML in VBA!

    As I wrote this comment, I found out about VBData (http://www.xtremevbtalk.com/showthread.php?t=310614). It looks interesting, particularly for tree-structured data.

    1. Scott

      I haven’t encountered an error in the “read” part, in fact that’s what I use the most. Can you post your code here and we can take a look at it?

  5. Great idea and good job, I’ll use it in my Access project with mentioning the source (code courtesy …). Reading and Writing work both well. I have personalized the Function by vetoing the writing if section don’t exists in ini file (with a recursive call to this inimanager itself).
    As in:
    If sManageSectionEntry(iniRead, “Produce”, “Fruit”, sINI_FILE) “Error” Then
    lRetVal = WritePrivateProfileString(sSection, sKey, sValue, sIniFile)
    Else
    lRetVal = 0
    End If
    ‘ Check to see if we had an error wrting to the INI file.
    If lRetVal = 0 Then sManageSectionEntry = “Error”

    Sorry for my English, I’m italian, so Google Translate is my friend 🙂

  6. Pavel

    Very useful, many thanks Scott! Used it with one change — create ini if it not yet exists and fill it with default values.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s