Building Version Control in Excel

February 15, 2010 by datapig Leave a reply »

One of the hardest things to manage in Excel solutions is version control. You know, that embarrassing knowledge that there are orphaned versions of your application floating through your company. And that horrifying sense that these useless versions are actually being used. There is nothing more embarrassing than asking one of your users to try and catch the splash screen to read off the version number. That's just sad dude…sad.

 

In this post, I'll show you a couple of techniques that will help you hack your own version control for your Excel solutions (BTW: This works for Access too).

 

The Basic Idea

The basic idea behind this technique is actually very simple. You place a version number internally into your Excel solution. Then you keep a version number in network file or a server table.

When your Excel solution opens, it calls back to your server/network file and looks at the version number there. If the internal number matches, the external number, then the Excel solution continues. If the two numbers don't match, then Excel pops up a message and quits.

There are certainly dozens of ways to get this to work.  However, I'll show you two: A method for those of you who don't use a database server, and a method for those of you who have access to a database server.

 

I don't have a database server:

If you don't have the luxury of a database server, you can implement version control using an XML file. Here are the steps:

 Step 1: Create your XML file

Open up Notepad or some other text editor and enter this XML code. Notice the Version element (in line three) is tagged with a 1.

Visual Basic:
  1. <!--xml version="1.0-->
  2. <VersionCheck>
  3. <Version>1<Version>
  4. </VersionCheck>

As you create new versions of your application, the idea is to edit this XML code to increment the version number.

 

 Step 2: Save your XML file

Save your Notepad file as a VersionCheck.xml and place it into a location your users can get to. Typically, this means saving your XML file onto a shared drive or a network directory.

  

Step 3: Create an XML Map to Your VersionCheck File

Open your Excel program/solution, then go to the Developer tab on the Ribbon. There, you will click Import under the XML group.

 

Select the XML file you are mapping.

 

Then assign a place to park it. In this case, I'll just pick A1.

 

Make sure you give the tab with your XML map a distinct name. Here, I'm using VersionCheck.

  

Step 4: Build an On Open Event

Once you have your XML map in place, you need to create an Open event for the workbook that will:

  • Refresh the XML Map
  • Compare the internal version number with the external number
  • Close if there is no match

 

Open the VBE and click "ThisWorkbook". Then choose Workbook from the Object selector.

 

Enter the following code. Notice that you are setting a Constant called ThisVersion. This is what you'll use as the internal version number.

Visual Basic:
  1. Private Sub Workbook_Open()
  2. Const ThisVersion As Integer = 1 '<--Change this internal version number
  3.  
  4. 'Refresh XML Binding
  5. ActiveWorkbook.XmlMaps("VersionCheck_Map").DataBinding.Refresh
  6.  
  7. 'Exit if external version number doesn't match internal number
  8. If Sheets("VersionCheck").Range("A1").Value <> ThisVersion Then
  9. MsgBox "Please download the current version", vbCritical, "You don't have the lastest version!"
  10. ThisWorkbook.Close SaveChanges:=True
  11. Exit Sub
  12. End If
  13.  
  14. End Sub

 

The idea is that each time you build a new version of your application; you will increment both the internal ThisVersion constant and the number in the XML - so they match.

Any user using the latest file, will have the correct match. Any user using an older file will get a message telling them to get the new version.

  

 I have a database server:

If you've got access to a database server, then it's a bit simpler for you.

 

First, create a new table in your database called VersionCheck, with one field called Version. Fill it with the number 1.

 

Next, enter this code in the workbook's Open Event.

