diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ac5abd9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,111 @@
+# Ignore Visual Studio temporary files, build results, and
+# files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (Mono)
+mono_crash.*
+
+# Auto-generated files
+*.vspscc
+*.vssscc
+
+# Build results
+[Dd]ebug/
+[Rr]elease/
+x64/
+x86/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Roslyn cache directories
+.vscode/
+.vscode-test/
+.vscode-oss/
+
+# NuGet Packages
+*.nupkg
+*.snupkg
+.nuget/
+packages/
+paket-files/
+.nuget/
+
+# Windows image file caches
+Thumbs.db
+ehthumbs.db
+
+# Folder config file
+desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# VS Code directories
+.vscode/
+.history/
+
+# Rider
+.idea/
+
+# JetBrains Rider config directory
+.idea/
+
+# Rider's default project-specific files
+.idea/
+.idea/.*
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# Azure Web Apps
+site/wwwroot/
+site/deployment/
+site/extensions/
+
+# Entity Framework Migrations
+Migrations/
+
+# Publish directory
+.publish/
+
+# Web config
+web.config
+app.config
+
+# Certificates
+*.pfx
+*.cer
+*.crt
+
+# Temporary files
+*.tmp
+*.temp
+
+# Log files
+*.log
+*.tlog
+
+# Add this section to ignore the MPELicenseTracker folder
+MPELicenseTracker/
+
+# Custom configuration files
+*.json
+*.env
+
+# Ignore all files and directories starting with a dot
+.*
+!.gitignore
+
+# Optional: Ignore the Visual Studio Code workspace file
+*.code-workspace
diff --git a/Installer1.cs b/Installer1.cs
new file mode 100644
index 0000000..89d6270
--- /dev/null
+++ b/Installer1.cs
@@ -0,0 +1,29 @@
+// Decompiled with JetBrains decompiler
+// Type: LicenseTrackerService.Installer1
+// Assembly: LicenseTrackerService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
+// MVID: 190801E4-3027-41A4-B03A-CBAE9DAB23BA
+// Assembly location: C:\MPE\MPELicenseTracker\LicenseTrackerService.exe
+
+using System.ComponentModel;
+using System.Configuration.Install;
+
+#nullable disable
+namespace LicenseTrackerService
+{
+ [RunInstaller(true)]
+ public class Installer1 : Installer
+ {
+ private IContainer components = (IContainer) null;
+
+ public Installer1() => this.InitializeComponent();
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && this.components != null)
+ this.components.Dispose();
+ base.Dispose(disposing);
+ }
+
+ private void InitializeComponent() => this.components = (IContainer) new System.ComponentModel.Container();
+ }
+}
diff --git a/MPELicenseAgent.sln b/MPELicenseAgent.sln
new file mode 100644
index 0000000..fa3c0fc
--- /dev/null
+++ b/MPELicenseAgent.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.10.35013.160
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MPELicenseAgent", "MPELicenseAgent\MPELicenseAgent.csproj", "{92405B10-B07E-4794-BC0D-230A599048F8}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {92405B10-B07E-4794-BC0D-230A599048F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {92405B10-B07E-4794-BC0D-230A599048F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {92405B10-B07E-4794-BC0D-230A599048F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {92405B10-B07E-4794-BC0D-230A599048F8}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {40E80C92-E24A-4BB1-AAB7-A4AF03AC2A06}
+ EndGlobalSection
+EndGlobal
diff --git a/MPELicenseAgent/MPELicenseAgent.csproj b/MPELicenseAgent/MPELicenseAgent.csproj
new file mode 100644
index 0000000..6feb457
--- /dev/null
+++ b/MPELicenseAgent/MPELicenseAgent.csproj
@@ -0,0 +1,77 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {92405B10-B07E-4794-BC0D-230A599048F8}
+ WinExe
+ MPELicenseAgent
+ MPELicenseAgent
+ v4.7.2
+ 512
+ true
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Component
+
+
+ ProjectInstaller.cs
+
+
+ Component
+
+
+ Service1.cs
+
+
+
+
+
+
+
+
+
+
+ ProjectInstaller.cs
+
+
+
+
\ No newline at end of file
diff --git a/MPELicenseAgent/Program.cs b/MPELicenseAgent/Program.cs
new file mode 100644
index 0000000..4a87541
--- /dev/null
+++ b/MPELicenseAgent/Program.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.ServiceProcess;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MPELicenseAgent
+{
+ internal static class Program
+ {
+ ///
+ /// The main entry point for the application.
+ ///
+ static void Main()
+ {
+ ServiceBase[] ServicesToRun;
+ ServicesToRun = new ServiceBase[]
+ {
+ new Service1()
+ };
+ ServiceBase.Run(ServicesToRun);
+ }
+ }
+}
diff --git a/MPELicenseAgent/ProjectInstaller.Designer.cs b/MPELicenseAgent/ProjectInstaller.Designer.cs
new file mode 100644
index 0000000..5369ba9
--- /dev/null
+++ b/MPELicenseAgent/ProjectInstaller.Designer.cs
@@ -0,0 +1,68 @@
+using System.Configuration.Install;
+using System.ServiceProcess;
+
+namespace MPELicenseAgent
+{
+ partial class ProjectInstaller
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Component Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+
+
+
+
+ this.serviceProcessInstaller1 = new System.ServiceProcess.ServiceProcessInstaller();
+ this.serviceInstaller1 = new System.ServiceProcess.ServiceInstaller();
+ //
+ // serviceProcessInstaller1
+ //
+ this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem; // Runs the service as the logged in user
+ this.serviceProcessInstaller1.Password = (string)null;
+ this.serviceProcessInstaller1.Username = (string)null;
+ //
+ // serviceInstaller1
+ //
+ this.serviceInstaller1.Description = "Used to track MPE license usage";
+ this.serviceInstaller1.DisplayName = "MPE License Tracker";
+ this.serviceInstaller1.ServiceName = "MPE License Tracker";
+
+ this.serviceInstaller1.StartType = System.ServiceProcess.ServiceStartMode.Automatic; //Make sure it starts automatically
+ //
+ // ProjectInstaller
+ //
+ this.Installers.AddRange(new System.Configuration.Install.Installer[] {
+ this.serviceProcessInstaller1,
+ this.serviceInstaller1});
+
+ }
+
+ #endregion
+
+ private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller1;
+ private System.ServiceProcess.ServiceInstaller serviceInstaller1;
+ }
+}
\ No newline at end of file
diff --git a/MPELicenseAgent/ProjectInstaller.cs b/MPELicenseAgent/ProjectInstaller.cs
new file mode 100644
index 0000000..767807d
--- /dev/null
+++ b/MPELicenseAgent/ProjectInstaller.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Configuration.Install;
+using System.Linq;
+using System.ServiceProcess;
+using System.Threading.Tasks;
+
+namespace MPELicenseAgent
+{
+ [RunInstaller(true)]
+ public partial class ProjectInstaller : System.Configuration.Install.Installer
+ {
+ public ProjectInstaller()
+ {
+ AfterInstall += serviceInstaller1_AfterInstall;
+
+ InitializeComponent();
+ }
+
+ private void serviceInstaller1_AfterInstall(object sender, InstallEventArgs e)
+ {
+ using (ServiceController serviceController = new ServiceController(this.serviceInstaller1.ServiceName))
+ {
+ if (serviceController.Status.Equals((object)ServiceControllerStatus.Running) || serviceController.Status.Equals((object)ServiceControllerStatus.StartPending))
+ serviceController.Stop();
+ serviceController.WaitForStatus(ServiceControllerStatus.Stopped);
+ serviceController.Start(); // Starts the service
+ serviceController.WaitForStatus(ServiceControllerStatus.Running);
+
+ // Automatic restarts ("Recovery" tab in service properties is sec via the command): +
+ // sc.exe failure "MPE License Tracker" reset= 0 actions= restart/60000
+ }
+ }
+ }
+}
diff --git a/MPELicenseAgent/ProjectInstaller.resx b/MPELicenseAgent/ProjectInstaller.resx
new file mode 100644
index 0000000..9f33750
--- /dev/null
+++ b/MPELicenseAgent/ProjectInstaller.resx
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ 17, 17
+
+
+ 194, 17
+
+
+ False
+
+
\ No newline at end of file
diff --git a/MPELicenseAgent/Properties/AssemblyInfo.cs b/MPELicenseAgent/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..c034a04
--- /dev/null
+++ b/MPELicenseAgent/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("MPELicenseAgent")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("MPE Engineering Ltd")]
+[assembly: AssemblyProduct("MPELicenseAgent")]
+[assembly: AssemblyCopyright("Copyright © MPE Engineering Ltd 2024")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("92405b10-b07e-4794-bc0d-230a599048f8")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/MPELicenseAgent/Service1.Designer.cs b/MPELicenseAgent/Service1.Designer.cs
new file mode 100644
index 0000000..33103ef
--- /dev/null
+++ b/MPELicenseAgent/Service1.Designer.cs
@@ -0,0 +1,37 @@
+namespace MPELicenseAgent
+{
+ partial class Service1
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Component Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ components = new System.ComponentModel.Container();
+ this.ServiceName = "Service1";
+ }
+
+ #endregion
+ }
+}
diff --git a/MPELicenseAgent/Service1.cs b/MPELicenseAgent/Service1.cs
new file mode 100644
index 0000000..6f8ead8
--- /dev/null
+++ b/MPELicenseAgent/Service1.cs
@@ -0,0 +1,338 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Management;
+using System.Net.Http;
+using System.ServiceProcess;
+using System.Text;
+using System.Threading.Tasks;
+using System.Timers;
+using System.Xml.Linq;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace MPELicenseAgent
+{
+ public partial class Service1 : ServiceBase
+ {
+ private string homeBaseAddress = "apilicenses.mpe.ca";
+ private string apiKey = "&v94gt8ZHFTTTeuT";
+ private string[] requestedPrograms = new string[30];
+ private Timer pollRateTimer;
+ private int pollRate;
+ private bool canPhoneHome = false;
+
+ public Service1()
+ {
+ InitializeComponent();
+ }
+
+ // Function that starts when the service is run
+ protected override async void OnStart(string[] args)
+ {
+ this.WriteToFile("\n*******************************************************\nMPE License Tracker\nMade by: Donavon McDowell\n");
+ try
+ {
+ await this.getHealth();
+ if (this.canPhoneHome)
+ {
+ this.WriteToFile(DateTime.Now.ToString() + " Connected to home base " + this.homeBaseAddress);
+ await this.getPollRate();
+ await this.getRequestedPrograms();
+ await this.getClientPrograms();
+ }
+ }
+ catch (Exception ex1)
+ {
+ Exception ex = ex1;
+ }
+ this.pollRateTimer = new Timer();
+ this.pollRateTimer.Interval = (double)this.pollRate;
+ this.pollRateTimer.Elapsed += new ElapsedEventHandler(this.OnElapsedTime);
+ this.pollRateTimer.Start();
+ }
+
+ protected override void OnStop()
+ {
+ if (this.pollRateTimer == null)
+ return;
+ this.pollRateTimer.Stop();
+ this.pollRateTimer.Dispose();
+ }
+
+ // Params: STRING data
+ // Description: Takes a string and writes it to an output / log file
+ // Location: Where the exe file for this service exists
+ public void WriteToFile(string data)
+ {
+ string str1 = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
+ if (!Directory.Exists(str1))
+ Directory.CreateDirectory(str1);
+ string str2 = Path.Combine(str1, "ServiceLog.txt");
+ if (File.Exists(str2) && new FileInfo(str2).Length / 1024L > 100L)
+ {
+ File.Delete(str2);
+ using (StreamWriter text = File.CreateText(str2))
+ text.WriteLine(data);
+ }
+ else
+ {
+ using (StreamWriter streamWriter = File.AppendText(str2))
+ streamWriter.WriteLine(data);
+ }
+ }
+
+
+ private async void OnElapsedTime(object source, ElapsedEventArgs e)
+ {
+ await this.getHealth();
+ if (!this.canPhoneHome)
+ return;
+ await this.getRequestedPrograms();
+ await this.getPollRate();
+ await this.getClientPrograms();
+ this.pollRateTimer.Interval = (double)this.pollRate;
+ }
+
+ // Checks the health of the api, if there are any issues it will wait and probe again in a little bit
+ // This is used so that we don't DDOS the reverse proxy
+ private async Task getHealth()
+ {
+ string baseUrl = "https://" + this.homeBaseAddress;
+ string endpoint = "/api/healthcheck";
+ HttpMethod method = HttpMethod.Get;
+ HttpClient httpClient = new HttpClient()
+ {
+ Timeout = TimeSpan.FromSeconds(4.0)
+ };
+ try
+ {
+ HttpResponseMessage response = await httpClient.GetAsync(baseUrl + endpoint);
+ if (response.IsSuccessStatusCode)
+ {
+ string responses = await response.Content.ReadAsStringAsync();
+ if (!string.IsNullOrEmpty(responses))
+ {
+ this.canPhoneHome = true;
+ }
+ else
+ {
+ this.canPhoneHome = false;
+ this.WriteToFile(DateTime.Now.ToString() + " Disconnected from home base " + this.homeBaseAddress);
+ }
+ responses = (string)null;
+ }
+ else
+ {
+ this.canPhoneHome = false;
+ this.WriteToFile(DateTime.Now.ToString() + " Disconnected from home base " + this.homeBaseAddress);
+ }
+ response = (HttpResponseMessage)null;
+ baseUrl = (string)null;
+ endpoint = (string)null;
+ method = (HttpMethod)null;
+ httpClient = (HttpClient)null;
+ }
+ catch (TaskCanceledException ex)
+ {
+ this.canPhoneHome = false;
+ this.WriteToFile(DateTime.Now.ToString() + " [Error]: Connection timed out while trying to phone home to " + this.homeBaseAddress);
+ baseUrl = (string)null;
+ endpoint = (string)null;
+ method = (HttpMethod)null;
+ httpClient = (HttpClient)null;
+ }
+ catch (Exception ex)
+ {
+ this.canPhoneHome = false;
+ this.WriteToFile(DateTime.Now.ToString() + " [Error]: There was an error in the getHealth() function. " + ex.Message);
+ baseUrl = (string)null;
+ endpoint = (string)null;
+ method = (HttpMethod)null;
+ httpClient = (HttpClient)null;
+ }
+ }
+
+ // This talks to the backend and gets all of the programs that we are interested in monitoring
+ private async Task getRequestedPrograms()
+ {
+ string baseUrl = "https://" + this.homeBaseAddress;
+ string endpoint = "/api/programs";
+ HttpMethod method = HttpMethod.Get;
+ try
+ {
+ string responses = await Service1.ApiClient.hitApi(baseUrl, endpoint, method);
+ string cleanedResponses = responses.Trim('[', ']');
+ string[] responseArray = cleanedResponses.Split(new string[1]
+ {
+ "\",\""
+ }, StringSplitOptions.None);
+ responseArray[0] = responseArray[0].Trim('"');
+ responseArray[responseArray.Length - 1] = responseArray[responseArray.Length - 1].Trim('"');
+ this.requestedPrograms = responseArray;
+ responses = (string)null;
+ cleanedResponses = (string)null;
+ responseArray = (string[])null;
+ baseUrl = (string)null;
+ endpoint = (string)null;
+ method = (HttpMethod)null;
+ }
+ catch (Exception ex)
+ {
+ this.WriteToFile(DateTime.Now.ToString() + " [Error]: There was an error in the getRequestedPrograms() function. " + ex?.ToString());
+ baseUrl = (string)null;
+ endpoint = (string)null;
+ method = (HttpMethod)null;
+ }
+ }
+
+ // This looks at the computer itself and determines any applications that match our requested programs
+ private async Task getClientPrograms()
+ {
+ Process[] processes = Process.GetProcesses();
+ string[] matchingProcesses = processes
+ .Select(p => p.ProcessName)
+ .Where(pn => this.requestedPrograms.Contains(pn, StringComparer.OrdinalIgnoreCase))
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .ToArray();
+
+ foreach (string processName in matchingProcesses)
+ {
+ Process[] processList = processes.Where(p => p.ProcessName.Equals(processName, StringComparison.OrdinalIgnoreCase)).ToArray();
+ foreach (Process process in processList)
+ {
+ string userName = GetProcessOwner(process.Id);
+ await this.sendMatchingPrograms(processName, userName);
+ }
+ }
+
+ processes = null;
+ matchingProcesses = null;
+ }
+
+ // Method to get the username of the process owner
+ private string GetProcessOwner(int processId)
+ {
+ try
+ {
+ string query = "Select * From Win32_Process Where ProcessID = " + processId;
+ using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(query))
+ using (ManagementObjectCollection processList = searcher.Get())
+ {
+ foreach (ManagementObject obj in processList)
+ {
+ object[] argList = new object[2] { string.Empty, string.Empty };
+ int returnVal = Convert.ToInt32(obj.InvokeMethod("GetOwner", argList));
+ if (returnVal == 0)
+ {
+ return argList[0]?.ToString(); // Return the username
+ }
+ }
+ }
+ }
+ catch
+ {
+ // Handle any exceptions
+ }
+ return "Unknown"; // Return 'Unknown' if unable to retrieve username
+ }
+
+
+ // This actually sends the matching applications to the backend
+ // This actually sends the matching applications to the backend
+ private async Task sendMatchingPrograms(string applicationName, string userName)
+ {
+ string baseUrl = "https://" + this.homeBaseAddress;
+ string endpoint = "/api/currentPrograms";
+ HttpMethod method = HttpMethod.Post;
+ try
+ {
+ var payload = new
+ {
+ applicationName = applicationName,
+ machineName = userName, // Replacing currentMachineName with userName
+ apiKey = this.apiKey
+ };
+ string response = await Service1.ApiClient.hitApi(baseUrl, endpoint, method, (object)payload);
+ Console.WriteLine("Response from server: " + response);
+ }
+ catch (HttpRequestException ex)
+ {
+ Console.WriteLine("Network error: " + ex.Message);
+ }
+ catch (JsonException ex)
+ {
+ Console.WriteLine("JSON parsing error: " + ex.Message);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("Error: " + ex.Message);
+ }
+ }
+
+ private async Task getPollRate()
+ {
+ string baseUrl = "https://" + this.homeBaseAddress;
+ string endpoint = "/api/pollrate";
+ HttpMethod method = HttpMethod.Get;
+ try
+ {
+ string response = await Service1.ApiClient.hitApi(baseUrl, endpoint, method);
+ JObject jsonObject = JObject.Parse(response);
+ string pollrateString = (string)jsonObject["pollrate"];
+ this.pollRate = int.Parse(pollrateString);
+ response = (string)null;
+ jsonObject = (JObject)null;
+ pollrateString = (string)null;
+ baseUrl = (string)null;
+ endpoint = (string)null;
+ method = (HttpMethod)null;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ baseUrl = (string)null;
+ endpoint = (string)null;
+ method = (HttpMethod)null;
+ }
+ }
+
+ public static class ApiClient
+ {
+ public static async Task hitApi(
+ string baseUrl,
+ string endpoint,
+ HttpMethod method,
+ object data = null)
+ {
+ string str;
+ using (HttpClient client = new HttpClient())
+ {
+ client.BaseAddress = new Uri(baseUrl);
+ HttpResponseMessage response;
+ if (method == HttpMethod.Get)
+ {
+ response = await client.GetAsync(endpoint);
+ }
+ else
+ {
+ if (!(method == HttpMethod.Post))
+ throw new NotSupportedException(string.Format("HTTP method {0} is not supported.", (object)method));
+ string jsonData = JsonConvert.SerializeObject(data);
+ StringContent content = new StringContent(jsonData, Encoding.UTF8, "application/json");
+ response = await client.PostAsync(endpoint, (HttpContent)content);
+ jsonData = (string)null;
+ content = (StringContent)null;
+ }
+ response.EnsureSuccessStatusCode();
+ str = await response.Content.ReadAsStringAsync();
+ }
+ return str;
+ }
+ }
+ }
+}
diff --git a/MPELicenseAgent/packages.config b/MPELicenseAgent/packages.config
new file mode 100644
index 0000000..e46d5a8
--- /dev/null
+++ b/MPELicenseAgent/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/ProjectInstaller.cs b/ProjectInstaller.cs
new file mode 100644
index 0000000..2b73900
--- /dev/null
+++ b/ProjectInstaller.cs
@@ -0,0 +1,64 @@
+// Decompiled with JetBrains decompiler
+// Type: LicenseTrackerService.ProjectInstaller
+// Assembly: LicenseTrackerService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
+// MVID: 190801E4-3027-41A4-B03A-CBAE9DAB23BA
+// Assembly location: C:\MPE\MPELicenseTracker\LicenseTrackerService.exe
+
+using System.ComponentModel;
+using System.Configuration.Install;
+using System.ServiceProcess;
+
+#nullable disable
+namespace LicenseTrackerService
+{
+ [RunInstaller(true)]
+ public class ProjectInstaller : Installer
+ {
+ private IContainer components = (IContainer) null;
+ private ServiceProcessInstaller serviceProcessInstaller1;
+ private ServiceInstaller serviceInstaller1;
+
+ public ProjectInstaller()
+ {
+ this.InitializeComponent();
+ this.serviceInstaller1.StartType = ServiceStartMode.Automatic;
+ this.AfterInstall += new InstallEventHandler(this.serviceInstaller1_AfterInstall);
+ }
+
+ private void serviceInstaller1_AfterInstall(object sender, InstallEventArgs e)
+ {
+ using (ServiceController serviceController = new ServiceController(this.serviceInstaller1.ServiceName))
+ {
+ if (serviceController.Status.Equals((object) ServiceControllerStatus.Running) || serviceController.Status.Equals((object) ServiceControllerStatus.StartPending))
+ serviceController.Stop();
+ serviceController.WaitForStatus(ServiceControllerStatus.Stopped);
+ serviceController.Start();
+ serviceController.WaitForStatus(ServiceControllerStatus.Running);
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && this.components != null)
+ this.components.Dispose();
+ base.Dispose(disposing);
+ }
+
+ private void InitializeComponent()
+ {
+ this.serviceProcessInstaller1 = new ServiceProcessInstaller();
+ this.serviceInstaller1 = new ServiceInstaller();
+ this.serviceProcessInstaller1.Account = ServiceAccount.LocalSystem;
+ this.serviceProcessInstaller1.Password = (string) null;
+ this.serviceProcessInstaller1.Username = (string) null;
+ this.serviceInstaller1.Description = "Used to track license usage";
+ this.serviceInstaller1.DisplayName = "MPE License Tracker";
+ this.serviceInstaller1.ServiceName = "MPELicenseTracker";
+ this.Installers.AddRange(new Installer[2]
+ {
+ (Installer) this.serviceProcessInstaller1,
+ (Installer) this.serviceInstaller1
+ });
+ }
+ }
+}
diff --git a/Service1.cs b/Service1.cs
new file mode 100644
index 0000000..88a357c
--- /dev/null
+++ b/Service1.cs
@@ -0,0 +1,326 @@
+// Decompiled with JetBrains decompiler
+// Type: LicenseTrackerService.Service1
+// Assembly: LicenseTrackerService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
+// MVID: 190801E4-3027-41A4-B03A-CBAE9DAB23BA
+// Assembly location: C:\MPE\MPELicenseTracker\LicenseTrackerService.exe
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.ServiceProcess;
+using System.Text;
+using System.Threading.Tasks;
+using System.Timers;
+
+#nullable disable
+namespace LicenseTrackerService
+{
+ public class Service1 : ServiceBase
+ {
+ private string homeBaseAddress = "apilicenses.mpe.ca";
+ private string apiKey = "&v94gt8ZHFTTTeuT";
+ private string[] requestedPrograms = new string[15];
+ private Timer pollRateTimer;
+ private int pollRate;
+ private bool canPhoneHome = false;
+ private IContainer components = (IContainer) null;
+
+ public Service1() => this.InitializeComponent();
+
+ protected override async void OnStart(string[] args)
+ {
+ this.WriteToFile("\n*******************************************************\nMPE License Tracker\nMade by: Donavon McDowell\n");
+ try
+ {
+ await this.getHealth();
+ if (this.canPhoneHome)
+ {
+ this.WriteToFile(DateTime.Now.ToString() + " Connected to home base " + this.homeBaseAddress);
+ await this.getPollRate();
+ await this.getRequestedPrograms();
+ await this.getClientPrograms();
+ }
+ }
+ catch (Exception ex1)
+ {
+ Exception ex = ex1;
+ }
+ this.pollRateTimer = new Timer();
+ this.pollRateTimer.Interval = (double) this.pollRate;
+ this.pollRateTimer.Elapsed += new ElapsedEventHandler(this.OnElapsedTime);
+ this.pollRateTimer.Start();
+ }
+
+ protected override void OnStop()
+ {
+ if (this.pollRateTimer == null)
+ return;
+ this.pollRateTimer.Stop();
+ this.pollRateTimer.Dispose();
+ }
+
+ private async void OnElapsedTime(object source, ElapsedEventArgs e)
+ {
+ await this.getHealth();
+ if (!this.canPhoneHome)
+ return;
+ await this.getRequestedPrograms();
+ await this.getPollRate();
+ await this.getClientPrograms();
+ this.pollRateTimer.Interval = (double) this.pollRate;
+ }
+
+ private async Task getHealth()
+ {
+ string baseUrl = "https://" + this.homeBaseAddress;
+ string endpoint = "/api/healthcheck";
+ HttpMethod method = HttpMethod.Get;
+ HttpClient httpClient = new HttpClient()
+ {
+ Timeout = TimeSpan.FromSeconds(4.0)
+ };
+ try
+ {
+ HttpResponseMessage response = await httpClient.GetAsync(baseUrl + endpoint);
+ if (response.IsSuccessStatusCode)
+ {
+ string responses = await response.Content.ReadAsStringAsync();
+ if (!string.IsNullOrEmpty(responses))
+ {
+ this.canPhoneHome = true;
+ }
+ else
+ {
+ this.canPhoneHome = false;
+ this.WriteToFile(DateTime.Now.ToString() + " Disconnected from home base " + this.homeBaseAddress);
+ }
+ responses = (string) null;
+ }
+ else
+ {
+ this.canPhoneHome = false;
+ this.WriteToFile(DateTime.Now.ToString() + " Disconnected from home base " + this.homeBaseAddress);
+ }
+ response = (HttpResponseMessage) null;
+ baseUrl = (string) null;
+ endpoint = (string) null;
+ method = (HttpMethod) null;
+ httpClient = (HttpClient) null;
+ }
+ catch (TaskCanceledException ex)
+ {
+ this.canPhoneHome = false;
+ this.WriteToFile(DateTime.Now.ToString() + " [Error]: Connection timed out while trying to phone home to " + this.homeBaseAddress);
+ baseUrl = (string) null;
+ endpoint = (string) null;
+ method = (HttpMethod) null;
+ httpClient = (HttpClient) null;
+ }
+ catch (Exception ex)
+ {
+ this.canPhoneHome = false;
+ this.WriteToFile(DateTime.Now.ToString() + " [Error]: There was an error in the getHealth() function. " + ex.Message);
+ baseUrl = (string) null;
+ endpoint = (string) null;
+ method = (HttpMethod) null;
+ httpClient = (HttpClient) null;
+ }
+ }
+
+ private async Task getRequestedPrograms()
+ {
+ string baseUrl = "https://" + this.homeBaseAddress;
+ string endpoint = "/api/programs";
+ HttpMethod method = HttpMethod.Get;
+ try
+ {
+ string responses = await Service1.ApiClient.hitApi(baseUrl, endpoint, method);
+ string cleanedResponses = responses.Trim('[', ']');
+ string[] responseArray = cleanedResponses.Split(new string[1]
+ {
+ "\",\""
+ }, StringSplitOptions.None);
+ responseArray[0] = responseArray[0].Trim('"');
+ responseArray[responseArray.Length - 1] = responseArray[responseArray.Length - 1].Trim('"');
+ this.requestedPrograms = responseArray;
+ responses = (string) null;
+ cleanedResponses = (string) null;
+ responseArray = (string[]) null;
+ baseUrl = (string) null;
+ endpoint = (string) null;
+ method = (HttpMethod) null;
+ }
+ catch (Exception ex)
+ {
+ this.WriteToFile(DateTime.Now.ToString() + " [Error]: There was an error in the getRequestedPrograms() function. " + ex?.ToString());
+ baseUrl = (string) null;
+ endpoint = (string) null;
+ method = (HttpMethod) null;
+ }
+ }
+
+ private async Task getClientPrograms()
+ {
+ Process[] processes = Process.GetProcesses();
+ string[] matchingProcesses = ((IEnumerable) processes).Select((Func) (p => p.ProcessName)).Where((Func) (pn => ((IEnumerable) this.requestedPrograms).Contains(pn, (IEqualityComparer) StringComparer.OrdinalIgnoreCase))).Distinct((IEqualityComparer) StringComparer.OrdinalIgnoreCase).ToArray();
+ string[] strArray = matchingProcesses;
+ for (int index = 0; index < strArray.Length; ++index)
+ {
+ string processName = strArray[index];
+ await this.sendMatchingPrograms(processName);
+ processName = (string) null;
+ }
+ strArray = (string[]) null;
+ processes = (Process[]) null;
+ matchingProcesses = (string[]) null;
+ }
+
+ private async Task sendMatchingPrograms(string applicationName)
+ {
+ string currentMachineName = Environment.MachineName;
+ string baseUrl = "https://" + this.homeBaseAddress;
+ string endpoint = "/api/currentPrograms";
+ HttpMethod method = HttpMethod.Post;
+ try
+ {
+ var payload = new
+ {
+ applicationName = applicationName,
+ machineName = currentMachineName,
+ apiKey = this.apiKey
+ };
+ string response = await Service1.ApiClient.hitApi(baseUrl, endpoint, method, (object) payload);
+ Console.WriteLine("Response from server: " + response);
+ payload = null;
+ response = (string) null;
+ currentMachineName = (string) null;
+ baseUrl = (string) null;
+ endpoint = (string) null;
+ method = (HttpMethod) null;
+ }
+ catch (HttpRequestException ex)
+ {
+ Console.WriteLine("Network error: " + ex.Message);
+ currentMachineName = (string) null;
+ baseUrl = (string) null;
+ endpoint = (string) null;
+ method = (HttpMethod) null;
+ }
+ catch (JsonException ex)
+ {
+ Console.WriteLine("JSON parsing error: " + ex.Message);
+ currentMachineName = (string) null;
+ baseUrl = (string) null;
+ endpoint = (string) null;
+ method = (HttpMethod) null;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("Error: " + ex.Message);
+ currentMachineName = (string) null;
+ baseUrl = (string) null;
+ endpoint = (string) null;
+ method = (HttpMethod) null;
+ }
+ }
+
+ private async Task getPollRate()
+ {
+ string baseUrl = "https://" + this.homeBaseAddress;
+ string endpoint = "/api/pollrate";
+ HttpMethod method = HttpMethod.Get;
+ try
+ {
+ string response = await Service1.ApiClient.hitApi(baseUrl, endpoint, method);
+ JObject jsonObject = JObject.Parse(response);
+ string pollrateString = (string) jsonObject["pollrate"];
+ this.pollRate = int.Parse(pollrateString);
+ response = (string) null;
+ jsonObject = (JObject) null;
+ pollrateString = (string) null;
+ baseUrl = (string) null;
+ endpoint = (string) null;
+ method = (HttpMethod) null;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ baseUrl = (string) null;
+ endpoint = (string) null;
+ method = (HttpMethod) null;
+ }
+ }
+
+ public void WriteToFile(string data)
+ {
+ string str1 = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
+ if (!Directory.Exists(str1))
+ Directory.CreateDirectory(str1);
+ string str2 = Path.Combine(str1, "ServiceLog.txt");
+ if (File.Exists(str2) && new FileInfo(str2).Length / 1024L > 100L)
+ {
+ File.Delete(str2);
+ using (StreamWriter text = File.CreateText(str2))
+ text.WriteLine(data);
+ }
+ else
+ {
+ using (StreamWriter streamWriter = File.AppendText(str2))
+ streamWriter.WriteLine(data);
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && this.components != null)
+ this.components.Dispose();
+ base.Dispose(disposing);
+ }
+
+ private void InitializeComponent()
+ {
+ this.components = (IContainer) new System.ComponentModel.Container();
+ this.ServiceName = nameof (Service1);
+ }
+
+ public static class ApiClient
+ {
+ public static async Task hitApi(
+ string baseUrl,
+ string endpoint,
+ HttpMethod method,
+ object data = null)
+ {
+ string str;
+ using (HttpClient client = new HttpClient())
+ {
+ client.BaseAddress = new Uri(baseUrl);
+ HttpResponseMessage response;
+ if (method == HttpMethod.Get)
+ {
+ response = await client.GetAsync(endpoint);
+ }
+ else
+ {
+ if (!(method == HttpMethod.Post))
+ throw new NotSupportedException(string.Format("HTTP method {0} is not supported.", (object) method));
+ string jsonData = JsonConvert.SerializeObject(data);
+ StringContent content = new StringContent(jsonData, Encoding.UTF8, "application/json");
+ response = await client.PostAsync(endpoint, (HttpContent) content);
+ jsonData = (string) null;
+ content = (StringContent) null;
+ }
+ response.EnsureSuccessStatusCode();
+ str = await response.Content.ReadAsStringAsync();
+ }
+ return str;
+ }
+ }
+ }
+}