Timebombing A Workbook
This page describes how to limit the functionality of a workbook after a period of time.
There may be circumstances in which you want to limit the functionality of a workbook after a specified
period of time, such as distributing a demonstration or trial version of an application. Excel doesn't lend itself well
to doing this. Nearly any limitation on a workbook can be circumvented by an experienced user. That said, there are a
numbers thing you can do limit the usable life of a workbook, called time-bombing as workbook.
Unfortunately, no VBA-based time-bombing method is foolproof. A skilled user can quite easily circumvent the time-bomb
code and get full access to the workbook, even after it has expired. The code on this page also requires that the
user has macros enabled. If macros are disabled, the code will not work. (See Ensuring Macros
Are Enabled for code to force the user to enable macros.) All that said, the time-bomb methods presented here are probably
"good enough". They will not prevent an experienced (and dishonest) user from circumventing the protection, but the code will
work for the vast majority of Excel users. All protection mechanisms in Excel are really intended to prevent the user from
accidentally changing a value or some code. The security just isn't strong enough to provide real protection of
proprietary information or information or code with intellectual property value. For that level of security, you should be
writing your code in Visual Basic 6 or VB.NET. See Creating A COM Add-In for details about
creating a COM Add-In and Automation Add Ins A Function Libraries for details about creating
an Automation Add-Ins.
A quick note regarding security in Excel: Security of any sort in Excel is extremely weak. There are many password breakers
available on the internet. I use PassWare's VBAKey and ExcelKey that can crack Excel and VBA Project passwords effortlessly,
usually in a matter of seconds. In most cases, the password assigned to an object (a sheet, workbook, or VBA Project) isn't the
only password that will succeed in opening the object. It is one of many mathematically related passwords, any one of which will
work. For example, in some tests of VBAKey, VBAKey will return not the password that I actually used to protect the project, but
a seemingly random string of characters that will pass successfully through the password validation logic.
As I tell all of my commercial clients, password protection, or protection of any sort, should be viewed as a method to keep
the honest user from accidentally changing or deleting something he shouldn't. The security is by no means sufficient to
protect proprietary data or code or code with intellectual property value. The same goes for any sort of registry based key
storage or settings. A skilled user can easily run RegEdit and see the values in the registry. The same hold true for macro
based security. Not only can the VBA password be easily broken, but nearly all macro based security relies on macros being enabled
in the first place and that the Application's EnableEvents setting is True.
It is a one-liner to circumvent either of those two restrictions.
The bottom line is that if you have valuable code and you need more than "honest person" security, then VBA is not sufficient. You
should create a COM Add-In in VB6 or a Shared Add-In or an Excel Workbook in VB.NET and Visual Studio Tools For Office.
All of the procedures desribed on this page use the constant:
Private Const C_NUM_DAYS_UNTIL_EXPIRATION = 30
You should change the value of this constant to number of days after the first usage of the workbook that the workbook
should be disabled. The value provided in the is 30 days. On the 31st day after the first time the workbook is opened,
it will become unusable.
You can download an example workbook here.
This procedure, TimeBombWithDefinedName, uses a hidden defined name to store the expiration date. The first
time the workbook is opened, that defined name will not exist and will be created by the code. (Be sure that you delete this name when
you are done with your own development work on the workbook.) When the defined name is created, it uses
the C_NUM_DAYS_UNTIL_EXPIRATION constant to calculate the expiration date. When workbook is opened
after the first time, the code reads the name ExpirationDate and tests that value against the
current date.
Sub TimeBombWithDefinedName()
Dim ExpirationDate As String
On Error Resume Next
ExpirationDate = Mid(ThisWorkbook.Names("ExpirationDate").Value, 2)
If Err.Number <> 0 Then
ExpirationDate = CStr(DateSerial(Year(Now), _
Month(Now), Day(Now) + C_NUM_DAYS_UNTIL_EXPIRATION))
ThisWorkbook.Names.Add Name:="ExpirationDate", _
RefersTo:=Format(ExpirationDate, "short date"), _
Visible:=False
End If
If CDate(Now) > CDate(ExpirationDate) Then
MsgBox "This workbook trial period has expired.", vbOKOnly
ThisWorkbook.Close savechanges:=False
End If
End Sub
A more extreme version of timebombing code is to permanently delete the workbook once the expiration date has passed. I generally
wouldn't recommend this approach, but it is certainly possible.
CAUTION: This code will permanently delete (not Recycle) the workbook that contains the code. Once deleted, there is
no way to get the workbook back. Be sure you that when you are testing you have another copy of the code.
However you store and calculate the expiration date, you can use the code below to delete the workbook containing the code.
Sub CommintSuicide()
With ThisWorkbook
Application.DisplayAlerts = False
If .Path <> vbNullString Then
.ChangeFileAccess xlReadOnly
Kill .FullName
End If
ThisWorkbook.Close SaveChanges:=False
End With
End Sub
This procedure, TimeBombMakeReadOnly, uses a defined name, just as the previous procedure did, but
rather than closing the workbook, it makes the workbook read-only.
Sub TimeBombMakeReadOnly()
Dim ExpirationDate As String
Dim NameExists As Boolean
On Error Resume Next
ExpirationDate = Mid(ThisWorkbook.Names("ExpirationDate").Value, 2)
If Err.Number <> 0 Then
ExpirationDate = CStr(DateSerial(Year(Now), _
Month(Now), Day(Now) + C_NUM_DAYS_UNTIL_EXPIRATION))
ThisWorkbook.Names.Add Name:="ExpirationDate", _
RefersTo:=Format(ExpirationDate, "short date"), _
Visible:=False
NameExists = False
Else
NameExists = True
End If
If CDate(Now) >= CDate(ExpirationDate) Then
If NameExists = False Then
ThisWorkbook.Save
End If
ThisWorkbook.ChangeFileAccess xlReadOnly
End If
End Sub
This procedure, TimeBombWithRegistry, stores the expiration date in the System Registry. To use
this code, you must include the modRegistry code module, avaiable on the
System Registry page, or use the RegistryWorx.dll available on the
RegistryWorx page. If you use the modRegistry module, you will
also need to include the modGetSystemErrorMessageText, available
here. The modRegistry module
and the modGetSystemErrorMessageText modules are included in the
downloadable example workbook.
Sub TimeBombWithRegistry()
Const C_REG_KEY = "Software\Pearson\Test\Settings"
Dim KeyExists As Boolean
Dim ValueExists As Boolean
Dim ExpirationDate As Long
Dim B As Boolean
KeyExists = RegistryKeyExists(HKEY_CURRENT_USER, C_REG_KEY, False)
If KeyExists = True Then
ValueExists = RegistryValueExists(HKEY_CURRENT_USER, C_REG_KEY, "Expiration")
If ValueExists = True Then
ExpirationDate = RegistryGetValue(HKEY_CURRENT_USER, C_REG_KEY, "Expiration")
Else
ExpirationDate = DateSerial(Year(Now), Month(Now), _
Day(Now) + C_NUM_DAYS_UNTIL_EXPIRATION)
B = RegistryCreateValue(HKEY_CURRENT_USER, C_REG_KEY, "Expiration", CLng(ExpirationDate))
If B = False Then
' error creating registry value
End If
End If
Else
ExpirationDate = DateSerial(Year(Now), Month(Now), _
Day(Now) + C_NUM_DAYS_UNTIL_EXPIRATION)
B = RegistryCreateKey(HKEY_CURRENT_USER, C_REG_KEY)
If B = True Then
B = RegistryCreateValue(HKEY_CURRENT_USER, C_REG_KEY, "Expiration", ExpirationDate)
If B = False Then
' error creating registry value
End If
Else
' error creating registry key
End If
End If
If CLng(Now) > CLng(ExpirationDate) Then
ThisWorkbook.Close savechanges:=False
End If
End Sub
Regardless of which procedure you use to limit usage of the workbook, you should call that procedure for the
Workbook_Open event procedure in the ThisWorkbook code module:
Private Sub Workbook_Open()
TimeBombWithRegistry
End Sub
Note that this will not work if the user has disabled VBA code or has set the Application.EnableEvents
property to False.
This page last updated: 11-Oct-2007