Skip to content

Commit

Permalink
Support running custom apps as services
Browse files Browse the repository at this point in the history
  • Loading branch information
mlocati committed Apr 18, 2021
1 parent 6be7e03 commit 6ec6f3f
Show file tree
Hide file tree
Showing 15 changed files with 1,165 additions and 190 deletions.
82 changes: 19 additions & 63 deletions Program.cs
@@ -1,17 +1,28 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.ServiceProcess;
using System.Windows.Forms;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Windows.Forms;

namespace MLocati.ServicesControl
{
static class Program
{
public static List<ServiceController> Services;
public static ServiceConfigManager ConfigManager;

private static Icon _icon = null;
public static Icon Icon
{
get
{
if (Program._icon == null)
{
Program._icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location);
}
return Program._icon;
}
}

[STAThread]
static void Main(string[] args)
Expand All @@ -33,78 +44,23 @@ static void Main(string[] args)
}
return;
}
Program.ReloadServices();
if (Program.Services.Count == 0)
Program.ConfigManager = new ServiceConfigManager(Path.ChangeExtension(Application.ExecutablePath, ".txt"));
if (Program.ConfigManager.ServiceConfigs.Length == 0)
{
using (frmSetServices f = new frmSetServices())
{
f.StartPosition = FormStartPosition.CenterScreen;
f.ShowDialog();
if (Program.Services.Count == 0)
if (Program.ConfigManager.ServiceConfigs.Length == 0)
{
return;
}
}
return;
}
using (frmMain frm = new frmMain())
{
Application.Run(frm);
}
foreach (ServiceController scd in Program.Services)
{
try
{ scd.Dispose(); }
catch
{ }
}
}

public static string ConfigFileName
{
get
{
return Path.ChangeExtension(Application.ExecutablePath, ".txt");
}
}