Visual Basic:
  1. Private Sub Workbook_Open()
  2. 'Be Sure to set a Reference to the Microsoft ActiveX Data Objects Library.
  3. Dim MyRecordset As ADODB.Recordset
  4. Dim MySQL As String
  5.  
  6. 'Change this to the appropriate database connection string
  7. Const MyConnection As String = "Provider=sqloledb;" & _
  8. "Data Source=204.204.20.24;" & _
  9. "database=SOSDB;" & _
  10. "User ID=SOSUSER;" & _
  11. "Password=SOSPASSWORD"
  12.  
  13. Const MyVersion As Integer = 1 '<;--Change Version Number Here
  14.  
  15. 'Start Recordset and connect to server
  16. Set MyRecordset = New ADODB.Recordset
  17. MySQL = "SELECT [Version] FROM [dboVersionCheck]"
  18. MyRecordset.Open MySQL, MyConnection, adOpenStatic, adLockReadOnly
  19.  
  20. 'Exit if external version number doesn't match internal number
  21. If MyRecordset!Version <> MyVersion Then
  22. MsgBox "Please download the current version", vbCritical, "You don't have the latest version!"
  23. Exit Sub
  24. End If
  25. End Sub

 

Notes:
- Obviously, your users will need to be able to hit the designated external server or network file for this to work.
- As a courtesy, I typically take the user to the download site automatically. But that's for another post.

- I know about SharePoint version control - this is different.
 

If you have a different/better way to handle version control in your applications, I'd love to hear it.

RELATED STUFF

  1. Zoom into Spreadsheet on Double-Click
  2. VBE Tips I Wish I Knew 5 Years Ago
  3. Highlighting the Active Row and Column
  4. Using the Switch Function in Excel
  5. Why is Excel so Negative about Hiding Subtotals
  6. Running an Access Parameter Query from Excel
  7. Creating a Zoom Box in Excel
  8. Smart Scroll Bars in Excel
  9. Excel Zoom Box Font Size Follow Up
  10. Fun with Custom Lists
Advertisement

