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
If System.NetworkInterfaceCount > 0 Then
Dim iface As NetworkInterface = System.GetNetworkInterface(0)
retval = iface.MACAddress
retval = “NoNetworkInterface”
[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)
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
If UserAccountDatabase.Error Then
MsgBox(sql + EndOfLine + UserAccountDatabase.ErrorMessage + EndOfLine + CurrentMethodName)