public static void ReloadServices()
{
Program.Services = new List<ServiceController>();
string configFilename = Program.ConfigFileName;
if (File.Exists(configFilename))
{
using (FileStream configStream = new FileStream(configFilename, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (StreamReader configReader = new StreamReader(configStream, true))
{
string line;
while ((line = configReader.ReadLine()) != null)
{
string[] possibleNames = line.Split(new char[] { '\t' }, StringSplitOptions.RemoveEmptyEntries);
if (possibleNames.Length > 0)
{
ServiceController sc = WindowsServices.GetServiceControl(possibleNames);
if (sc != null)
Program.Services.Add(sc);
}
}
}
}
}
}

private static Icon _icon = null;
public static Icon Icon
{
get
{
if (Program._icon == null)
{
Program._icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location);
}
return Program._icon;
}
}
}
}
6 changes: 3 additions & 3 deletions Properties/AssemblyInfo.cs
Expand Up @@ -7,13 +7,13 @@
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Michele Locati")]
[assembly: AssemblyProduct("ServicesControl")]
[assembly: AssemblyCopyright("Copyright © Michele Locati 2015")]
[assembly: AssemblyCopyright("Copyright © Michele Locati 2015-2021")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

[assembly: ComVisible(false)]

[assembly: Guid("bfd7dc94-3a6f-4dea-a388-3bda99821617")]

[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyVersion("2.0.0.0")]
[assembly: AssemblyFileVersion("2.0.0.0")]
34 changes: 34 additions & 0 deletions ServiceConfig.cs
@@ -0,0 +1,34 @@
namespace MLocati.ServicesControl
{
public abstract class ServiceConfig
{
public readonly string ServiceName;
protected ServiceConfig(string serviceName)
{
this.ServiceName = serviceName;
}
}

public class SystemServiceConfig : ServiceConfig
{
public SystemServiceConfig(string serviceName)
: base(serviceName)
{
}

}

public class ServiceLikeServiceConfig : ServiceConfig
{
public readonly string Executable;
public readonly string CurrentDirectory;
public readonly string Arguments;
public ServiceLikeServiceConfig(string serviceName, string executable, string currentDirectory, string arguments)
: base(serviceName)
{
this.Executable = executable;
this.CurrentDirectory = currentDirectory;
this.Arguments = arguments;
}
}
}
117 changes: 117 additions & 0 deletions ServiceConfigManager.cs
@@ -0,0 +1,117 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

namespace MLocati.ServicesControl
{
internal class ServiceConfigManager
{
private readonly string Filename;

private ServiceConfig[] _serviceConfigs;
public ServiceConfig[] ServiceConfigs
{
get { return this._serviceConfigs; }
}

public ServiceConfigManager(string filename)
{
this.Filename = filename;
this.Reload();
}

public void Reload()
{
var serviceConfigs = new List<ServiceConfig>();
var lines = this.ReadFile();
var rxAttribs = new Regex(@"^[ \t]+(?<name>(executable|currentdirectory|arguments))[ \t]*:[ \t]*(?<value>.*?)[ \t]*$", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.ExplicitCapture);
for (var lineIndex = 0; lineIndex < lines.Length;)
{
var serviceName = lines[lineIndex++];
if (serviceName != serviceName.Trim())
{
throw new InvalidDataException($"Invalid line {lineIndex}: {serviceName}");
}
var attribs = new Dictionary<string, string>();
for (; lineIndex < lines.Length;)
{
var match = rxAttribs.Match(lines[lineIndex]);
if (!match.Success)
{
break;
}
var attribName = match.Groups["name"].Value.ToLower();
if (attribs.ContainsKey(attribName))
{
throw new InvalidDataException($"Duplicated attribute: {attribName}");
}
attribs.Add(attribName, match.Groups["value"].Value);
lineIndex++;
}
if (attribs.ContainsKey("executable"))
{
serviceConfigs.Add(new ServiceLikeServiceConfig(
serviceName,
attribs["executable"],
attribs.ContainsKey("currentdirectory") ? attribs["currentdirectory"] : "",
attribs.ContainsKey("arguments") ? attribs["arguments"] : ""
));
}
else
{
serviceConfigs.Add(new SystemServiceConfig(serviceName));
}
}
this._serviceConfigs = serviceConfigs.ToArray();
}

public void Save(ServiceConfig[] serviceConfigs)
{
StringBuilder sb = new StringBuilder();
foreach (var serviceConfig in ServiceConfigs)
{
sb.AppendLine(serviceConfig.ServiceName);
if (serviceConfig is ServiceLikeServiceConfig)
{
var sc = (ServiceLikeServiceConfig)serviceConfig;
sb.AppendLine($"\tExecutable: {sc.Executable}");
sb.AppendLine($"\tCurrentDirectory: {sc.CurrentDirectory}");
sb.AppendLine($"\tArguments: {sc.Arguments}");
}
}
using (var configStream = new FileStream(this.Filename, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None))
{
configStream.SetLength(0L);
using (var configWriter = new StreamWriter(configStream, Encoding.UTF8))
{
configWriter.Write(sb.ToString());
}
}
this._serviceConfigs = serviceConfigs;
}
private string[] ReadFile()
{
var result = new List<string>();
if (File.Exists(this.Filename))
{
using (var configStream = new FileStream(this.Filename, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (var configReader = new StreamReader(configStream, Encoding.UTF8))
{
string line;
while ((line = configReader.ReadLine()) != null)
{
line = line.TrimEnd();
if (line.Length > 0)
{
result.Add(line);
}
}
}
}
}
return result.ToArray();
}
}
}
26 changes: 26 additions & 0 deletions ServiceDriver.cs
@@ -0,0 +1,26 @@
using System;
using System.ServiceProcess;

namespace MLocati.ServicesControl
{
public interface ServiceDriver : IDisposable
{
string DisplayName { get; }

ServiceControllerStatus Status { get; }

bool CanStop { get; }
bool CanPauseAndContinue { get; }

void Start();

void Pause();
void Continue();

void Stop();

void Refresh();

void WaitForStatus(ServiceControllerStatus desiredStatus, TimeSpan timeout);
}
}

0 comments on commit 6ec6f3f

Please sign in to comment.