From 70efdc32dcd7906d187d657eeee62be63c14125b Mon Sep 17 00:00:00 2001 From: Donny Date: Fri, 30 Aug 2024 14:27:20 -0600 Subject: [PATCH] First Commit --- .gitignore | 111 ++++++ Installer1.cs | 29 ++ MPELicenseAgent.sln | 25 ++ MPELicenseAgent/MPELicenseAgent.csproj | 77 +++++ MPELicenseAgent/Program.cs | 25 ++ MPELicenseAgent/ProjectInstaller.Designer.cs | 68 ++++ MPELicenseAgent/ProjectInstaller.cs | 37 ++ MPELicenseAgent/ProjectInstaller.resx | 129 +++++++ MPELicenseAgent/Properties/AssemblyInfo.cs | 36 ++ MPELicenseAgent/Service1.Designer.cs | 37 ++ MPELicenseAgent/Service1.cs | 338 +++++++++++++++++++ MPELicenseAgent/packages.config | 4 + ProjectInstaller.cs | 64 ++++ Service1.cs | 326 ++++++++++++++++++ 14 files changed, 1306 insertions(+) create mode 100644 .gitignore create mode 100644 Installer1.cs create mode 100644 MPELicenseAgent.sln create mode 100644 MPELicenseAgent/MPELicenseAgent.csproj create mode 100644 MPELicenseAgent/Program.cs create mode 100644 MPELicenseAgent/ProjectInstaller.Designer.cs create mode 100644 MPELicenseAgent/ProjectInstaller.cs create mode 100644 MPELicenseAgent/ProjectInstaller.resx create mode 100644 MPELicenseAgent/Properties/AssemblyInfo.cs create mode 100644 MPELicenseAgent/Service1.Designer.cs create mode 100644 MPELicenseAgent/Service1.cs create mode 100644 MPELicenseAgent/packages.config create mode 100644 ProjectInstaller.cs create mode 100644 Service1.cs 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; + } + } + } +}