Tuesday, August 29, 2006

The overlooked INI file

Here's one that you may not have looked at before.  ColdFusion has three functions that let you work with Windows style .ini files.  I think most people pretty much ignore these functions, but I've found them to be quite handy in setting up applications.

Granted, if you're using one of the popular CF frameworks like Fusebox or Model-Glue, this may be of little interest since they tend to have ways of dealing with setting up environment variables.

For the rest of us, we generally wind up with some sort of cfswitch/cfcase construct in Application.cfm/.cfc to establish a set of environment variables in the application scope.  When you move your app to a new environment, we edit the values there or add a new case to our switch.

Using ini files instead of the switch approach is just an alternative and doesn't really add much in the way of different capabilities.  It's pretty much a stylistic decision.

I personally find the simplicity of ini files hard to beat, though.  Maybe that's because of my familiarity with them from past computing days.  Ini files (if you haven't worked with them before) are simply collections of name/value pairs stored in a plain text file.

You can segregate groups of name/value pairs with a section name and some bracket syntax.

A typical beginning ini file for an application might look like this:

;This file holds environment variables for myApp

I tend to name my file cfconfig.ini and store it in the root of my app, but you can do what you like.  You just need to put it in a place where cf can see it.

Enough yammering, how about some code:

Application.cfm/cfc excerpt

<!--- Environment variables --->
<cfset iniPath = getDirectoryFromPath(getCurrentTemplatePath()) & "/cfconfig.ini" />
<cfset section = cgi.SERVER_NAME & ":" & cgi.SERVER_PORT & ":" & cgi.REMOTE_USER />
<cfset sectionStruct = getProfileSections(iniPath) />
<cfif structKeyExists(sectionStruct, section)>
               iniPath = getDirectoryFromPath(getCurrentTemplatePath()) & "/cfconfig.ini";
               section = cgi.SERVER_NAME & ":" & cgi.SERVER_PORT & ":" & cgi.REMOTE_USER;
               keylist = structFind(getProfileSections(iniPath),section);
               for (k=1;k lte listLen(keylist);k=k+1)
                    "application.#listGetAt(keylist,k)#" = getProfileString(iniPath,section,listGetAt(keylist,k));
     <cfinclude template="setup.cfm" />

If you use Application.cfc, then this would most likely go in your onApplicationStart method.  For Application.cfm, it usually just appears near the top.  Notice that I use a combination of the cgi.server_name, cgi.server_port and cgi.remote_user as a way to differentiate the different server environments.  This seems to work pretty good for me, although I sometimes have to implement a webserver based login in order to keep different development environments separate.  That's probably not needed by most people, but I tend to have more than one working environment for some apps.  The only reason I had to add the remote_user was because I tended to wind up with several localhost:80 for development environments that might not be set up exactly the same.

I have a file that I use to get an app started that I call setup.cfm.  The complete code follows:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
          iniPath = getDirectoryFromPath(getCurrentTemplatePath()) & "cfconfig.ini";
          section = cgi.SERVER_NAME & ":" & cgi.SERVER_PORT & ":" & cgi.REMOTE_USER;

          if (isDefined("form.fieldnames"))
               // get rid of the two fields for a new value since we handle those separately
               form.fieldnames = listDeleteAt(form.fieldnames,listFind(form.fieldnames,"NEWKEY"));
               form.fieldnames = listDeleteAt(form.fieldnames,listFind(form.fieldnames,"NEWVALUE"));
               // loop over the form fields and add corresponding entries to the current section of the ini file
               for (k=1;k lte listLen(form.fieldnames);k=k+1)
                    setProfileString(iniPath,section,listGetAt(form.fieldnames,k), evaluate("form." & listGetAt(form.fieldnames,k)));     
               // add new name/value pairs to the ini file if they exist
               if (form.newKey neq "")
          } else if (structKeyExists(getProfileSections(iniPath),"default")) {
               // Move any default entries to this section if it is the first time this section is being set up.
               defaultKeys = structFind(getProfileSections(iniPath),"default");
               if (NOT structKeyExists(getProfileSections(iniPath),section))
                    for (i=1;i lte listLen(defaultKeys);i=i+1)
          // reset the application
     <style type="text/css">
               background-color: #cfc;

     <h1>Setup for #application.applicationName#</h1>
     <a href="cfconfig.ini" target="_blank">cfconfig.ini</a>
     <p>Run this page to establish application level variables the first time
     you install the application on a server or visit it with a new url.  
     You can re-run this file at any time to refresh the application
          <form id="setupForm" action="" method="post" name="setupForm">
               <table cellpadding="3" cellspacing="0" border="1">
                    <cfset keylist="">
                    <cfif structKeyExists(getProfileSections(iniPath),section)>
                         <cfset keylist = structFind(getProfileSections(iniPath),section)>
                    <cfloop list="#keylist#" index="key">
                                   <input type="text" name="#key#" value="#getProfileString(iniPath,section,key)#" size="100">
                              <input type="text" name="newKey" value="">
                              <input type="text" name="newValue" value="" size="100">
                         <th colspan="2">
                              <input type="submit" value="Update">
          <h3>Full text of cfconfig.ini</h3>
          <cffile action="read" file="#iniPath#" variable="ini">


The last piece is to put together a starting ini file.  If you create a section called [default], the startup.cfm file will copy any items you have under that section to any new section it creates when you move your app to a new location.

A few other items to note; if you use cflogin to protect your entire app, you probably will not be able to get to your setup.cfm page to get started.  I usually write a small exception for the setup.cfm file in the cflogin logic.  You also may want to disable the setup file or delete it once you have your ini file established in a production environment.  Let's face it, you probably don't need to deploy setup to production.  You can ALWAYS edit the ini file directly.  It's not really that hard.

The application.cfm file excerpt I have here is being used in a development environment, which is why you don't see a test for determining if the application has been initialized.  It's less robust, but it also makes any changes to environment variables immediately available.  Otherwise, I'd have to do things like restart the cf service to see changes to the environment variables.

Let me know if you find this as straight forward as I do.  


  1. Yeah, i just learned about this cause I happen to be studying ColdFusion. I have been through some old applications where the INI file is read in manually and parsed and converted to strucutres... this would be SOOO much easier.

    Good example :)

  2. Good advise.

    Note that you can also use a .cfm and start the file with a cfabort so that the settings can't be easily read outside of ColdFusion.

  3. That's a good point, Steve. I tend to forget about that because I use the URLScan tool on IIS that prevents .ini files from being dished out by default. I haven't worked with it yet on Windows Server 2003, so I'm not sure if it's still necessary or not.

  4. I'd rather use .properties files - there's more support from the underlying platform.


  5. Dave,
    That must depend on the platform you're using. The first time I saw a .properties file was with eclipse. I've seen .ini files for decades.

  6. .properties files are how java applications configure themselves. ColdFusion is a java application (and what I mean when I say "underlying platform"). So no, it doesn't depend on what platform you are using (except BD.NET).

  7. Hi guys. I am in the closing process of configuring my ini file in an application.cfm file. It is working nicely. I have 3 questions. Can you please take a look at my code and tell me how I can implement some logic so I go to the setup page first with cflogin? Also, I have 3 other places where I go a different application.cfm page. Can I use the same setup file that is setting behind the application.cfm area with the cflogin tag. Also, notice that I have named my config file config.cfm so that I can use ColdFusion to prevent someone from seeing anything if they browse directly to the page. I am not able to post my code.