12 Responses

  1. Daniel says:

    For version control/automatic updates, I have implemented a class that wraps the WinHTTP API, and use it to download a plain TXT file from the download server. The version number is in the first line of the text file; subsequent lines contain a summary of the changes that is displayed in the userform, so that the users can decide whether they want to update or not. The same clsWinHTTP class is also used to download the update itself. All in all, users have to click just once, the rest is automatic.

    "And that horrifying sense that these useless versions are actually being used." -- In those cases where users send me feedback, I'm often surprised what old versions are still being used. I think a lot of people have updating angst!

  2. Drew says:

    @daniel

    I would love to see in action. Can you post some example code in here? Really the part I'm interrested is the auto downloading of the new version. We don't have a HTTP server so I would like to use datapig's version control along with the ability to auto download.

    Thanks

  3. dermotb says:

    Sometimes your updates won't be critical, so you may want to specify a minimum version required - any version older than this will not run, while any version that is newer will simply tell you there is a newer version, and suggest you get it.

    Using a spreadsheet to download a newer version of itself is kinda tricky, so one solution is to launch your app with a separate little spreadsheet that checks versions and does any updating, then it launches the main spreadsheet.

    Oh, and I might just save the version number as text in an ordinary text file or web page. No need for databases and XML unless you're building industrial strength software.

  4. Daniel says:

    Drew,

    to see my autoupdate in action, you can download an older release of my addin (e.g.: http://sourceforge.net/projects/xltoolbox/files/XL_Toolbox_2.62b.exe/download ) and install it. When you start up Excel (and have an internet connection), you should see the update prompt immediately. The addin is open source, so you can examine the code yourself.

    As dermotb pointed out, updating "in place" is essentially not possible. My addin downloads the update installer (generated with InnoSetup), then starts the installer with a special "/update" command line switch. This causes the installer to shut down Excel (by sending a WM_CLOSE message through the Windowing system). When the setup process is finished, the installer restarts Excel.

  5. Roy MacLean says:

    Interesting. Generalizing the pattern, you could use the XML-Map Refresh to perform any initialization with current data. For example, you could load a set of active jobs into a timesheet workbook.

  6. jamescox says:

    Nope, that's no better - and there isn't a 'preview post' button to help me with trial and error.

    Therefore, I surrender (somewhat gracefully) and if anyone is actually interested in what the .xml structure looks like, we'll figure out a way...

  7. datapig says:

    James: try again using these vb tags without the quotes

    "["vb"]"
    Code goes here
    "[/"vb"]"

  8. JP says:

    Jan Karel Pieterse has an article on his site about using the URLDownloadToFile API to check a website for updates and grab the latest file. Can't find a link right now though.

  9. jamescox says:

    I have a need to do version control in other-than-Office applications that do use VBA, and so created

    an all-VBA (no worksheet) version that doesn't use a database server. In general, there will be more

    than one application that needs version control, the following .xml file structure was created:

    Visual Basic:
    1. <!-- VersionMinder.xml -->
    2. <programs>
    3.   <program>
    4.       <progName>So-So App.xlsm</progName>
    5.       <progLocation>H:\Abandon\All\Hope\</progLocation>
    6.       <curVersion>3</curVersion>
    7.       <curVerDate>2009-11-12</curVerDate>
    8.       <minVersion>1</minVersion>
    9.       <users1>Alice Appleby, Betty Barton</users1>
    10.       <users2>Charlie Clark, Dan Delta</users2>
    11.       <resPerson>Fred J. Muggs</resPerson>
    12.   </program>
    13.   <program>
    14.       <progName>Nice App.xlsm</progName>
    15.       <progLocation>H:\Abandon\All\Hope\</progLocation>
    16.       <curVersion>4</curVersion>
    17.       <curVerDate>2009-12-16</curVerDate>
    18.       <minVersion>1</minVersion>
    19.       <users1>Eloise Eckert, Fred Friendly</users1>
    20.       <users2>Garry Goose</users2>
    21.       <resPerson>Mortimer Snerd</resPerson>
    22.   </program>
    23.   <program>
    24.       <progName>Killer App.xlsm</progName>
    25.       <progLocation>H:\Abandon\All\Hope\</progLocation>
    26.       <curVersion>2</curVersion>
    27.       <curVerDate>2010-01-23</curVerDate>
    28.       <minVersion>2</minVersion>
    29.       <users1>Harry Harrison, Ivan Iverson, Janice James</users1>
    30.       <users2>Kevin Killough</users2>
    31.       <resPerson>Joe Friday</resPerson>
    32.   </program>
    33.       <!-- More programs can be added - use above format -->
    34. </programs>

    Visual Basic:
    1. Option Explicit
    2.  
    3. 'Note that as new versions of this workbook are created, the sgVersion value below will need to be changed here and in the VersionMinder.xml file
    4.  
    5. Public Const sgVersionMinder As String = "J:\AllAccess\Read-Only\VersionMinder.xml"
    6. Public Const sgVersion As String = "1"
    7. Public Const sgProgName As String = "Killer App.xlsm"
    8.  
    9.  
    10. Public Sub VersionChecker()
    11.  
    12.     Dim objXML As MSXML2.DOMDocument
    13.     Dim objProgList As MSXML2.IXMLDOMNodeList
    14.     Dim objProg As MSXML2.IXMLDOMNode
    15.  
    16.     Dim sElementTxt As String
    17.     Dim sprogName As String
    18.     Dim sprogLocation As String
    19.     Dim scurVersion As String
    20.     Dim scurVerDate As String
    21.     Dim sminVersion As String
    22.     Dim susers1 As String
    23.     Dim susers2 As String
    24.     Dim sresPerson As String
    25.     Dim sMsg As String
    26.     Dim lI As Integer
    27.    
    28.     'Optional use of a global program name constant (see above) or if you want to enforce a no-renaming policy, go with the ThisWorkbook.Name option
    29.     sprogName = sgProgName
    30. '    sprogName = ThisWorkbook.Name
    31.  
    32.     Set objXML = New MSXML2.DOMDocument
    33.    
    34.     If Not objXML.Load(sgVersionMinder) Then
    35.         Err.Raise objXML.parseError.ErrorCode, , objXML.parseError.reason
    36.     End If
    37.  
    38.    
    39.     Set objProgList = objXML.SelectNodes("programs/program")
    40.  
    41.     For Each objProg In objProgList
    42.         If objProg.SelectSingleNode("progName").Text = sprogName Then
    43.             'We have a winner! - so cache the program element texts (Note that if the element is present but there is no entry - eg, <minVersion></minVersion> - it is rendered as a "" string
    44.  
    45.             'Note that for ChildNodes, .Length is the equivalent of .Count for other Excel collective objects, and that in walking the collection, the first index number is zero
    46.             For lI = 0 To objProg.ChildNodes.Length - 1
    47.                
    48.                 sElementTxt = objProg.ChildNodes(lI).Text
    49.                
    50.                 Select Case objProg.ChildNodes(lI).BaseName
    51.                
    52.                     Case "progName"
    53.                         'No Op - we already know the program name
    54.                     Case "progLocation"
    55.                         sprogLocation = sElementTxt
    56.                     Case "curVersion"
    57.                         scurVersion = sElementTxt
    58.                     Case "curVerDate"
    59.                         scurVerDate = sElementTxt
    60.                     Case "minVersion"
    61.                         sminVersion = sElementTxt
    62.                     Case "users1"
    63.                         susers1 = sElementTxt
    64.                     Case "users2"
    65.                         susers2 = sElementTxt
    66.                     Case "resPerson"
    67.                         sresPerson = sElementTxt
    68.                     Case Else
    69.                         MsgBox "A new element has been added to the VersionMinder.xml file. There should be a new version of this program for you to download."
    70.                 End Select
    71.             Next lI
    72.             Exit For 'Each
    73.         End If
    74.     Next
    75.    
    76.     If sprogLocation = "" Then
    77.    
    78.         sMsg = "No version information for this workbook was found.  If you have changed the name from " & vbLf & vbLf & sgProgName & vbLf & vbLf
    79.         sMsg = sMsg & "please rename it as shown above.  Keeping the same name will let it inform you when there is a new version available."
    80.         MsgBox sMsg, vbCritical, "Version Verification Failed!"
    81.        
    82.         ThisWorkbook.Close SaveChanges:=False
    83.        
    84.     End If
    85.    
    86.     'Do whatever you want to do in terms of just notifying the user and continue running, up to closing the workbook.
    87.     If scurVersion <> sgVersion Then
    88.         sMsg = "The version of this workbook you are running is version " & sgVersion & vbLf & vbLf
    89.         sMsg = sMsg & "Version " & scurVersion & " created on " & scurVerDate & " is available at " & vbLf & vbLf & vbTab & sprogLocation & vbLf & vbLf
    90.         sMsg = sMsg & "If you need assistance, you may contact " & sresPerson & ", the person currently responsible for maintaining this workbook."
    91.         MsgBox sMsg, vbCritical, "Outdated version detected!"
    92.    
    93.         'In BOFH mode, you could close the workbook at this point
    94.        
    95.     End If
    96.    
    97.     'Environ$("username") could be checked against susers1 as a whitelist of users who are allowed to run the workbook or against susers2 as a blacklist of users who aren't allowed to run it. ("I have no idea why you can't run the workbook - your username is right there in the users2 element!
    98.    
    99.     'Alternately, Environ$("computername") could be used and checked against either susers1 or susers2 to restrict the workbook to be run from specific computers.
    100.    
    101.  
    102. End Sub

    Supposedly, it's possible to select a single node by spedifying an element value with syntax in VBA that looks like:

    Dim objElement As MSXML2.IXMLDOMElement
    Set objElement = objXML.SelectSingleNode("//*[@progName=""TXO_Gate_Compliance_Analyzer.xlsm""]")

    But I've not been able to puzzle out any version that works. If it DID work, it would (probably) be faster than the looping over all program elements implemented below. If anyone knows what the correct syntax is, I'd love to learn it! :)

    Can anyone take a stab at the right syntax?

    BTW - lots of credit to Tim Hastings' blog entry at http://www.nonhostile.com/howto-xml-vb6.asp - it gave

    me a big leg up on getting this to work...

  10. Larph says:

    Great solution to one of my biggest headaches - are you planning on following up with how to take users to the latest version automatically?!

    Thx.

Leave a Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

*

* Copy this password:

* Type or paste password here:

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>