Updating An add-in Through the Internet
Introduction
When you're offering an add-in through your website you usually have no connection to the people who are using your programming work. It is not unusual that you have discovered (or been pointed at) bugs in your code or that you've done some updating to the utility you'd like your users to benefit from. Sometimes it might be useful if your users could be notified automatically of such updates (many software titles have such a mechanism built-in). This little article shows a way to include that functionality with your add-in.
Assumptions for this article
I've assumed the following:
- You only want to update the add-in itself, not any accompanying files.
- The name of the download file is identical to the add-in's filename.
- Your add-in has a build number.
- There is a html page on your website (or you render one on demand extracting the build number from a database using a query in some php or asp code) which contains nothing else than a build number (no HTML tags surrounding the number).
- The code will create a webquery in a cell that points to that webpage. The cell is named "Available_Build".
Excel 2007 peculiarities
The web query is inserted using VBA, because Excel 2007 disables all connections in an addin by default, until the user clicks the button on the message bar (top of Excel window, below the ribbon) and enables them:

Messagebar showing that something has been disabled in your addin.

What the user sees in the security dialog after clicking the Options button on the message bar.
Also, it appears Excel 2007 does not allow inserting a webquery in one of the add-in's worksheets. To work around this problem, the add-in's "IsAddin" property is temporarily set to False, then the web query is inserted and the property is set back to True.
Updating mechanism
The updating process works as follows:
- The add-in checks when the last update check has been performed.
- If this is more than 7 days ago (or has never been done before), the check is started.
- The add-in refreshes it's web query and compares build numbers.
- If build on web is higher, permission is asked to download and update.
- The current add-in saves itself, appending "(OldVersion)" to its filename (this enables us to overwrite the old add-in with the new one).
Note: I've tried whether marking the add-in file as read-only would enable the code to delete the file itself, but this appears not to work when the file is on a local drive. I've heard reports that when the add-in is on a network folder, this does work. I don't know if it works in all cases though and would be pleased to get your feedback on that (use form at bottom of page or send me an email).
- A message is shown that asks the user to reopen Excel.
- Excel opens the new file. This file also contains code that removes the "(OldVersion)" file.
Update modes
The code handles two modes, automatic updates and manual update checks.
In the automatic case, the webquery is added to the addin, but is refreshed asynchronously. A worksheet change event is scheduled in the class module, which will fire as soon as the webquery has finished updating (or has timed out). This is done so the check for an update can run in the background whilst Excel finishes its startup process. The user won't have to wait (well, not noticably anyway) for your update to do its work.
In the manual case, the web query is updated synchronously (nothing will work in Excel until it has retrieved its result). I did this because in this case you will confuse your user of he does not get "immediate" feedback on the result.
Code
The code that does the actual updating is wrapped in a class module called "clsUpdate", see the entire code below.
'-------------------------------------------------------------------------
' Module : clsUpdate
' Company : JKP Application Development Services (c)
' Author : Jan Karel Pieterse
' Created : 19-2-2007
' Purpose : Class to check for program updates
'-------------------------------------------------------------------------
Option Explicit
Public WithEvents Sht As Worksheet
Private Declare Function URLDownloadToFile Lib "urlmon" _
Alias "URLDownloadToFileA" (ByVal pCaller As Long, _
ByVal szURL As String, ByVal szFileName As String, _
ByVal dwReserved As Long, ByVal lpfnCB As Long) As Long
Private mdtLastUpdate As Date
Private msAppName As String
Private msBuild As String
Private msCheckURL As String
Private msCurrentAddinName As String
Private msDownloadName As String
Private msTempAddInName As String
Private mbManual As Boolean
Private Sub Class_Terminate()
Set Sht = Nothing
End Sub
Private Sub DownloadFile(strWebFilename As String, strSaveFileName As String)
' Download the file.
URLDownloadToFile 0, strWebFilename, strSaveFileName, 0, 0
End Sub
Public Function IsThereAnUpdate(Optional bShowMsg As Boolean = False) As Boolean
Dim sNewBuild As String
On Error Resume Next
SaveSetting AppName, "Updates", "LastUpdate", CStr(Int(Now))
If Err.Number <> 0 And bShowMsg Then
MsgBox "Error retrieving update information, please try again later.", vbInformation + vbOKOnly
End If
End Function
Public Sub DoUpdate()
Dim sNewBuild As String
sNewBuild = ThisWorkbook.Names("Available_build").RefersToRange.Value
If Len(sNewBuild) = 0 Or Len(sNewBuild) > 4 Then
MsgBox "Unable to fetch version information, please try again later.", vbOKOnly + vbInformation
Exit Sub
End If
If CLng(sNewBuild) > CLng(msBuild) Then
If MsgBox("We have an update, do you wish to download?", vbQuestion + vbYesNo) = vbYes Then
DownloadName = "http://www.jkp-ads.com/downloadscript.asp?filename=" & ThisWorkbook.Name
If GetUpdate Then
Application.Cursor = xlDefault
MsgBox "Successfully updated the addin, please restart Excel to start using the new version!", vbOKOnly + vbInformation
Else
Application.Cursor = xlDefault
MsgBox "Updating has failed.", vbInformation + vbOKOnly
End If
Else
Application.Cursor = xlDefault
End If
ElseIf Manual Then
Application.Cursor = xlDefault
MsgBox "Your program is up to date.", vbInformation + vbOKOnly
End If
TidyUp:
On Error GoTo 0
Exit Sub
End Sub
Private Sub Sht_Change(ByVal Target As Range)
Application.Cursor = xlDefault
If Len(Target.Value) <= 4 Then
DoUpdate
Application.Cursor = xlDefault
ElseIf Manual Then
'Query failed to refresh and was called manually
Application.Cursor = xlDefault
MsgBox "Unable to retrieve version information, please try again later", vbInformation + vbOKOnly
End If
Set Sht = Nothing
TidyUp:
On Error GoTo 0
Exit Sub
End Sub
Public Sub PlaceBuildQT(ByVal bManual As Boolean)
Dim oNm As Name
On Error GoTo LocErr
Application.ScreenUpdating = False
For Each oNm In ThisWorkbook.Worksheets("Sheet1").Names
oNm.Delete
Next
If CInt(Left(Application.Version, 2)) > 11 Then
' Trick!! Somehow Excel 2007 cannot insert a web query into an add-in!!
ThisWorkbook.IsAddin = False
End If
With ThisWorkbook.Worksheets("Sheet1").QueryTables.Add(Connection:= _
"URL;" & CheckURL, Destination:=ThisWorkbook.Names( _
"Available_Build").RefersToRange)
.Name = "autosafebuild"
.FieldNames = True
.RowNumbers = False
.FillAdjacentFormulas = False
.PreserveFormatting = True
.RefreshOnFileOpen = False
.BackgroundQuery = Not bManual
.RefreshStyle = xlOverwriteCells
.SavePassword = False
.SaveData = True
.AdjustColumnWidth = False
.RefreshPeriod = 0
.WebSelectionType = xlEntirePage
.WebFormatting = xlWebFormattingNone
.WebPreFormattedTextToColumns = True
.WebConsecutiveDelimitersAsOne = True
.WebSingleBlockTextImport = False
.WebDisableDateRecognition = False
' .WebDisableRedirections = False
On Error Resume Next
.Refresh BackgroundQuery:=Not (bManual)
On Error GoTo 0
If Not bManual Then
Set Sht = ThisWorkbook.Worksheets("Sheet1")
Else
DoUpdate
End If
End With
TidyUp:
If CInt(Left(Application.Version, 2)) > 11 Then
ThisWorkbook.IsAddin = True
' Trick!! Otherwise, Excel 2007 will ask to save your add-in when it closes.
ThisWorkbook.Saved = True
End If
Application.ScreenUpdating = True
On Error GoTo 0
Exit Sub
LocErr:
If CInt(Left(Application.Version, 2)) > 11 Then
ThisWorkbook.IsAddin = True
ThisWorkbook.Saved = True
End If
Application.ScreenUpdating = True
Application.Cursor = xlDefault
If Err.Description Like "*QueryTables*" Then
MsgBox "Error retrieving version information, please try again later.", vbInformation + vbOKOnly
Resume TidyUp
End If
End Sub
Public Property Get Build() As String
Build = msBuild
End Property
Public Property Let Build(ByVal sBuild As String)
msBuild = sBuild
End Property
Public Sub RemoveOldCopy()
CurrentAddinName = ThisWorkbook.FullName
TempAddInName = CurrentAddinName & "(OldVersion)"
On Error Resume Next
Kill TempAddInName
End Sub
Public Function GetUpdate() As Boolean
On Error Resume Next
'If workbook has been saved readonly, we can safely delete the file!
If ThisWorkbook.ReadOnly Then
Err.Clear
Kill CurrentAddinName
End If
LastUpdate = Now
ThisWorkbook.SaveAs TempAddInName
DoEvents
Kill CurrentAddinName
On Error GoTo 0
DownloadFile DownloadName, CurrentAddinName
If Err = 0 Then GetUpdate = True
End Function
Private Property Get CurrentAddinName() As String
CurrentAddinName = msCurrentAddinName
End Property
Private Property Let CurrentAddinName(ByVal sCurrentAddinName As String)
msCurrentAddinName = sCurrentAddinName
End Property
Private Property Get TempAddInName() As String
TempAddInName = msTempAddInName
End Property
Private Property Let TempAddInName(ByVal sTempAddInName As String)
msTempAddInName = sTempAddInName
End Property
Public Property Get DownloadName() As String
DownloadName = msDownloadName
End Property
Public Property Let DownloadName(ByVal sDownloadName As String)
msDownloadName = sDownloadName
End Property
Public Property Get CheckURL() As String
CheckURL = msCheckURL
End Property
Public Property Let CheckURL(ByVal sCheckURL As String)
msCheckURL = sCheckURL
End Property
Public Property Get LastUpdate() As Date
Dim dtNow As Date
dtNow = Int(Now)
mdtLastUpdate = CDate(GetSetting(AppName, "Updates", "LastUpdate", "0"))
If mdtLastUpdate = 0 Then
'Never checked for an update, save today!
SaveSetting AppName, "Updates", "LastUpdate", CStr(Int(dtNow))
End If
LastUpdate = mdtLastUpdate
End Property
Public Property Let LastUpdate(ByVal dtLastUpdate As Date)
mdtLastUpdate = dtLastUpdate
SaveSetting AppName, "Updates", "LastUpdate", CStr(Int(mdtLastUpdate))
End Property
Public Property Get AppName() As String
AppName = msAppName
End Property
Public Property Let AppName(ByVal sAppName As String)
msAppName = sAppName
End Property
Public Property Get Manual() As Boolean
Manual = mbManual
End Property
Public Property Let Manual(ByVal bManual As Boolean)
mbManual = bManual
End Property
Alternatively, we could do this with the InternetExplorer control (and thus need a reference to the associated library) . This could be done like this:
Public Function IsThereAnUpdateUsingIE() As Boolean
Dim oIE As InternetExplorer
Set oIE = New InternetExplorer
With oIE
.Navigate2 CheckURL
Do
Loop Until .Busy = False
If Len(.Document.body.innerhtml) > 0 Then
If CLng(.Document.body.innerhtml) > CLng(Build) Then
IsThereAnUpdate = True
End If
End If
.Quit
End With
Set oIE = Nothing
End Function
In a normal module, we create an instance of this class, set it's initial values and do the updating. The comments in the code describe what is being done:
Option Explicit
Dim mcUpdate As clsUpdate
Public Declare Function InternetGetConnectedState _
Lib "wininet.dll" (lpdwFlags As Long, _
ByVal dwReserved As Long) As Boolean
Function IsConnected() As Boolean
Dim Stat As Long
IsConnected = (InternetGetConnectedState(Stat, 0&) <> 0)
End Function
Sub AutoUpdate()
CheckAndUpdate False
End Sub
Sub ManualUpdate()
On Error Resume Next
Application.OnTime Now, "CheckAndUpdate"
End Sub
Public Sub CheckAndUpdate(Optional bManual As Boolean = True)
Set mcUpdate = New clsUpdate
If bManual Then
Application.Cursor = xlWait
End If
With mcUpdate
'Set intial values of class
'Current build
.Build = 0
'Name of this app, probably a global variable, such as GSAPPNAME
.AppName = "UpdateAnAddin"
'Get rid of possible old backup copy
.RemoveOldCopy
'URL which contains build # of new version
.CheckURL = "http://www.jkp-ads.com/downloads/UpdateAnAddinBuild.htm"
'Started check automatically or manually?
.Manual = bManual
'Check once a week
If (Now - .LastUpdate >= 7) Or bManual Then
.PlaceBuildQT bManual
End If
End With
TidyUp:
On Error GoTo 0
Exit Sub
End Sub
Download Demo
Download the demo file here: Update An addin




