Introduction:
This article describes an easy approach to keeping
track of each installation of an application. The intent of the example is to
demonstrate the use of a simple web service to collect and store information
about each user and machine running the application.
I was originally driven to coming up with something
like this in response to working for an employer that operated strictly on the
honor system. By that I mean that the company would sell its software by the
seat to individual clients but with no licensing or other restrictions imposed
or enforced by the software that might prevent the installation of additional
copies of that software; for example, if a client purchased five seats of the
product, it was assumed by the company that the client would only install it
five times.
In one particular sale of the product, I felt that it
was likely that the client would install additional copies beyond what they had
purchased (given the magnitude of work they had to perform, five seats seemed
woefully inadequate) and I had asked for approval to implement a licensing
scheme in the code; the company declined to do this for whatever reason. They
did agree for me to devise a method for tracking installations without requiring
a license. The code in this project is a derivation of what I did there mainly
because in the original approach, I was not permitted to terminate the
application if it were not registered; I was only able to collect information on
the installations. What we discovered was that the five purchased seats were
installed over 100 times by the client (at about a $380,000 loss in revenue to
the company in this one instance).
When I wrote this example version, I did it a little
differently in that I create a bogus registry entry that is used to determine
whether or not the application was installed and registered through the web
service. If the product is not registered, I prevent the application from being
used. When I terminate the application, I inform the user that the registration
process is incomplete and that the process requires an active connection to the
internet. This works much like what is seen when you are required to, for
example, activate a copy of Microsoft Word through an internet connection. The
registry key is checked each time the application is executed, if the registry
key exists and its value indicates that the product is registered, then nothing
else happens, if that is not the case, the application will not run until the
registration occurs. If a person were to X-copy the application onto another
machine, the application will still check for the key the first time it is
executed and therefore it will not run until it is also registered.
I did not use it in this example, but you could use the
original installation date and allow the user to use the product for some set
period of days prior to locking them out which could be useful if you are
authoring shareware with a time limited trial period. Here in this example, I
just lock them out from the beginning. This approach does not prevent the client
from over installing the product; it merely forces each installation to be
registered through the web service. You could easily modify the web service to
check and see if an individual license has been registered and prevent
additional installations of that license but for the purposes of this example, I
am only demonstrating the capture of each installation through the service.
As it stands, the example is not the be-all, end-all
solution to securing your application against theft. It does however offer an
approach for determining the extent of abuse you may be encountering with a
release of custom software to a client company. That information may be useful
in making an argument for building a more robust approach to licensing your
distributions.
Getting Started.
To get started, unzip the source code included with
this project. In the code example, you will find a web service, and a Win Form
application. Open the Web Service up and make any changes necessary to get it to
operate on your machine. The project containing the web service has an MS Access
database included and the database contains a single table used to collect the
installation information. In reality you'd probably configure a database and
table in SQL Server or Oracle to contain the data but for the purposes of this
demonstration it was easier to pack it up in Access. Aside from the MS Access
database, you will note that the web service project only contains a single
service entitled, "RegisterInstallation".
Once you've configured the web service, run it and
check it with the proxy to validate that it is updating the database correctly:
Figure 1: Run the Web Service
With the web service running, select the link to "RegisterInstallation":
Figure 2: Testing the "RegisterInstallation" Web
Service
Once you've keyed in some sample values as indicated in
Figure 2, click on the "Invoke" button and view the response, if successful, you
should see something like what is shown in Figure 3:
Figure 3: The web service returns "true" if the
installation information is saved to the Database
Given the web service has returned a true, take a look
at the database to confirm that the values entered have been stored in the
table:
Figure 4: The AppInstallers Table Updated By The Web
Service
Having confirmed that the web service is up and
running, open the application solution into Visual Studio 2005. Examine the
content of the project in the solution explorer, you should see the following:
Figure 5: RegTest Application Solution Content
Within the solution, note that the registration service
has been added as a web reference, you will likely need to update this
reference. The app.config file is the default version and can be ignored
here. The classes worthy of note are the frmMain.vb class and the
RegistrationCheck.vb class. The main form class (frmMain.vb) represents your
application, and the registration check class is used to capture information
about the machine containing the application and package up for submittal to the
registration web service.
Assuming that the web service and the project
references check out, we can now take a look at the code.
Code: Web Service Project - Registration
Service
Locate the RegistrationService.asmx file contained in "RegistrationSvc"
web service solution and open it up in the IDE. You will note that this is a
very simple service and in general, all that it does is expose a function
entitled, "RegisterInstallation". This function receives a collection of
arguments containing the information about the installer and formats and
executes an insert query against the MS Access database used to contain the
installer information.
The import section of the code in this class has been
amended to include the portions of the System.Data library that pertain to use
of an OleDb data source:
Imports
System.Web
Imports
System.Web.Services
Imports
System.Web.Services.Protocols
Imports
System.Data
Imports
System.Data.OleDb
<WebService(Namespace:="http://localhost/RegistrationService/")>
_
<WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)>
_
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()>
_
Public
Class Service
Inherits System.Web.Services.WebService
The
class declaration is cleverly called "Service" and the attributes applied to the
class are also in the default configuration with the exception of the updated
namespace. The only function in the service exposed as a web method is the
RegisterInstallation function; its code is as follows:
<WebMethod()> _
Public
Function RegisterInstallation(ByVal
strDomainAndUser As
String, _
ByVal
strComputerName As
String, _
ByVal
strApplication As
String, _
ByVal
strVersion As String,
_
ByVal
strAppID As String,
_
ByVal
strInstallDate As
String) As
Boolean
Dim conn As
OleDbConnection = New
OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;"
& _
"Data
Source=c:\inetpub\wwwroot\RegistrationSvc\Registration.mdb;" & _
"User
ID=;Password=;")
Try
Dim
strBuilder As New
System.Text.StringBuilder
strBuilder.Append("INSERT
INTO AppInstallers ")
strBuilder.Append("(DomainAndUser,
ComputerName, Application,
Version, AppID, InstallDate) VALUES(")
strBuilder.Append("'"
& strDomainAndUser & "', ")
strBuilder.Append("'"
& strComputerName & "', ")
strBuilder.Append("'"
& strApplication & "', ")
strBuilder.Append("'"
& strVersion & "', ")
strBuilder.Append("'"
& strAppID & "', ")
strBuilder.Append("'"
& strInstallDate & "')")
Dim sSQL As
String = strBuilder.ToString()
Dim cmd As
OleDbCommand = New OleDbCommand(sSQL, conn)
Try
conn.Open()
cmd.ExecuteNonQuery()
conn.Close()
Catch ex As
Exception
conn.Close()
Return
False
End Try
Catch ex As
Exception
conn.Close()
Return False
End Try
conn.Close()
Return True
End
Function
This
function is pretty straight forward; the arguments detailing the installer's
information are passed as arguments to the function, the function then takes the
arguments directly and passes them to an insert query against the MS Access
database. Once the execution of the insert statement has completed, the function
returns a Boolean set to true if the data is passed to the database or false if
the operation fails to update the database.
That is
all there is to the web service; the calling application will gather the user's
information and, if appropriate, connect to the web service and post the data to
the database used to house the installer information.
One
point to make here is with regards to the setting of the installation date; in
this example, the value is passed into the web method from the client. If you
are interested in creating a grace period for using the application (e.g., allow
the user to use the application for 30 days without registering it) you may wish
to set this value on server side instead of the client. This will prevent the
user from changing the date on their machine to gain additional, free use of the
application.
Next,
open the "RegTest" solution into the IDE and we can take a look at that.
Code: RegistrationCheck Class.
First off, open the "RegistrationCheck.vb" class into
the code window. You will note that the class contains two subroutines and one
function. The imports statements and class declaration are as follows:
Imports
System
Imports
System.Windows.Forms.SystemInformation
Public
Class RegistrationCheck
By
importing the "SystemInformation" library, we are able to capture the
information used to identify the machine and user running the application. This
will provide us with the source for the arguments passed to the web service.
Now take
a look at the function, "CheckProductRegistration". This function is public and
is the means by which the application checks for prior registrations and then
gathers and submits data to the web service during the initialization of an
unregistered version of the application. The code for this function is as
follows:
Public
Function CheckProductRegistration()
As Boolean
' Check registry for key,
' create the key if it does not exist
CheckForOrMakeRegKey()
' Check the key to see if application has been
registered
' the key DA\SOMLA\1.0 is nonsense; it is just there
to confuse
anyone that tries to
' defeat the registration process. (actually, SOMLA
is some software
I
used in the
' mid 1980's to test aircraft crash effects upon a
seated aircraft
occupant, but you
' can put whatever you want in there, use Bullwinkle
or cDc if you
don't like SOMLA)
' Set this boolean upon determining whether or not
' the product has been registered
Dim blnIsRegistered
As Boolean =
False
Dim key As
Microsoft.Win32.RegistryKey
Try
key =
Microsoft.Win32.Registry.CurrentUser.OpenSubKey
("SOFTWARE\\DA\\SOMLA\\1.0")
If (Not key
Is Nothing)
Then
Dim intVal As
Integer = CType(key.GetValue("dval"),
Integer)
If intVal = 0 Then
blnIsRegistered = False
ElseIf intVal = 1
Then
blnIsRegistered = True
Return True
'
it is already registered so just return true
' and exit out of function
End If
Else
' The key does not exist, the application has not
been
registered
blnIsRegistered = False
End If
key.Close()
Catch
' do nothing here, we want this to be pretty quiet...
End Try
' Registration has not occurred, so try to register
it
If blnIsRegistered =
False Then
' Obtain the user name currently logged on to this
computer
Dim strUser As
String
strUser =
System.Windows.Forms.SystemInformation.UserName.ToString()
' Obtain the user's domain name for the current
session
Dim strDomain As
String
strDomain =
System.Windows.Forms.SystemInformation.UserDomainName.ToString()
' Combine the user and domain names into a single
entry
Dim
strDomainAndUser As
String = strDomain & "\" & strUser
' Get the name of the computer the application
' is installed on
Dim strComputerName
As String
strComputerName
=
System.Windows.Forms.SystemInformation.ComputerName.ToString()
' Create a local instance of the web service
Dim ws As
New RegistrationService.Service
' The web service returns a boolean to indicate
' success or failure in execution
Dim blnTest As
Boolean = False
' Run the web service and load the installer's
' information into the database
Try
blnTest =
ws.RegisterInstallation(strDomainAndUser,
strComputerName,
_
"RegTest", "1.0",
"{376FEDC1-BC65-4c6a-B3C2-49CD57BFD127}", _
Now.ToLongDateString())
Catch
' Create registry entry and set it to false
CreateKeyAndSetValue(0)
End Try
'NOTE: the GUID is canned as is meant to mark the
install
' as a specific copy of the application
' create new guids and registry keys for other
customers
' and applications - this will allow you to
track who bought
' the software versus where it ends up getting
installed.
If blnTest = True
Then
' Create registry entry and set it to 1 - registered
CreateKeyAndSetValue(1) ' IS registered
Return True
Else
' Create registry entry and set it to 0 - not
registered
CreateKeyAndSetValue(0) ' NOT registered
Return False
End If
End If
End Function
The
first thing this function does is call a private subroutine used to check for
the existence of the registry key used to check for prior registrations of the
application. If the key does not exist (first use), the key will be created in
the subroutine. The key used
"SOFTWARE\\DA\\SOMLA\\1.0" is
just some nonsense used to through someone off if they were to try to find and
manually edit the key value used to determine whether or not the application has
been registered. As I mention in the comments, SOMLA is an application dating
back to the 1980's; it was used to run analysis of the effects of a crash upon
the human spine. "DA" was for district attorney. You can change these values to
whatever you want but you should make some effort to conceal the purpose of the
key and its value.
Next the
function gets the value of the key used to mark the application as having
successfully been submitted to the registration web service; if the value
indicates that the application has not been registered or activated through the
service, it sets a Boolean to false, the Boolean is then tested and if it is
false, the function will attempt to gather the user's information and submit it
to the web service for registration. If the product is then registered, the
calling method is notified of the success and the registry value is updated to
indicate that the product has been activated through the service. If the Boolean
returns false, the user is booted out of the application, else the user is
granted access to the application.
The two
subroutines in this class are merely used to support the function by creating or
confirming the existence of a registry key, and by allowing the function to set
the key value associated with the registration of the application. The code for
these two subroutines is as follows:
Private
Sub CheckForOrMakeRegKey()
Try
Dim regVersion As
Microsoft.Win32.RegistryKey
regVersion =
Microsoft.Win32.Registry.CurrentUser.OpenSubKey _
("SOFTWARE\\DA\\SOMLA\\1.0",
True)
If regVersion Is
Nothing Then
regVersion
=
Microsoft.Win32.Registry.CurrentUser.CreateSubKey _
("SOFTWARE\\DA\\SOMLA\\1.0")
End If
regVersion.Close()
Catch
'do nothing
End Try
End Sub
Private
Sub CreateKeyAndSetValue(ByVal
intVal As Integer)
Try
' Declare a registry key variable and set it to
' be the fake SOMLA key
Dim regVersionKey As
Microsoft.Win32.RegistryKey
regVersionKey =
Microsoft.Win32.Registry.CurrentUser.OpenSubKey _
("SOFTWARE\\DA\\SOMLA\\1.0",
True)
' If the key exists, then set its dval key value
' to the integer passed into this subroutine
If (Not
regVersionKey Is
Nothing) Then
regVersionKey.SetValue("dval",
intVal)
regVersionKey.Close()
End If
Catch
'do
nothing
End Try
End
Sub
That
concludes the discussion of the contents of this class. Next, open up the main
form (frmMain.vb) class into the code window. The only item worthy of note in
the main form's code is the handler for the load event:
Private
Sub frmMain_Load(ByVal
sender As System.Object,
ByVal e As
System.EventArgs) Handles
MyBase.Load
' Each time the application is loaded, we check for
the existence of
' the
registry entry:
Dim rc As
New RegistrationCheck
Dim bln As
Boolean
bln =
rc.CheckProductRegistration()
' If the bln indicates that the application is not
registered (and
therefore
' could not be registered, then tell them Goodbye and
exit the
application.
If bln = False
Then
MessageBox.Show("You have not registered this
product.
Registration requires internet access.",
"Registration Failed",
MessageBoxButtons.OK)
Application.Exit()
End If
End
Sub
This
code is pretty simple, it merely creates an instance of the "RegistrationCheck"
class, sets a Boolean variable to capture the results of the attempt to register
the product, and then processes the evaluation of the returned Boolean. If the
Boolean is false, the user is notified that product activation requires an
internet connection and then the application is terminated if activation has not
occurred. The user will continue to be met with this response until they permit
the application to connect to the internet and subsequently activate the
product.
Naturally, you can modify what occurs in response to a failed activation. For
example, if you want to let the user use the product for thirty days after the
original installation, you could amend the web service to return the initial
installation date and compare that date to today's date and then decide whether
or not to allow them to use the application.
Summary.
The sample application and web service are meant to
convey an approach to activating a product. This is not a particularly robust
way of doing it and it certainly is not foolproof. The approach could easily be
compromised if the user were able to locate and manually edit the registry
value. The approach does show you a general approach to product activation and
is a good way of collecting specific information about who is using the software
and determining which uniquely marked copy of the software the installer/user is
running on a specific machine.