We sent XML that was signed with public/private keys and we just used XPath to figure out what modules were allowed. We would load the file into memory from disk on application start up and then would persist to disk any time they updated their license.
We stored a bunch of information in that XML document such as trials, machine name, machine hash (which took a combination of factors) and sent those all back to our server on registration/update. We were able to track the number of licenses used as well as the number of machines and which machines had licenses.
Then comes the issue of non-internet connected machines. We had our program calculate the signed data based on the factors listed above and then had them paste it in a special section on our website. We then generated our key for them and told them to paste it into the application which then persisted to disk. They would have to do that every time they wanted to update but it was very easy and low friction.
Was it the most effeicent way of doing things? Probably not. Was it the most secure? Definitely not. But it was easy to implement, easy to use and provided, what we thought, was a great UX for the end user while still maintaining some sort of licensing controls.
*EDIT: On the server backend we had just a simple database that had each module, customer, machine name, and access (probably some more info, it's been a few years since I've looked at this as I've moved on).
EDIT 2: I've actually thought of taking what I learned from that and starting a licensing as a service that will provide the flexibility of what I implemented so that you could provide fine grain access in your product fairly easily. I'm not sure if there would be enough interest though.