Offering trial versions of apps is a common strategy to attract users by allowing them to evaluate software before purchasing. However, designing a secure and user-friendly test functionality can be challenging.
In this article, I will guide you setting up a 30-day trial period for a C# Windows Forms application. This approach ensures a secure experience by storing encrypted proof information and linking the proof to a hardware-encrypted identifier.
Generate a hardware ID
The first step is that generate a unique identifier based on the user’s hardware configuration.
This process involves collecting IDs from the processor and main hard drive, which are then merged into a single string.
The string is then encrypted to securely store the unique hardware ID, making it difficult to replicate or tamper with.
public static class HardwareId { public static string GetHardwareId() { return GetProcessorId() + "-" + GetDiskId(); } private static string GetProcessorId() { string processorId = string.Empty; var searcher = new ManagementObjectSearcher("SELECT ProcessorId FROM Win32_Processor"); foreach (var item in searcher.Get()) { processorId = item["ProcessorId"].ToString(); break; } return processorId; } private static string GetDiskId() { string diskId = string.Empty; var searcher = new ManagementObjectSearcher("SELECT SerialNumber FROM Win32_DiskDrive WHERE MediaType="Fixed hard disk media""); foreach (var item in searcher.Get()) { diskId = item["SerialNumber"].ToString().Trim(); break; } return diskId; } }
Encrypt test information
After creating the unique ID, the next step is to encode it using Advanced Encryption Standard method for its balance of security and performance.
Once encrypted, the data is then stored in the Windows Registry.
public static class EncryptionService { private static readonly string encryptionKey = "EncryptionKey123"; public static string EncryptString(string plainText) { byte[] initVector = new byte[16]; byte[] array; using (Aes aes = Aes.Create()) { aes.Key = Encoding.UTF8.GetBytes(encryptionKey); aes.IV = initVector; ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV); using (MemoryStream memoryStream = new MemoryStream()) { using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) { using (StreamWriter streamWriter = new StreamWriter(cryptoStream)) { streamWriter.Write(plainText); } array = memoryStream.ToArray(); } } } return Convert.ToBase64String(array); } public static string DecryptString(string cipherText) { byte[] initVector = new byte[16]; byte[] buffer = Convert.FromBase64String(cipherText); using (Aes aes = Aes.Create()) { aes.Key = Encoding.UTF8.GetBytes(encryptionKey); aes.IV = initVector; ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV); using (MemoryStream memoryStream = new MemoryStream(buffer)) { using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) { using (StreamReader streamReader = new StreamReader(cryptoStream)) { return streamReader.ReadToEnd(); } } } } } }
Manage judgment logic
In our scenario, trial management is based on two processes:
- raising the judgment
- and monitoring its status.
Tall first releasethe application stores the trial start date and the hardware ID, both in encrypted form.
In subsequent sessions, it verifies the current hardware ID with the previously stored one and determines how many days are left on the trial by comparing today’s date with the encoded start date. If the hardware IDs match and the test has failed, the application continues to function as intended. Otherwise, you should provide options for the user to purchase or extend the trial.
public static class TrialManager { private const string RegistryPath = @"SOFTWARE\YourCompany\YourProduct"; private const string StartDateValueName = "StartDate"; private const string HardwareIdValueName = "HardwareId"; public static void InitializeTrial() { using (var key = Registry.CurrentUser.CreateSubKey(RegistryPath)) { if (key.GetValue(StartDateValueName) == null || key.GetValue(HardwareIdValueName) == null) { string startDate = EncryptString(DateTime.UtcNow.ToString("o")); // Data de start a perioadei de încercare string hardwareId = EncryptString(HardwareId.GetHardwareId()); // ID-ul hardware criptat key.SetValue(StartDateValueName, startDate); key.SetValue(HardwareIdValueName, hardwareId); } } } public static bool IsTrialValid() { using (var key = Registry.CurrentUser.OpenSubKey(RegistryPath)) { if (key != null) { string encryptedStartDate = key.GetValue(StartDateValueName)?.ToString(); string encryptedHardwareId = key.GetValue(HardwareIdValueName)?.ToString(); if (!string.IsNullOrEmpty(encryptedStartDate) && !string.IsNullOrEmpty(encryptedHardwareId)) { string decryptedStartDate = DecryptString(encryptedStartDate); string decryptedHardwareId = DecryptString(encryptedHardwareId); if (decryptedHardwareId == HardwareId.GetHardwareId()) { DateTime startDate = DateTime.Parse(decryptedStartDate); return DateTime.UtcNow <= startDate.AddDays(30); } } } } return false; } public static int DaysRemainingInTrial() { using (var key = Registry.CurrentUser.OpenSubKey(RegistryPath)) { if (key != null) { string encryptedStartDate = key.GetValue(StartDateValueName)?.ToString(); if (!string.IsNullOrEmpty(encryptedStartDate)) { string decryptedStartDate = EncryptionService.DecryptString(encryptedStartDate); DateTime startDate = DateTime.Parse(decryptedStartDate); TimeSpan timeSpan = startDate.AddDays(30) - DateTime.UtcNow; return (int)timeSpan.TotalDays; } } } return -1; } private static string EncryptString(string input) => EncryptionService.EncryptString(input); private static string DecryptString(string input) => EncryptionService.DecryptString(input); }
Integrate the test manager
The last step is to configure the Test Manager in our application.
To keep the user informed about the trial status, display a message at the start, indicating whether the app is in trial mode and how many days are left.
If the trial has expired, notify the user and provide options to purchase the full version or extend the trial, improving the user experience and encouraging conversion to the full version.
static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); // Initialize the trial period when the application runs for the first time TrialManager.InitializeTrial(); int daysRemaining = TrialManager.DaysRemainingInTrial(); if (daysRemaining >= 0) { MessageBox.Show($"You are in trial mode. You have {daysRemaining} days left in your trial period.", "Trial Mode", MessageBoxButtons.OK, MessageBoxIcon.Information); Application.Run(new Form1()); } else { MessageBox.Show("Your trial period has expired. Please purchase the full version to continue using the application.", "Trial Expired", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); // Optional: Provide a way for the user to purchase or extend the trial // Application.Run(new PurchaseForm()); } }
Build and install
Now, it’s time to create the installer package for your application and install it to test the trial functionality.
You can use the Advanced Installer add-in for Visual Studio to build the package. For more details on how you can easily create the MSI installer for your C# application, check out our dedicated article guide on this topic or the video below:
Advanced installer trial and licensing features
For a smooth integration of trial and licensing functionalities, you can try the Advanced Installer.
The advanced installer lets you easily set up a trial period, manage license keys, and ensure users have a clear path to evaluating your software.
For detailed instructions on configuring Advanced Licensing features, explore our how-to articles: https://www.advancedinstaller.com/user-guide/qat-trial.html.
CONCLUSION
Implementing a secure and user-friendly trial period in a C# Windows Forms application can become a complex process.
The Advanced Installer tool offers a simpler and safer alternative. Its dedicated trial and licensing management feature minimizes complexity and potential for error, ensuring a streamlined process for both developers and end users.