Comments
Showing last 8 comments of 35 in total (Show All Comments):Comment by: Merrill McHenry (2/10/2010 12:54:44 PM)My goal is to totally disable a spreadsheet months after it's date of origin (by referencing a date cell i have auto-updated when a data cell is updated). What would be the VBA code (or change from above for that)?
Thank you kindly
Great stuff - thanks
Comment by: Jan Karel Pieterse (2/11/2010 5:42:03 AM)Hi Merrill,
Something like this in the Workbook_Open event in the ThisWorkbook module:
If Date - ThisWorkbook.Names("DateCell").RefersToRange.Value > 60 Then
Msgbox "Workbook has expired!!", vbExclamation
Thisworkbook.Close False
End If
Comment by: Jarom Petrich (4/11/2010 1:53:30 AM)Wow, excellent post.
I'm still a newby to Visual Basic, but have recently created an addin that needs a self updating feature. This addin is stored on a network drive and doesn't have a build number, instead the addin name changes from "MyAddinV1.12", to "MyAddinV1.13". I've tried unsuccessfuly to rework your code to suit my purposes. Any help/insight would be greatly appreciated.
Comment by: Jan Karel Pieterse (4/11/2010 2:27:48 AM)Hi Jarom,
I would change that and use the same addin name.
If you place a new copy of your add-in in that network location, set it to read-only. This means next time you can simply replace the file even while people are using it.
If everyone is using the addin off of the network, you are done. Otherwise for example if people use laptops and are offline sometimes), I would place a simple textfile in the same network folder which only contains your build number. If you give that file the html extension, you can use that in the example code I gave, it should work.
Comment by: david helks (8/14/2010 3:10:57 AM)Hi
sorry to appear dumb.
I tried to run the macro this weekend Fri eve 13/8 and Sat am 14/8. I keep getting 'page not found' error when it tries to look for the url
www.jkp-ads.com/downloads/UpdateAnAddinBuild.htm
the macro just dumps the text of the web page in cell B1 and below. B1 contains text HOME rather than a number
I assume the macro errors because it has set a text string to the version number when it expects a numeric value for the version number
appreciate your advice. Am sure it's something simple
thanks
David
Comment by: Jan Karel Pieterse (8/16/2010 4:41:44 AM)Hi David,
The correct url for that web page is:
www.jkp-ads.com/UpdateAnAddinBuild.htm
Comment by: Matt Holt (8/16/2010 2:51:21 PM)Hi JKP, this is a great utility, thanks for making this available.
Just a quick question, my add-on currently can function when no workbooks are open and it stores any configuration settings to a .properties file.
Am I correct in saying that your utility requires a workbook so it can save the query? Is there anyway to modify it so a workbook doesn't have to be open (and if need be, I can store any settings in my properties file)?
Thanks a bunch,
Matt
Comment by: Jan Karel Pieterse (8/16/2010 11:14:51 PM)Hi Matt,
The code uses a worksheet in your addin itself, so it needs no other open workbooks. Of course you can also use a different approach, where you do not use a webquery to read the web page, but do it all from within VBA, using -for example- the Microsoft Internet Controls object library.
Have a question, comment or suggestion? Then please use this form.
If your question is not directly related to this web page, but rather a more general "How do I do this" Excel question, then I advise you to ask your question here: www.eileenslounge.com.