Hi all,
I am almost ready to start selling my first Xojo desktop application for Mac and Windows - this is a project that I moved over from Filemaker, since they have deprecated runtimes, which is what I am currently selling.
In the Filemaker version of my app, I use the PersistentID function to prevent people from copying an unlocked copy of the app to a new computer. On start up of the app, I check that the PersistentID matches the PersistentID that was stored in the database when the license key was first validated. Whilst this mostly works, the PersistentID is not always persistent! I have had a couple of users who’s computers change PersistentID, seemingly at random. From what I’ve read, this may possibly be related to changing hardware components or changing MAC address.
In any case, as Xojo doesn’t have an equivalent built in function, I have tried to design my own. This is based mainly on code I found in the Xojo Forum by Dave S. From my limited testing so far, it seems to serve its purpose. I was hoping to get some feedback as to whether there are any potential pitfalls that I have missed, or if there is a more elegant way to do this.
I have tried to build in “soft” matching, so that if the determined serial no changes for some unknown reason, but the MAC address still matches, then it still counts as a match. I am a self-taught hack, so my apologies if you look at this code and think “WTF is this guy doing…”
[code]Public Function MachineID(type As MachineEnum) as String
Dim retval As String
Select Case type
Case MachineEnum.SerialNo, MachineEnum.SerialHash
Dim sh As New Shell
Dim s As String
Dim x As Integer
Dim bytes As String
Dim hex As String
#If TargetMacOS
sh.execute "/usr/sbin/ioreg -l | /usr/bin/grep IOPlatformSerialNumber"
s=sh.Result
x=InStr(s,"=")
If x>0 Then s=ReplaceAll(Trim(Mid(s,x+1)),ChrB(34),"")
#ElseIf TargetWindows
sh.Execute "wmic bios get serialnumber" 'works for Vista and newer
s=sh.Result
s=ReplaceAll(s, "SerialNumber", "")
s=Trim(s)
'// use IPCONFIG /all and parse the "Physical Address from "Local Area Connection" Block
'// will be something like C4-2C-03-02-31-26 [C42c03023126 take first 11 char = C42C0302312]
'sh.Execute("ipconfig /all")
'If sh.ErrorCode = 0 Then
's = sh.Result
's = Mid(s,InStr(s,"Local Area Connection")+21)
's = Mid(s,InStr(s,"Physical Address")+16)
's = Left(s,InStr(s,EndOfLine))
's= ReplaceAll(Trim(Right(s,19)),"-","")
'End If
#EndIf
If type = MachineEnum.SerialHash Then
bytes = MD5(s)
// To display the hash, convert it to hexadecimal
hex = EncodeHex(bytes)
s=hex
End If
retval = s
Case MachineEnum.MACAddress
If System.NetworkInterfaceCount > 0 Then
Dim iface As NetworkInterface = System.GetNetworkInterface(0)
retval = iface.MACAddress
Else
retval = “NoNetworkInterface”
End If
End Select
Return retval
End Function
[/code]
[code]Public Function MatchMachineIdentity() as Boolean
Dim machId As String
Dim serialNo As String
Dim macAddress As String
machId = MachineID(MachineEnum.SerialHash)
serialNo = MachineID(MachineEnum.SerialNo)
macAddress = MachineID(MachineEnum.MACAddress)
'connect to UserAccountDatabase
'(done in MainWindow.open event)
'get data
Dim sql As String
Dim rs As RecordSet
sql = "SELECT Account_ID, Machine_ID, ComputerSerialNo, ComputerMACAddress "
sql = sql + "FROM UserAccount "
sql = sql + "WHERE TRUE "
sql = sql + “ORDER BY Account_ID”
rs = UserAccountDatabase.SQLSelect(sql)
Dim id As Integer
Dim machIdOnfIle As String
Dim serialNoOnfIle As String
Dim macAddressOnfIle As String
If rs <> Nil Then
id = rs.Field(“Account_ID”).IntegerValue
machIdOnfIle = rs.Field(“Machine_ID”).StringValue
serialNoOnfIle = rs.Field(“ComputerSerialNo”).StringValue
macAddressOnfIle = rs.Field(“ComputerMACAddress”).StringValue
If machId = machIdOnfIle Then
'machineIDs match
Return True
Else
For i as Integer = 0 to System.NetworkInterfaceCount-1
Dim n as NetworkInterface
n = System.GetNetworkInterface( i )
If macAddressOnfIle = n.MACAddress Then
'macAddress still matches
sql = "UPDATE UserAccount SET Machine_ID = '" + machId + "', ComputerSerialNo = '" + serialNo + "'"
UserAccountDatabase.SQLExecute(sql)
If UserAccountDatabase.Error Then
MsgBox(UserAccountDatabase.ErrorMessage + EndOfLine + CurrentMethodName)
End If
Return True
End If
Next
Return False
End If
Else
If UserAccountDatabase.Error Then
MsgBox(sql + EndOfLine + UserAccountDatabase.ErrorMessage + EndOfLine + CurrentMethodName)
Quit
End If
End If
End Function
[/code]